| name | fastapi-project-structure |
| description | Standard directory layout and module organization for FastAPI applications |
| license | Apache-2.0 |
FastAPI Project Structure
Standard Layout
project/
app/
__init__.py
main.py # FastAPI app instance and lifespan
config.py # Settings via pydantic-settings
api/
__init__.py
v1/
__init__.py
router.py # Aggregates all v1 routers
endpoints/
__init__.py
users.py
items.py
models/
__init__.py
user.py # SQLAlchemy / ORM models
item.py
schemas/
__init__.py
user.py # Pydantic request/response schemas
item.py
deps/
__init__.py
database.py # DB session dependency
auth.py # Auth dependencies
services/
__init__.py
user_service.py # Business logic
item_service.py
db/
__init__.py
session.py # Engine and session factory
base.py # Declarative base
tests/
__init__.py
conftest.py
test_users.py
test_items.py
alembic/ # Database migrations
versions/
env.py
alembic.ini
pyproject.toml
Key Conventions
app/main.py -- Application entry point
from contextlib import asynccontextmanager
from fastapi import FastAPI
from app.api.v1.router import api_router
from app.db.session import engine
@asynccontextmanager
async def lifespan(app: FastAPI):
yield
await engine.dispose()
app = FastAPI(title="My API", version="1.0.0", lifespan=lifespan)
app.include_router(api_router, prefix="/api/v1")
app/api/v1/router.py -- Aggregate routers
from fastapi import APIRouter
from app.api.v1.endpoints import users, items
api_router = APIRouter()
api_router.include_router(users.router, prefix="/users", tags=["users"])
api_router.include_router(items.router, prefix="/items", tags=["items"])
app/models/ vs app/schemas/
- models/: ORM models (SQLAlchemy). Represent database tables.
- schemas/: Pydantic models. Represent API request/response shapes.
Keep them separate. Never return an ORM model directly from an endpoint.
app/deps/ -- Dependency injection
Place reusable dependencies here. Common patterns:
from collections.abc import AsyncGenerator
from sqlalchemy.ext.asyncio import AsyncSession
from app.db.session import async_session_maker
async def get_db() -> AsyncGenerator[AsyncSession, None]:
async with async_session_maker() as session:
yield session
app/services/ -- Business logic
Endpoints should be thin. Move logic into service functions:
from sqlalchemy.ext.asyncio import AsyncSession
from app.models.user import User
async def get_user_by_id(db: AsyncSession, user_id: int) -> User | None:
return await db.get(User, user_id)
app/config.py -- Settings
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
database_url: str
secret_key: str
debug: bool = False
model_config = {"env_file": ".env"}
settings = Settings()
Rules
- One router file per resource (users.py, items.py, etc.).
- Never put business logic in endpoint functions. Use services.
- Always version your API (
/api/v1/, /api/v2/).
- Use
Depends() for all shared state (db sessions, auth, settings).
- Keep Pydantic schemas and ORM models in separate directories.