News & Updates

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

By Erica Hollis 5 min read 2606 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 News Correspondent covering technology, society, and the changing landscape of everyday life. Her work explores the connections between innovation and public interest, translating complex developments into accessible reporting while examining their opportunities, challenges, and lasting effects.


You Might Like