# FastAPI Project Structure Best Practices: A Scalable Directory Layout Guide

> Discover best practices for a scalable FastAPI project structure. Organize your code into an app package with sub-modules for routers, schemas, models and more using the application factory pattern.

- Repository: [Sebastián Ramírez/fastapi](https://github.com/tiangolo/fastapi)
- Tags: best-practices
- Published: 2026-02-16

---

**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`](https://github.com/tiangolo/fastapi/blob/main/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`](https://github.com/tiangolo/fastapi/blob/main/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`](https://github.com/tiangolo/fastapi/blob/main/docs/en/docs/tutorial/bigger-applications.md). Each directory has a single responsibility:

```text
.
├── 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`](https://github.com/tiangolo/fastapi/blob/main/app/main.py) to prevent circular imports and enable testing. As implemented in [`fastapi/applications.py`](https://github.com/tiangolo/fastapi/blob/main/fastapi/applications.py), the `FastAPI` class accepts configuration parameters like `title` and `version`.

```python

# 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`](https://github.com/tiangolo/fastapi/blob/main/docs/en/docs/tutorial/bigger-applications.md), this pattern mirrors Flask blueprints and allows you to mount entire domain routers with `app.include_router()`.

```python

# 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`](https://github.com/tiangolo/fastapi/blob/main/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.

```python

# 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`](https://github.com/tiangolo/fastapi/blob/main/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.

```python

# 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`](https://github.com/tiangolo/fastapi/blob/main/tests/test_cli.py) demonstrates this pattern using `TestClient`.

```python

# 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`](https://github.com/tiangolo/fastapi/blob/main/docs/en/docs/deployment/docker.md) recommends this layer-caching strategy.

```dockerfile

# 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`](https://github.com/tiangolo/fastapi/blob/main/requirements.txt) changes, significantly speeding up rebuilds during development.

## Summary

- **Separate concerns** by organizing code into `routers/`, `schemas/`, `models/`, `crud/`, `core/`, and `deps/` sub-packages within an `app/` directory.
- **Centralize the FastAPI instance** using a factory function in [`app/main.py`](https://github.com/tiangolo/fastapi/blob/main/app/main.py) to 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.py`](https://github.com/tiangolo/fastapi/blob/main/app/deps/dependencies.py) to manage database sessions and authentication logic via FastAPI's injection system.
- **Optimize Docker builds** by copying [`requirements.txt`](https://github.com/tiangolo/fastapi/blob/main/requirements.txt) before 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`](https://github.com/tiangolo/fastapi/blob/main/app/main.py) rather than creating the `FastAPI` instance at module import time. As implemented in [`fastapi/applications.py`](https://github.com/tiangolo/fastapi/blob/main/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`](https://github.com/tiangolo/fastapi/blob/main/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`](https://github.com/tiangolo/fastapi/blob/main/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.