0Pricing
FastAPI Backend Development Bootcamp · 강의

Beanie ODM을 활용한 문서 모델링

Pydantic 위에서 Beanie를 사용해 타입이 지정된 문서 모델, 인덱스 및 내장 구조를 정의합니다.

Beanie ODM을 활용한 문서 모델링은(는) CoddyKit의 무료 FastAPI Backend Development Bootcamp 강의입니다. 이것은 4개 중 2번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 FastAPI Backend Development Bootcamp 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. FastAPI Backend Development Bootcamp 강의에는 총 4개의 강의가 포함되어 있습니다.

이 강의의 일부는 아직 번역되지 않았으며 영어로 표시됩니다.

What Beanie Brings to FastAPI

Beanie is an asynchronous ODM (Object-Document Mapper) for MongoDB built directly on top of Pydantic and the async motor driver. In a FastAPI backend it gives you typed, validated documents that feel just like the Pydantic models you already use for request and response bodies.

  • Each document class maps to one MongoDB collection.
  • Each instance maps to one document (a JSON-like record).
  • Validation, serialization, and JSON Schema come for free from Pydantic v2.

Because everything is async, Beanie pairs naturally with FastAPI's async route handlers and avoids blocking the event loop on database I/O.

Your First Document Model

A Beanie model subclasses Document instead of Pydantic's BaseModel. Fields are declared with normal type hints, and Beanie automatically gives every document an id field backed by MongoDB's _id (an ObjectId).

  • Required fields have no default; optional fields use Optional[...] or a default value.
  • The collection name is derived from the class name unless you override it.

Below, Product becomes a collection of product documents.

from typing import Optional
from beanie import Document


class Product(Document):
    name: str
    price: float
    description: Optional[str] = None
    in_stock: bool = True


# An instance is just a validated Pydantic object until you insert it
item = Product(name="Keyboard", price=49.9)
print(item.name, item.price, item.in_stock)

Configuring the Collection with Settings

Beanie reads optional configuration from an inner class Settings. The most common option is name, which sets the MongoDB collection name explicitly instead of relying on the class name.

  • name — the collection name.
  • use_state_management — track changed fields for partial saves.
  • validate_on_save — re-run validation when saving an existing document.

Pinning the collection name keeps your schema stable even if you later rename the Python class.

from beanie import Document


class Product(Document):
    name: str
    price: float

    class Settings:
        name = "products"
        validate_on_save = True


print(Product.Settings.name)

Initializing Beanie at App Startup

Before any document can talk to MongoDB you must call init_beanie once, passing your motor database and the list of document models. In FastAPI this belongs in the lifespan handler so it runs at startup.

  • AsyncIOMotorClient creates the async connection.
  • document_models registers every model so Beanie can build indexes and queries.

This is framework/server wiring — it needs a live MongoDB, so treat it as a setup pattern, not a standalone script.

from contextlib import asynccontextmanager
from fastapi import FastAPI
from motor.motor_asyncio import AsyncIOMotorClient
from beanie import init_beanie


@asynccontextmanager
async def lifespan(app: FastAPI):
    client = AsyncIOMotorClient("mongodb://localhost:27017")
    await init_beanie(
        database=client.shop_db,
        document_models=[Product],
    )
    yield
    client.close()


app = FastAPI(lifespan=lifespan)

Field Validation with Pydantic

Because a Document is a Pydantic model, every validation tool you know still works: Field constraints, custom validators, and rich types like EmailStr or HttpUrl.

  • Field(gt=0) rejects non-positive prices.
  • Field(min_length=...) enforces string length.
  • Validation runs when the object is constructed, so bad data never reaches MongoDB.

This snippet is pure Pydantic-style validation and runs on its own.

from pydantic import BaseModel, Field, ValidationError


class Product(BaseModel):
    name: str = Field(min_length=1, max_length=80)
    price: float = Field(gt=0)
    sku: str = Field(pattern=r"^[A-Z]{3}-\d{4}$")


try:
    Product(name="Mouse", price=-5, sku="bad")
except ValidationError as e:
    print("Rejected:", len(e.errors()), "errors")

good = Product(name="Mouse", price=19.99, sku="MOU-0001")
print("Accepted:", good.sku)

Declaring Indexes with Indexed

Indexes make queries fast and can enforce uniqueness. Beanie offers two styles. The simplest is the Indexed wrapper applied to a field's type, which creates a single-field index.

  • Indexed(str, unique=True) builds a unique index — perfect for an email or SKU.
  • Beanie creates the index automatically during init_beanie.

Use unique indexes to push integrity rules down into the database rather than relying only on app checks.

import pymongo
from beanie import Document, Indexed
from pydantic import EmailStr


class User(Document):
    email: Indexed(EmailStr, unique=True)
    username: Indexed(str)
    age: int

    class Settings:
        name = "users"

Compound Indexes in Settings

For multi-field or advanced indexes, declare them in Settings.indexes using PyMongo's IndexModel. This is how you build compound indexes, control sort direction, or add a TTL.

  • List the fields with directions: pymongo.ASCENDING / DESCENDING.
  • Pass unique=True or expireAfterSeconds=... through the IndexModel.

Order matters: a compound index on (category, price) optimizes queries that filter by category and then sort by price.

import pymongo
from pymongo import IndexModel
from beanie import Document


class Product(Document):
    name: str
    category: str
    price: float

    class Settings:
        name = "products"
        indexes = [
            IndexModel(
                [("category", pymongo.ASCENDING), ("price", pymongo.DESCENDING)],
                name="category_price_idx",
            ),
        ]

Embedded Documents with BaseModel

MongoDB stores nested objects inside a single document. In Beanie an embedded structure is just a plain Pydantic BaseModel used as a field type — it is not a separate collection and has no id.

  • Embed when the nested data is owned by the parent and always read together (an address inside a user).
  • The whole structure is validated and serialized as one document.

Here Address is embedded inside the User document.

from pydantic import BaseModel
from beanie import Document


class Address(BaseModel):
    street: str
    city: str
    postal_code: str


class User(Document):
    name: str
    address: Address

    class Settings:
        name = "users"


u = User(name="Ada", address=Address(street="1 Main", city="Oslo", postal_code="0150"))
print(u.address.city)

Lists of Embedded Structures

A document field can hold a list of embedded models, which is ideal for one-to-many data that belongs entirely to the parent — think order line items or comments.

  • list[OrderItem] validates every element on construction.
  • Each item is serialized inline, so reading the order needs no extra query.

Prefer embedding lists when the collection is bounded and read with the parent; use references when it grows unbounded.

from pydantic import BaseModel
from beanie import Document


class OrderItem(BaseModel):
    product_name: str
    quantity: int
    unit_price: float


class Order(Document):
    customer: str
    items: list[OrderItem]

    class Settings:
        name = "orders"

    @property
    def total(self) -> float:
        return sum(i.quantity * i.unit_price for i in self.items)


order = Order(
    customer="Lin",
    items=[OrderItem(product_name="Pen", quantity=3, unit_price=1.5)],
)
print(order.total)

Referencing Other Documents with Link

When related data lives in its own collection and is shared or large, use a reference instead of embedding. Beanie's Link[OtherDocument] stores a pointer (a DBRef) and can fetch the linked document on demand.

  • Link[Category] keeps the category in its own collection.
  • Use fetch_links=True on a query, or await doc.fetch_link(...), to resolve it.

Rule of thumb: embed owned, read-together data; link shared or independently-queried data.

from beanie import Document, Link


class Category(Document):
    name: str

    class Settings:
        name = "categories"


class Product(Document):
    name: str
    price: float
    category: Link[Category]

    class Settings:
        name = "products"

Modeling Choices: Embed vs Reference

The core design decision in document modeling is whether to embed data or reference it. There is no universal answer — it depends on access patterns and data size.

  • Embed when: the child is owned by the parent, always loaded together, and bounded in size (address, order items).
  • Reference when: the data is shared across documents, queried on its own, or can grow without limit (categories, authors, audit logs).

MongoDB documents have a 16 MB cap, so unbounded embedded arrays eventually break — another reason to reference large, growing collections.

Quick Check: Embed or Reference?

You are modeling an e-commerce backend with Beanie. A Product belongs to exactly one Category, categories are shared across thousands of products, and you frequently list all categories on their own admin page.

Recap: Document Modeling with Beanie

You learned how to model MongoDB data with Beanie on top of Pydantic:

  • Document subclasses map a Python class to a collection; class Settings sets the collection name and options.
  • init_beanie must run at startup (in FastAPI's lifespan) with your motor database and document_models.
  • Pydantic Field constraints and validators keep bad data out of the database.
  • Index with the Indexed wrapper for single fields or Settings.indexes + IndexModel for compound and unique indexes.
  • Embed owned, bounded, read-together data as nested BaseModels; reference shared or growing data with Link[...], mindful of the 16 MB document cap.

With these tools you can design typed, validated, query-efficient MongoDB schemas for your FastAPI backend.

자주 묻는 질문

“Beanie ODM을 활용한 문서 모델링” 강의는 무료인가요?

네 — “Beanie ODM을 활용한 문서 모델링” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 FastAPI Backend Development Bootcamp 강의 전체를 잠금 해제할 수 있습니다. FastAPI Backend Development Bootcamp 강의에는 총 4개의 강의가 포함되어 있습니다.

“Beanie ODM을 활용한 문서 모델링”에서 뭘 배우나요?

Pydantic 위에서 Beanie를 사용해 타입이 지정된 문서 모델, 인덱스 및 내장 구조를 정의합니다. 브라우저에서 직접 실행하는 실습 코드로 FastAPI Backend Development Bootcamp을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.

FastAPI Backend Development Bootcamp을(를) 시작하는 데 경험이 필요한가요?

사전 경험은 필요하지 않습니다. CoddyKit의 FastAPI Backend Development Bootcamp은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 2번째 강의입니다.

“Beanie ODM을 활용한 문서 모델링” 강의는 얼마나 걸리나요?

대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.

이 FastAPI Backend Development Bootcamp 강의에서 코드를 작성하고 실행할 수 있나요?

네. 모든 FastAPI Backend Development Bootcamp 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. Motor를 활용한 비동기 MongoDB 접근
  2. Beanie ODM을 활용한 문서 모델링
  3. 집계 파이프라인과 복잡한 쿼리
  4. 스키마 진화와 문서 마이그레이션
← FastAPI Backend Development Bootcamp(으)로 돌아가기