LightODM vs Beanie
This page compares LightODM with Beanie, a popular MongoDB ODM for Python.
Quick Comparison
Feature |
LightODM |
Beanie |
|---|---|---|
Code Size |
~500 lines |
~5000+ lines |
Pydantic |
v2 native |
v1 compatibility layer |
Sync Support |
✅ Yes (PyMongo) |
❌ No (async only) |
Async Support |
✅ Yes (Motor) |
✅ Yes (Motor) |
Dependencies |
3 (pydantic, pymongo, motor) |
Many (including extra features) |
Connection Pattern |
BYOC (Bring Your Own Connection) |
Opinionated initialization |
Direct DB Access |
✅ Full PyMongo/Motor access |
Abstracted through Beanie API |
Learning Curve |
Minimal (if you know PyMongo) |
Moderate (learn Beanie abstractions) |
Query Builder |
Use MongoDB query syntax directly |
Beanie’s fluent query API |
Migrations |
DIY with PyMongo |
Built-in migration tools |
Validation |
Pydantic v2 |
Pydantic v1/v2 |
Relationships |
Manual (via refs) |
Built-in Link/BackLink |
Caching |
DIY |
Optional built-in caching |
Best For |
Simple projects, sync+async, full control |
Complex projects, async-only, feature-rich |
Philosophy
- LightODM: Minimalistic wrapper
LightODM is a thin layer over PyMongo/Motor that adds Pydantic validation and convenience methods. You still write MongoDB queries directly.
- Beanie: Full-featured ODM
Beanie is a comprehensive ODM with its own query language, migrations, relationships, and many additional features.
When to Choose LightODM
Choose LightODM if you:
Need both sync and async
LightODM supports both paradigms with the same model:
# Sync user.save() users = User.find({}) # Async await user.asave() users = await User.afind({})
Beanie is async-only.
Prefer MongoDB’s native query syntax
LightODM uses MongoDB queries directly:
# LightODM - standard MongoDB syntax User.find({"age": {"$gte": 18}, "city": "NYC"})
vs Beanie’s query builder:
# Beanie - custom query API await User.find(User.age >= 18, User.city == "NYC").to_list()
Want minimal dependencies
LightODM has only 3 dependencies. Beanie has more for its extra features.
Need direct PyMongo/Motor access
With LightODM, you get the actual collection object:
collection = User.get_collection() # PyMongo Collection collection.create_index([("email", 1)], unique=True)
Prefer simplicity over features
LightODM’s entire codebase is ~500 lines. Easy to understand and debug.
When to Choose Beanie
Choose Beanie if you:
Only use async/await
If your app is fully async and you don’t need sync support, Beanie is a great choice.
Want built-in relationships
Beanie has
LinkandBackLinkfor document relationships:class User(Document): name: str class Post(Document): title: str author: Link[User] # Automatic relationship handling
LightODM requires manual reference management.
Need migration tools
Beanie has built-in migration support for schema changes.
Prefer fluent query API
Some developers prefer Beanie’s Pythonic query syntax over raw MongoDB queries.
Want additional features
Beanie includes caching, event hooks, custom types, and more out of the box.
Code Comparison
Basic Model Definition
LightODM:
from lightodm import MongoBaseModel
from typing import Optional
class User(MongoBaseModel):
class Settings:
name = "users"
name: str
email: str
age: Optional[int] = None
Beanie:
from beanie import Document
from typing import Optional
class User(Document):
name: str
email: str
age: Optional[int] = None
class Settings:
name = "users"
Very similar! The main difference is the base class.
Initialization
LightODM - Simple connection:
from lightodm import connect
connect(
url="mongodb://localhost:27017",
db_name="mydb"
)
LightODM - Custom connection (BYOC):
from pymongo import MongoClient
client = MongoClient("mongodb://localhost:27017")
db = client.mydb
class User(MongoBaseModel):
@classmethod
def get_collection(cls):
return db.users
Beanie - Requires initialization:
from beanie import init_beanie
from motor.motor_asyncio import AsyncIOMotorClient
client = AsyncIOMotorClient("mongodb://localhost:27017")
await init_beanie(
database=client.mydb,
document_models=[User, Post, Comment]
)
Beanie requires listing all models upfront.
CRUD Operations
LightODM:
# Create & Save (sync)
user = User(name="John", email="john@example.com")
user.save()
# or async
await user.asave()
# Find (sync)
users = User.find({"age": {"$gte": 18}})
# or async
users = await User.afind({"age": {"$gte": 18}})
# Update (sync)
User.update_one({"_id": user_id}, {"$set": {"age": 31}})
# or async
await User.aupdate_one({"_id": user_id}, {"$set": {"age": 31}})
Beanie (async only):
# Create & Save
user = User(name="John", email="john@example.com")
await user.insert()
# Find
users = await User.find(User.age >= 18).to_list()
# Update
await user.set({User.age: 31})
Complex Queries
LightODM - Use MongoDB aggregation directly:
pipeline = [
{"$match": {"age": {"$gte": 18}}},
{"$group": {
"_id": "$city",
"count": {"$sum": 1},
"avg_age": {"$avg": "$age"}
}}
]
results = User.aggregate(pipeline)
# or
results = await User.aaggregate(pipeline)
Beanie - Use aggregation API:
results = await User.find(User.age >= 18).aggregate([
{"$group": {
"_id": "$city",
"count": {"$sum": 1},
"avg_age": {"$avg": "$age"}
}}
]).to_list()
Both support MongoDB’s powerful aggregation framework.
Relationships
LightODM - Manual references:
class Post(MongoBaseModel):
class Settings:
name = "posts"
title: str
author_id: str # Manual reference
def get_author(self):
return User.get(self.author_id)
async def aget_author(self):
return await User.aget(self.author_id)
Beanie - Built-in Links:
from beanie import Document, Link
class Post(Document):
title: str
author: Link[User] # Automatic reference
# Fetch with relationship
post = await Post.find_one(Post.title == "Hello").fetch_links()
print(post.author.name) # Automatically loaded
Beanie’s Link is more convenient but adds abstraction.
Migrations
LightODM:
No built-in migration support. Use PyMongo directly:
from lightodm import get_db
db = get_db()
# Add field to all users
db.users.update_many(
{"status": {"$exists": False}},
{"$set": {"status": "active"}}
)
Beanie:
Built-in migration framework:
from beanie import Document, iterative_migration
@iterative_migration()
class User(Document):
name: str
status: str = "active" # New field
class Settings:
name = "users"
Beanie handles migrations automatically.
Migration from Beanie
Migrating from Beanie to LightODM is straightforward:
Change base class:
# Before (Beanie) from beanie import Document class User(Document): ... # After (LightODM) from lightodm import MongoBaseModel class User(MongoBaseModel): ...
Update connection initialization:
# Before (Beanie) await init_beanie(database=db, document_models=[User]) # After (LightODM) from lightodm import connect connect(db_name="mydb")
Convert async-only methods to sync or async:
# Before (Beanie - async only) await user.insert() users = await User.find_all().to_list() # After (LightODM - choose sync or async) user.save() # sync await user.asave() # async users = User.find({}) # sync users = await User.afind({}) # async
Replace query builder with MongoDB queries:
# Before (Beanie) users = await User.find( User.age >= 18, User.city == "NYC" ).to_list() # After (LightODM) users = User.find({"age": {"$gte": 18}, "city": "NYC"}) # or async users = await User.afind({"age": {"$gte": 18}, "city": "NYC"})
Handle relationships manually:
# Before (Beanie) class Post(Document): author: Link[User] post = await Post.find_one().fetch_links() print(post.author.name) # After (LightODM) class Post(MongoBaseModel): author_id: str async def get_author(self): return await User.aget(self.author_id) post = await Post.afind_one({}) author = await post.get_author() print(author.name)
For a complete migration guide, see the MIGRATION_FROM_BEANIE.md file in the repository.
Performance
LightODM is slightly faster for simple operations due to less abstraction overhead.
Beanie may be faster for complex relationship queries with fetch_links() optimization.
For most applications, the performance difference is negligible. Choose based on features and developer experience.
Conclusion
- Choose LightODM for:
Sync + async support
Minimal dependencies
Direct MongoDB control
Simple projects
Learning MongoDB
- Choose Beanie for:
Async-only applications
Built-in relationships
Migration tools
Feature-rich ODM
Complex domain models
Both are excellent tools. LightODM prioritizes simplicity and control, while Beanie provides a comprehensive feature set.