使用 Beanie ODM 建模文档
基于 Pydantic 使用 Beanie 定义类型化文档模型、索引和嵌入式结构。
使用 Beanie ODM 建模文档 是 CoddyKit 上的免费 FastAPI Backend Development Bootcamp 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 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.
AsyncIOMotorClientcreates the async connection.document_modelsregisters 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=TrueorexpireAfterSeconds=...through theIndexModel.
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=Trueon a query, orawait 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 Settingssets the collection name and options. init_beaniemust run at startup (in FastAPI's lifespan) with your motor database anddocument_models.- Pydantic
Fieldconstraints and validators keep bad data out of the database. - Index with the
Indexedwrapper for single fields orSettings.indexes+IndexModelfor compound and unique indexes. - Embed owned, bounded, read-together data as nested
BaseModels; reference shared or growing data withLink[...], 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 建模文档」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 FastAPI Backend Development Bootcamp 课程的其余内容,请升级到 CoddyKit PRO。 FastAPI Backend Development Bootcamp 课程共包含 4 节课。
「使用 Beanie ODM 建模文档」这节课中我会学到什么?
基于 Pydantic 使用 Beanie 定义类型化文档模型、索引和嵌入式结构。 你通过在浏览器中直接运行的动手代码来练习 FastAPI Backend Development Bootcamp,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 FastAPI Backend Development Bootcamp 需要有经验吗?
无需任何先前经验。CoddyKit 上的 FastAPI Backend Development Bootcamp 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。
「使用 Beanie ODM 建模文档」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 FastAPI Backend Development Bootcamp 课中编写并运行代码吗?
能。每节 FastAPI Backend Development Bootcamp 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- 使用 Motor 异步访问 MongoDB
- 使用 Beanie ODM 建模文档
- 聚合流水线与复杂查询
- 模式演进与文档迁移