- Published on
How to Use FastAPI with Django ORM: Step-by-Step Tutorial (2025)
- Authors
- Name
- Sahil Fruitwala

FastAPI is one of the hottest frameworks for building fast, modern APIs in Python. It's async-ready, super quick, and comes with automatic docs that make life easier. On the flip side, Django has a fantastic ORM that handles database stuff like a champ, complete with migrations and a solid structure.
So what if we mashed them together? FastAPI on the front, Django ORM at the back — the best of both worlds! In this guide, we’ll walk through how to set it all up, step-by-step.
What We'll Cover
- Project layout
- Setting up Django inside FastAPI
- Creating a Django model
- Building API endpoints (sync and async)
- Running and testing it all
Let’s dive in!
Project Layout
Here’s a sneak peek at how the project is structured:
├── config
│ └── settings.py # Django settings
├── db_app
│ ├── __init__.py
│ ├── migrations # Django migrations
│ │ ├── __init__.py
│ │ └── ...
│ └── models.py # Our Django models
├── routers
│ └── users.py # FastAPI router for user endpoints
├── .env # For environment variables (optional but handy)
├── main.py # Where FastAPI starts
├── manage.py # Django utility script
├── pyproject.toml # Project dependencies (if using Poetry)
├── db.sqlite3 # SQLite database (default)
└── README.md
config/settings.py
: Contains the necessary Django settings, primarily for database configuration and defining installed apps.db_app/
: A Django app directory holding our database models (models.py
) and migrations.routers/
: Contains FastAPIAPIRouter
instances to organize endpoints.main.py
: Initializes the FastAPI application and includes the routers. It also handles the critical step of setting up Django.manage.py
: The standard Django utility script, essential for running database migrations.
Installing the Right Stuff
Here are the packages you’ll want to install:
pip install fastapi uvicorn django
pip install psycopg2-binary # If you're using PostgreSQL
pip install python-dotenv pydantic
pip install asgiref # For async ORM support
Step 1: Django Settings (config/settings.py)
In config/settings.py
, set up a minimal Django config. Here's a slimmed-down version:
import os
from pathlib import Path
from dotenv import load_dotenv
BASE_DIR = Path(__file__).resolve().parent.parent
load_dotenv(BASE_DIR / ".env")
SECRET_KEY = os.getenv('DJANGO_SECRET_KEY', 'super-insecure-default')
DEBUG = os.getenv('DEBUG', 'False') == 'True'
INSTALLED_APPS = [
'db_app',
]
DATABASES = {
'default': {
'ENGINE': os.getenv('DB_ENGINE', 'django.db.backends.sqlite3'),
'NAME': os.getenv('DB_NAME', BASE_DIR / 'db.sqlite3'),
'USER': os.getenv('DB_USER', ''),
'PASSWORD': os.getenv('DB_PASSWORD', ''),
'HOST': os.getenv('DB_HOST', ''),
'PORT': os.getenv('DB_PORT', ''),
'OPTIONS': {
'timeout': 20,
},
'TEST': {
'NAME': os.getenv('TEST_DB_NAME', BASE_DIR / 'test_db.sqlite3'),
},
}
}
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
USE_TZ = True
TIME_ZONE = 'UTC'
Key points:
- BASE_DIR: Correctly locates the project root.
- SECRET_KEY: Still required by Django.
- INSTALLED_APPS: Must include the Django app (db_app) where your models.py resides.
- DATABASES: Defines your database connection(s). Using environment variables is recommended for security and flexibility.
Step 2: FastAPI Meets Django (main.py)
Before touching any Django models or routers, you must initialize Django:
import os
import django
from fastapi import FastAPI
# This tells Django where to find the settings.py file.
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
# This loads the settings and configures Django apps.
# MUST be called before importing any Django models or ORM-dependent code.
django.setup()
# Import routers as needed
from routers.users import router as users_router
app = FastAPI(title="FastAPI + Django ORM")
@app.get("/")
async def root():
return {"message": "API is running!"}
app.include_router(users_router)
Critical Steps:
- os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings'): Tells Django where to find its settings file.
- django.setup(): Loads Django settings and configures apps. This must happen before any code attempts to import or use Django models or the ORM.
Step 3: Models (db_app/models.py)
Let’s define a super simple user model:
from django.db import models
class User(models.Model):
name = models.CharField(max_length=100)
email = models.EmailField(unique=True)
password = models.BinaryField() # For hashed passwords
created_at = models.DateTimeField(auto_now_add=True)
last_modified = models.DateTimeField(auto_now=True)
def __str__(self):
return self.email
Run these commands to get the database ready:
python manage.py makemigrations db_app
python manage.py migrate
Step 4: Database Migrations (manage.py)
Since we're using Django's ORM, we also use its migration system. And to make Django CLI commands like makemigrations work, add a minimal manage.py
.
Create Migrations: Whenever you change your models.py, run:
python manage.py makemigrations db_app
(Replace db_app with your app name if different). This creates migration files in db_app/migrations/.Apply Migrations: To apply the migrations to your database:
python manage.py migrate
You need the manage.py script for this. Ensure it correctly sets the DJANGO_SETTINGS_MODULE
import os
import sys
def main():
"""Run administrative tasks."""
# Point Django to your settings module
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') # Crucial line
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()
Step 5: User Endpoints (routers/users.py)
Here’s how we create sync and async endpoints for our User model using FastAPI and Pydantic.
Pydantic Models
from pydantic import BaseModel, Field, EmailStr
class UserBase(BaseModel):
name: str = Field(..., max_length=100)
email: EmailStr
class UserCreate(UserBase):
password: str = Field(..., min_length=8)
class UserPublic(UserBase):
id: int
class Config:
from_attributes = True
Sync Endpoints
from fastapi import APIRouter, HTTPException, status
from django.db import IntegrityError
from django.core.exceptions import ObjectDoesNotExist
from db_app.models import User as UserModel
router = APIRouter(prefix="/users", tags=["Users"])
@router.post("/sync/", response_model=UserPublic)
def create_user_sync(user: UserCreate):
try:
user_obj = UserModel.objects.create(**user.model_dump())
return user_obj
except IntegrityError:
raise HTTPException(status_code=400, detail="Email already exists")
@router.get("/sync/{user_id}", response_model=UserPublic)
def get_user_sync(user_id: int):
try:
user = UserModel.objects.get(pk=user_id)
return user
except ObjectDoesNotExist:
raise HTTPException(status_code=404, detail="User not found")
Async Endpoints
How do you use Asynchronous methods of FastAPI?
We don't need to do any major change in our django models or Pydantic schema. Only thing we need to change is make endpoint functions async
and convert django calles from sync to async using sync_to_async
method from asgiref.sync
.
from asgiref.sync import sync_to_async
@router.post("/async/", response_model=UserPublic)
async def create_user_async(user: UserCreate):
try:
user_obj = await sync_to_async(UserModel.objects.create)(**user.model_dump())
return user_obj
except IntegrityError:
raise HTTPException(status_code=400, detail="Email already exists")
@router.get("/async/{user_id}", response_model=UserPublic)
async def get_user_async(user_id: int):
try:
user = await sync_to_async(UserModel.objects.get)(pk=user_id)
return user
except ObjectDoesNotExist:
raise HTTPException(status_code=404, detail="User not found")
Sync vs. Async Choice:
- Use Synchronous (def) if:
- Your endpoint only interacts with the database and performs no other await operations.
- Simplicity is preferred. FastAPI's thread pool handles it efficiently for many use cases.
- Use Asynchronous (async def + sync_to_async) if:
- Your endpoint needs to await other asynchronous operations (e.g., external API calls, async file I/O) alongside the database call. Using sync_to_async prevents the synchronous ORM call from blocking the event loop while waiting for other async tasks.
- You are building a highly concurrent application where minimizing any potential blocking of the event loop is critical.
- You anticipate Django introducing more native async ORM features in the future and want to structure your code accordingly.
Important Note on sync_to_async: When wrapping Django ORM calls, you generally want to use the default thread_sensitive=True
setting in sync_to_async
. This ensures that the ORM operation runs in the same thread context, which is often required by Django's database connection handling.
Wrapping It Up
And that’s it! You now have a FastAPI project running with Django's rock-solid ORM. To recap:
- FastAPI handles the web and API side.
- Django ORM takes care of the database layer.
- You can write both sync and async endpoints depending on your needs.
If you want use whole setup my fastapi-with-django-orm template on GitHub. I have added multiple models, routes and also setup for securing the endpoint.
Give it a spin using Uvicorn:
uvicorn main:app --reload
Enjoy your new hybrid setup! 🚀