News & Updates

How to Organize a FastAPI Project: Structure Tips and Real‑World Examples

By Erica Hollis 9 min read 2079 views

How to Organize a FastAPI Project: Structure Tips and Real‑World Examples

Why a Thoughtful Layout Matters

FastAPI may feel lightweight, but a messy folder tree quickly turns a promising API into a debugging nightmare. A clear structure keeps routes readable, dependencies injectable, and tests isolated—especially when the codebase outgrows a single main.py.

Core Building Blocks

Before diving into directories, it helps to label the main components you’ll encounter in almost any FastAPI service:

  • Routers: groups of endpoints, usually per domain (e.g., users, items).
  • Schemas: Pydantic models that validate request and response bodies.
  • Services: business logic that lives outside the route handlers.
  • Dependencies: reusable functions for DB sessions, auth, etc.
  • Tests: unit and integration checks that mirror your package layout.

Suggested Directory Layout

Below is a flexible skeleton that scales from a hobby project to a production‑grade microservice:

my_fastapi_app/

├── app/

│ ├── __init__.py

│ ├── main.py

│ ├── api/

│ │ ├── __init__.py

│ │ ├── v1/

│ │ │ ├── __init__.py

│ │ │ ├── users.py

│ │ │ └── items.py

│ ├── core/

│ │ ├── __init__.py

│ │ ├── config.py

│ │ └── security.py

│ ├── crud/

│ │ ├── __init__.py

│ │ ├── user.py

│ │ └── item.py

│ ├── models/

│ │ ├── __init__.py

│ │ ├── user.py

│ │ └── item.py

│ ├── schemas/

│ │ ├── __init__.py

│ │ ├── user.py

│ │ └── item.py

│ └── db/

│ ├── __init__.py

│ └── session.py

├── tests/

│ ├── __init__.py

│ ├── conftest.py

│ ├── test_users.py

│ └── test_items.py

└── pyproject.toml

What Each Folder Does

  • app/api: versioned routers keep backward compatibility painless.
  • app/core: global settings, secret handling, and reusable utilities.
  • app/crud: thin wrappers around ORM queries; separates DB concerns from routes.
  • app/models: SQLAlchemy (or Tortoise) classes that map to tables.
  • app/schemas: Pydantic models that define what’s accepted and returned.
  • app/db: session factory and engine creation, often driven by core.config.

Putting It All Together: A Minimal Example

Imagine you need a simple endpoint to create a user. The files interact like this:

app/schemas/user.py

from pydantic import BaseModel, EmailStr

class UserCreate(BaseModel):

email: EmailStr

password: str

class UserRead(BaseModel):

id: int

email: EmailStr

class Config:

orm_mode = True

app/crud/user.py

from sqlalchemy.orm import Session

from ..models.user import User

from ..schemas.user import UserCreate

def create_user(db: Session, payload: UserCreate) -> User:

db_user = User(email=payload.email, hashed_password=hash(payload.password))

db.add(db_user)

db.commit()

db.refresh(db_user)

return db_user

app/api/v1/users.py

from fastapi import APIRouter, Depends, HTTPException, status

from sqlalchemy.orm import Session

from ...crud.user import create_user

from ...schemas.user import UserCreate, UserRead

from ...db.session import get_db

router = APIRouter(prefix="/users", tags=["users"])

@router.post("/", response_model=UserRead, status_code=status.HTTP_201_CREATED)

def register_user(payload: UserCreate, db: Session = Depends(get_db)):

try:

return create_user(db, payload)

except Exception as exc:

raise HTTPException(status_code=400, detail=str(exc))

app/main.py

from fastapi import FastAPI

from .api.v1 import users

app = FastAPI(title="My Awesome API")

app.include_router(users.router, prefix="/api/v1")

This tiny flow demonstrates the separation of concerns: validation lives in schemas, persistence in crud, and routing stays thin.

Testing Tips Aligned with the Structure

Because tests mirror the package layout, you can quickly locate the target of a failing case. A common pattern:

  • Use pytest.fixture in tests/conftest.py to spin up a temporary DB.
  • Import the FastAPI app from app.main and run requests with httpx.AsyncClient.
  • Validate both status codes and response schemas.

Scaling Beyond the Basics

When the service grows, consider these optional upgrades without breaking the core layout:

  • Background tasks: a tasks/ module that houses Celery or RQ workers.
  • Versioned documentation: separate OpenAPI specs per API version.
  • Plugin architecture: dynamically load routers from a plugins/ folder.

Final Thoughts on Maintaining Order

Good folder hygiene isn’t a one‑time chore; it’s a habit. Whenever you add a new domain, ask yourself:

  1. Does it belong in an existing versioned router or need its own?
  2. Should the data model sit alongside similar tables, or merit a dedicated subpackage?
  3. Is there a reusable dependency I can extract for future endpoints?

Answering these questions early keeps the codebase approachable, even as contributors come and go.

Structuring FastAPI Project Using 3-Tier Design Pattern | Medium ...
Build Fast, Scale Smart: The Ultimate FastAPI Project Structure Guide ...
Project Introduction and Advanced FastAPI Project Structure | Pro-Level ...
Build Fast, Scale Smart: The Ultimate FastAPI Project Structure Guide ...

Written by Erica Hollis

Erica Hollis is a Chief Correspondent with over a decade of experience covering breaking trends, in-depth analysis, and exclusive insights.