Creating a Full Stack Application with Django, FastAPI, and Next.js
How to create a full stack app with Django, FastAPI and Next.js.
In this tutorial, we will create a full stack application using Django for the backend, FastAPI for the API, and Next.js for the frontend. This stack is battle-tested in production SaaS projects and gives you strong scaffolding, fast async APIs, and a modern frontend — all running in Docker containers you can ship to any cloud provider.
- Django: We use Django for its strong scaffolding abilities, primarily for its models, migrations, and admin capabilities. SQLite would work for prototypes, but PostgreSQL is the right default for production: it handles concurrent writes safely, supports row-level locking, and integrates cleanly with Django's ORM.
- FastAPI: We choose FastAPI because of its speed, OpenAPI support, built-in interactive docs, and Pydantic schemas and validation. FastAPI runs on Starlette and is fully ASGI-native, which makes it a natural fit alongside Django's ASGI adapter — both can share the same uvicorn worker without thread-pool workarounds.
- Next.js: We utilize Next.js for its flexibility in providing server-side rendering (SSR), static site generation (SSG), and incremental static regeneration (ISR) on a per-page basis.
The source code for this tutorial can be found at https://github.com/damianhodgkiss/next-django-fastapi-fullstack-tutorial/. However, I recommend following the full tutorial to gain a comprehensive understanding of each step.
We choose to use Docker containers for hosting portability. While we may miss out on some instant auto-deployment features like those offered by Vercel, this configuration is designed for flexibility and major cloud providers such as AWS, Azure, and GCP. Additionally, self-hosting on a VPS with Portainer would be fairly straightforward with this stack too.
Why This Stack? Key Architectural Tradeoffs
Before diving in, it's worth understanding why this combination makes sense and where the friction points are.
Django vs. FastAPI routing — Django handles the admin interface, ORM, auth, and migrations. FastAPI handles all external API routes. The key insight is that they share the same Python process and database connection, but are exposed on different paths via Nginx (/admin → Django, /api → FastAPI). You are not running two separate backends; you're mounting two ASGI apps in the same container. This means Django models are fully importable inside FastAPI route handlers — no duplication of database logic.
PostgreSQL vs. SQLite — Django ships with SQLite enabled by default, which is fine for development but breaks under concurrent writes (e.g., multiple API requests hitting the same table simultaneously). PostgreSQL handles connection pooling, row-level locking, and is required for production deployments of any SaaS with real users.
Async mismatch to watch for — Django's ORM is synchronous by default. If you call a Django ORM query directly inside a FastAPI async def route handler, you'll block the event loop. Two safe options: use sync_to_async from asgiref (bundled with Django), or keep FastAPI route handlers as plain def (FastAPI runs them in a thread pool automatically). Example:
CORS — When your Next.js frontend (port 3000) talks directly to FastAPI (port 8001) during development, you'll hit CORS errors. Add CORSMiddleware to your FastAPI app in asgi.py:
In production, Nginx handles routing so both services share port 80 and CORS isn't an issue — but you still want this in place for local development.
Prerequisites
Before we begin, ensure that you have the following installed:
- Python: If not already installed, download and install Python from the official website.
- django-admin: If not installed, run the following command to install it:
Note: Since this is a Docker application, we only need to install enough to set up Django and run create-next-app on the host machine.
Steps
1. Create Project Directory
Create a new directory for the project and navigate into it:
2. Set Up Django Backend
2.1 Start Django Project
Create a backend directory and start a new Django project:
2.2 Create Dockerfile
Create a Dockerfile in the backend directory with the following content:
2.3 Configure Python Packages
Edit the requirements.txt file in the backend directory to include the necessary packages:
Note on versions: Pin your dependencies as shown above.
fastapi==0.109.1anduvicorn==0.25.0are a stable pairing. If you upgrade FastAPI, check the FastAPI changelog for Pydantic v2 migration notes, as v2 introduced breaking schema changes.
2.4 Configure Database and Email Authentication
Edit the settings.py file in the backend/mysaas directory to configure the database and email authentication:
Why PostgreSQL over SQLite? Django defaults to SQLite for convenience, but SQLite uses file-level locking — two simultaneous writes will queue or fail under load. PostgreSQL uses row-level locking and a proper connection model, making it the right default for any project expecting real traffic. Using environment variables (via
os.getenv) means the samesettings.pyworks for local Docker, staging, and production without code changes.
2.5 Initialize PostgreSQL
Create a postgres directory and an init.sql file within it:
Add the following configuration for the PostgreSQL service in the docker-compose.yml file:
About
pgautoupgrade: This image automatically upgrades the PostgreSQL data directory when you pull a newer Postgres version, so you don't have to manuallypg_dumpand restore. It's useful for long-running projects that want to stay current without a migration ceremony. For teams preferring explicit control, pin to a specific Postgres version likepostgres:16.
2.6 Run Django
Start the Django application using Docker Compose:
Check if Django is running by opening http://localhost:8000 in your browser.
2.7 Configure Custom User Model with django-use-email-as-username
Before we configure the database and email authentication, let's set up a custom user model using django-use-email-as-username.
Do this before your first migration. Django's auth system bakes in the User model early — swapping it after running migrations requires resetting the database. Set this up now.
First, create a custom users app:
Now, edit the backend/mysaas/settings.py file to use the custom user model:
2.8 Configure Static Files
Now we need to configure static files to ensure the Django admin interface works correctly.
Edit backend/mysaas/settings.py to set the STATIC_ROOT:
Edit backend/mysaas/urls.py to serve static files during development:
Collect static files by running the following command:
This command will gather all static files from your apps and place them in the directory specified by STATIC_ROOT.
2.9 Run Migrations
Run the database migrations:
Congratulations! Django is now configured.
3. Set Up FastAPI
3.1 Edit ASGI Configuration
Edit the asgi.py file in the backend/mysaas directory. This is where Django and FastAPI are wired together — both run in the same uvicorn process, exposed on different mount points:
Why two separate callables?
applicationis the Django ASGI callable — it handles/admin,/static, and anything Django's URL router matches.fastappis the FastAPI instance, served separately on port 8001 (and proxied to/apiin production). Keeping them as distinct objects means you get FastAPI's/docsand/openapi.jsonwithout conflicts with Django's routing.
3.2 Update Docker Compose Configuration
Add the FastAPI service to the docker-compose.yml file:
3.3 Run FastAPI
Start the FastAPI service using Docker Compose:
Check if FastAPI is running by opening http://localhost:8001/docs in your browser. You should see the interactive Swagger UI with the /health endpoint listed.
4. Set Up Next.js Frontend
4.1 Create Next.js App
Create a new Next.js app using the following command:
4.2 Create Dockerfile
Create a Dockerfile in the frontend directory:
Add the frontend service configuration to the docker-compose.yml file:
4.3 Configure Next.js Output Mode
Change the output mode to standalone in the next.config.mjs file:
Why
standaloneoutput? Next.js's standalone mode copies only the files needed to run the app (no fullnode_modules) into.next/standalone. This dramatically reduces the Docker image size and is the recommended mode for containerised Next.js deployments. The Dockerfile above relies on this: it copies from.next/standalonerather than the full project directory.
4.4 Check Next.js
Verify that Next.js is running by opening http://localhost:3000 in your browser.
5. Configure Nginx
5.1 Create Nginx Configuration
Create an nginx directory and an nginx.conf file within it:
How Nginx ties the stack together: In production all three services share port 80. Nginx inspects the URL prefix and routes to the right upstream —
/adminand/staticgo to Django,/apiand/docsgo to FastAPI, and everything else hits Next.js. This means your frontend makes API calls to/api/v1/...on the same domain, which eliminates CORS entirely in production. The CORS middleware inasgi.pyis still useful for local development where you're hittinglocalhost:8001directly.
Common Issues and Fixes
| Symptom | Likely cause | Fix |
|---|---|---|
Event loop is closed error in FastAPI route | Calling Django ORM directly in async def | Wrap with sync_to_async or use plain def handler |
| Django admin CSS missing | collectstatic not run | Run docker compose exec admin python manage.py collectstatic |
CORS policy error in browser | Frontend calling API directly during dev | Add CORSMiddleware to fastapp in asgi.py |
django.db.utils.OperationalError on startup | Postgres not ready when Django starts | Add depends_on: postgres and a healthcheck to the Django service |
| Auth model swap error | Ran migrations before setting AUTH_USER_MODEL | Reset the database and run migrations fresh |
Next Steps
- Authentication: Add Next-Auth (Auth.js) or Clerk for user login.
- Observability: Add structured logging across all three services so you can trace a request end-to-end.
- Secrets management: Move from
.envfiles to AWS Secrets Manager or SSM Parameter Store for production deployments.
Damian Hodgkiss
Senior Staff Engineer at Sumo Group, leading development of AppSumo marketplace. Technical solopreneur with 25+ years of experience building SaaS products.