logo
Published on

How to Use FastAPI with Django ORM: Step-by-Step Tutorial (2025)

Authors
  • avatar
    Name
    Sahil Fruitwala
    Twitter
Integrate FastAPI with Django ORM: A Complete Step-by-Step Guide (2025)

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 FastAPI APIRouter 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.

  1. 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/.

  2. 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! 🚀