FastAPI Project Structure Best Practices: A Scalable Directory Layout Guide
Organize FastAPI projects into an app/ package with sub-modules for routers, schemas, models, CRUD, core configuration, and dependencies, using an application factory pattern in main.py to prevent circular imports and enable testability.
FastAPI's minimalist design allows you to start with a single file, but production services require a clean architecture. The tiangolo/fastapi repository demonstrates a proven project structure that separates concerns across dedicated modules, making large codebases maintainable and testable.
Why FastAPI Project Structure Matters
FastAPI intentionally imposes no folder conventions, as seen in fastapi/applications.py where the FastAPI class is implemented without filesystem assumptions. However, real-world applications quickly outgrow single-file scripts. A structured layout prevents circular imports, enables unit testing with TestClient, and aligns with Docker layer caching strategies documented in the official deployment guides.
Recommended FastAPI Directory Layout
The following structure reflects the "Bigger Applications" tutorial pattern from docs/en/docs/tutorial/bigger-applications.md. Each directory has a single responsibility:
.
├── app
│ ├── __init__.py # optional: expose `app = FastAPI()`
│ ├── main.py # creates the FastAPI instance & includes routers
│ ├── core
│ │ ├── config.py # Pydantic Settings
│ │ ├── security.py # OAuth2, JWT helpers
│ │ └── logger.py
│ ├── crud
│ │ ├── user.py
│ │ └── item.py
│ ├── db
│ │ ├── base.py # SQLAlchemy Base
│ │ └── session.py # SessionLocal, engine
│ ├── models
│ │ ├── user.py
│ │ └── item.py
│ ├── schemas
│ │ ├── user.py
│ │ └── item.py
│ ├── routers
│ │ ├── __init__.py
│ │ ├── users.py
│ │ └── items.py
│ └── deps
│ └── dependencies.py
├── tests
│ ├── conftest.py # pytest fixtures (client, db)
│ ├── test_users.py
│ └── test_items.py
├── Dockerfile
├── requirements.txt
└── README.md
Implementing the Core Components
Application Factory Pattern in main.py
Centralize the FastAPI instance creation in app/main.py to prevent circular imports and enable testing. As implemented in fastapi/applications.py, the FastAPI class accepts configuration parameters like title and version.
# app/main.py
from fastapi import FastAPI
from app.routers import users, items
from app.core.config import settings
def get_application() -> FastAPI:
"""Factory that creates the FastAPI instance."""
app = FastAPI(
title=settings.PROJECT_NAME,
version=settings.VERSION,
openapi_url="/openapi.json",
)
# Register routers
app.include_router(users.router, prefix="/users", tags=["users"])
app.include_router(items.router, prefix="/items", tags=["items"])
return app
app = get_application()
This pattern appears in the "Bigger Applications" tutorial, which demonstrates that keeping the app instance in one file prevents import conflicts when routers need to import the application.
Domain-Driven Routers with APIRouter
Group routes by domain into separate modules under app/routers/, using APIRouter instances for each logical entity. According to docs/en/docs/tutorial/bigger-applications.md, this pattern mirrors Flask blueprints and allows you to mount entire domain routers with app.include_router().
# app/routers/users.py
from fastapi import APIRouter, Depends, HTTPException, status
from typing import List
from app import crud, schemas, deps
router = APIRouter()
@router.get("/", response_model=List[schemas.User])
def read_users(skip: int = 0, limit: int = 100,
db=Depends(deps.get_db)):
"""Return a list of users."""
return crud.user.get_multi(db, skip=skip, limit=limit)
@router.post("/", response_model=schemas.User, status_code=status.HTTP_201_CREATED)
def create_user(user_in: schemas.UserCreate, db=Depends(deps.get_db)):
"""Create a new user."""
return crud.user.create(db, obj_in=user_in)
Database Session Management with Dependencies
Encapsulate database sessions in app/deps/dependencies.py using generator functions that yield SQLAlchemy or SQLModel sessions. This approach, documented in the "Dependencies" section of the Bigger Applications tutorial, leverages FastAPI's dependency injection system to ensure sessions are properly closed after each request.
# app/deps/dependencies.py
from typing import Generator
from sqlalchemy.orm import Session
from app.db.session import SessionLocal
def get_db() -> Generator[Session, None, None]:
"""Yield a DB session and ensure it is closed after the request."""
db = SessionLocal()
try:
yield db
finally:
db.close()
Centralized Configuration in Core
Store environment variables and secrets in app/core/config.py using Pydantic Settings. This centralizes validation and type coercion for configuration values, preventing the leakage of sensitive data into business logic.
# app/core/config.py
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
PROJECT_NAME: str = "My FastAPI Project"
VERSION: str = "1.0.0"
DATABASE_URL: str = "postgresql://user:pass@localhost/db"
class Config:
env_file = ".env"
settings = Settings()
Testing Strategy
Place tests in a top-level tests/ directory and use fixtures to instantiate the application without side effects. FastAPI's own test suite in tests/test_cli.py demonstrates this pattern using TestClient.
# tests/conftest.py
import pytest
from fastapi.testclient import TestClient
from app.main import get_application
@pytest.fixture(scope="module")
def client() -> TestClient:
"""Provide a TestClient that uses the FastAPI app."""
app = get_application()
with TestClient(app) as c:
yield c
Using a factory function like get_application() allows you to create fresh app instances for each test module, preventing state leakage between tests.
Docker and Deployment
Optimize container builds by copying dependency files before application code. The FastAPI Docker guide at docs/en/docs/deployment/docker.md recommends this layer-caching strategy.
# Dockerfile
FROM python:3.12-slim
WORKDIR /code
# Install only requirements first to leverage Docker cache
COPY ./requirements.txt /code/
RUN pip install --no-cache-dir -r /code/requirements.txt
# Copy the application code
COPY ./app /code/app
COPY ./main.py /code/
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80"]
This ordering ensures that Docker caches the heavy pip install layer unless requirements.txt changes, significantly speeding up rebuilds during development.
Summary
- Separate concerns by organizing code into
routers/,schemas/,models/,crud/,core/, anddeps/sub-packages within anapp/directory. - Centralize the FastAPI instance using a factory function in
app/main.pyto prevent circular imports and enable testable application creation. - Group routes with APIRouter into domain-specific modules under
app/routers/, keeping endpoint definitions isolated and maintainable. - Isolate dependencies in
app/deps/dependencies.pyto manage database sessions and authentication logic via FastAPI's injection system. - Optimize Docker builds by copying
requirements.txtbefore application code to maximize layer caching.
Frequently Asked Questions
Should I use a single main.py file or the full package structure for my FastAPI project?
Start with a single file for prototypes, but migrate to the package structure once you exceed a few endpoints. The tiangolo/fastapi documentation demonstrates that the APIRouter pattern becomes essential when you need to split logic across multiple files, preventing circular import issues that commonly occur in flat structures.
How do I avoid circular imports when splitting my FastAPI app into multiple modules?
Use an application factory pattern by defining a get_application() function in app/main.py rather than creating the FastAPI instance at module import time. As implemented in fastapi/applications.py, delaying instantiation until the factory is called prevents import-time side effects and allows routers to import the app instance without circular dependencies.
Where should I store database connection logic in a FastAPI project?
Encapsulate database sessions in app/deps/dependencies.py using generator functions that yield SQLAlchemy or SQLModel sessions. This approach, documented in the "Bigger Applications" tutorial, leverages FastAPI's dependency injection system to ensure sessions are properly closed after each request while keeping database logic out of route handlers.
What is the best way to organize FastAPI routers for a large application?
Group routes by domain into separate modules under app/routers/, using APIRouter instances for each logical entity such as users or items. According to the official tutorial in docs/en/docs/tutorial/bigger-applications.md, this pattern mirrors Flask blueprints and allows you to mount entire domain routers with app.include_router() while maintaining strict isolation between business domains.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →