Database Integration
Connect and interact with databases efficiently using FastAPI to build data-driven applications.
Content
Using Tortoise ORM
Versions:
Watch & Learn
AI-discovered learning video
Sign in to watch the learning video for this topic.
Using Tortoise ORM with FastAPI — The Async Desert Rose You Didn't Know You Needed
"If SQLAlchemy is the Swiss Army knife of ORMs, Tortoise is the lightweight espresso shot for async Python apps." — Your slightly caffeinated TA
You're already familiar with setting up databases and getting cozy with SQLAlchemy earlier in this course. Now we take the async-first lane: welcome to Tortoise ORM, a Django-like ORM built specifically for async frameworks like FastAPI. This isn't repeating setup basics — it's about choosing a tool that's naturally fluent in async, and gluing it to FastAPI in a secure, maintainable way (remember our Security and Authentication lessons? We'll integrate that mindset here).
Why Tortoise? Quick elevator pitch
- Async-native: built for asyncio from the ground up — no threads pretending to be async.
- Django-ish models: you'll feel right at home if you've used Django ORM.
- Pydantic helpers: generates Pydantic models for fast endpoint wiring.
- Lightweight and easier to pick up for simple-to-medium projects compared to the sprawling SQLAlchemy ecosystem.
But yes, SQLAlchemy remains powerful and flexible; Tortoise is a pragmatic alternative when async simplicity and developer speed are the priority.
First things first: install
pip install tortoise-orm
pip install aiofiles # sometimes useful for async file ops in apps
pip install aerich # optional, for migrations
(We covered how to pick DB backends in "Setting Up a Database" — Tortoise supports sqlite, postgres, mysql, etc. Use the same credentials and connection strings.)
Core concepts — the cheat sheet
- Model classes inherit from
tortoise.models.Model. - Fields are
tortoise.fieldslikeIntField,CharField,ForeignKeyField. - Use
register_tortoiseto attach Tortoise to FastAPI lifecycle events (startup/shutdown). - Generate Pydantic schemas with
pydantic_model_creator. - Use Aerich for migrations.
Minimal FastAPI + Tortoise example (auth-ready)
This snippet shows how to register Tortoise, define a User model, make Pydantic schemas, and wire endpoints. It assumes you already learned how to secure endpoints; here we show a pattern to fetch the current user with a token and Tortoise queries.
from fastapi import FastAPI, Depends, HTTPException
from tortoise import fields
from tortoise.models import Model
from tortoise.contrib.fastapi import register_tortoise
from tortoise.contrib.pydantic import pydantic_model_creator
app = FastAPI()
class User(Model):
id = fields.IntField(pk=True)
username = fields.CharField(50, unique=True)
hashed_password = fields.CharField(128)
is_active = fields.BooleanField(default=True)
# Auto-generate Pydantic schemas
User_Pydantic = pydantic_model_creator(User, name='User')
UserIn_Pydantic = pydantic_model_creator(User, name='UserIn', exclude_readonly=True)
# DB registration (hook into FastAPI startup/shutdown)
register_tortoise(
app,
db_url='sqlite://db.sqlite3',
modules={'models': ['__main__']},
generate_schemas=True, # set False in prod and use migrations
add_exception_handlers=True,
)
# Dependency to get current user (after you implement JWT retrieval in auth lessons)
async def get_current_user(token: str = Depends(/* your jwt dependency */)):
user_id = /* decode token to id */
user = await User.get_or_none(id=user_id)
if not user or not user.is_active:
raise HTTPException(status_code=401, detail='Unauthorized')
return user
@app.post('/users', response_model=User_Pydantic)
async def create_user(user_in: UserIn_Pydantic):
# do password hashing using passlib as in Security lessons
user_obj = await User.create(**user_in.dict())
return await User_Pydantic.from_tortoise_orm(user_obj)
@app.get('/me', response_model=User_Pydantic)
async def read_me(current_user=Depends(get_current_user)):
return await User_Pydantic.from_tortoise_orm(current_user)
Notes:
- We used
register_tortoiseto avoid manualstartup/shutdownwiring; it's a convenience that callsTortoise.initandTortoise.close_connectionsfor you. generate_schemas=Trueis OK for prototyping, but use Aerich for controlled migrations in production.
Transactions, relationships, and querying — the essentials
- Transactions: use
in_transactioncontext manager.
from tortoise.transactions import in_transaction
async with in_transaction() as conn:
await User.filter(...).using_db(conn).update(...)
# ensures atomicity
- Relationships behave like Django's. Use
ForeignKeyFieldandManyToManyField. - To avoid N+1 problems, use
prefetch_relatedandselect_related:
posts = await Author.filter(id=1).prefetch_related('posts')
- For efficiency, fetch Pydantic directly from querysets:
await User_Pydantic.from_queryset(User.filter(...))
Migrations with Aerich (recommended)
- pip install aerich
- Initialize:
aerich init -t yourmodule.settings.TORTOISE_CONFIG aerich init-dbfor first timeaerich migrateandaerich upgradethereafter
Aerich stores migration history and lets you evolve schemas without generate_schemas=True.
Quick comparison: Tortoise vs SQLAlchemy (from earlier module)
| Concern | Tortoise | SQLAlchemy |
|---|---|---|
| Async first | ✅ built-in | ✅ (async supported via async engines, more setup) |
| Learning curve | Low | Medium–High |
| Ecosystem | Smaller, focused | Huge, extensible |
| Migrations | Aerich | Alembic |
| Flexibility for complex queries | Good for most | Best for extreme edge cases |
So if you enjoyed SQLAlchemy's power but felt setup-heavy, Tortoise is your fast-lane alternative for async apps.
Security integration checklist (connects to our Authentication module)
- Hash passwords with a proven lib (passlib's bcrypt/argon2) — never store plaintext.
- Use JWT or OAuth for tokens; decode in a dependency and fetch user with Tortoise.
- Check
is_active, roles, or permissions in dependencies before returning data. - Avoid leaking DB internals in error messages; add exception handlers.
Common gotchas & tips
- Remember: everything is async. Use
awaitlike your life depends on it. - For prod with Postgres, prefer
asyncpgas the driver. generate_schemas=Trueis fine locally but don't rely on it in production.- If you need raw SQL, use
await Tortoise.get_connection('default').execute_query('...').
Final mic drop — TL;DR and next steps
- Tortoise ORM is a practical, async-first ORM that integrates cleanly with FastAPI.
- Use
register_tortoisefor lifecycle wiring,pydantic_model_creatorfor quick schemas, and Aerich for migrations. - Tie it into your auth system by decoding tokens -> fetching user via Tortoise -> applying permission checks.
Big idea: choose the tool that matches the concurrency model of your app. If your app is async, using an async-native ORM is not just convenient — it prevents subtle performance and correctness issues.
Actionable next steps:
- Convert a small sample router from SQLAlchemy to Tortoise and compare developer ergonomics.
- Add Aerich to your repo and practice a migration cycle.
- Implement a secure login flow using your Security module and test it end-to-end.
Go build something asynchronous and slightly delightful. Your users (and your server) will thank you.
Comments (0)
Please sign in to leave a comment.
No comments yet. Be the first to comment!