FastAPI Project Structure
A well-organized project structure is essential for building maintainable and scalable FastAPI applications. When your project is properly structured, it becomes easier to collaborate with other developers, test your code, and add new features. In this guide, we'll explore the recommended project structure for FastAPI applications and best practices for organizing your code.
Why Project Structure Matters
Before diving into specific structures, let's understand why having a good project structure is important:
- Maintainability: Makes it easier to find and update code
- Scalability: Allows your project to grow without becoming unmanageable
- Collaboration: Helps team members understand the codebase quickly
- Testing: Facilitates writing and running tests
- Documentation: Makes your code self-documenting
Basic FastAPI Project Structure
Let's start with a basic project structure for a small FastAPI application:
my_fastapi_app/
├── app/
│ ├── __init__.py
│ ├── main.py
│ ├── dependencies.py
│ ├── models/
│ │ └── __init__.py
│ ├── routers/
│ │ └── __init__.py
│ ├── schemas/
│ │ └── __init__.py
│ └── services/
│ └── __init__.py
├── tests/
│ ├── __init__.py
│ └── test_main.py
├── .env
├── .gitignore
├── requirements.txt
└── README.md
This basic structure separates your application code, tests, and configuration files. Let's examine each component:
The app
directory
This is where all your application code lives. The main.py
file serves as the entry point for your application.
# app/main.py
from fastapi import FastAPI
from app.routers import items, users
app = FastAPI(
title="My FastAPI App",
description="A simple FastAPI application",
version="0.1.0"
)
app.include_router(items.router)
app.include_router(users.router)
@app.get("/")
async def root():
return {"message": "Welcome to my FastAPI application"}
The dependencies.py
file
This file contains shared dependencies used across your application, such as database connections or authentication utilities.
# app/dependencies.py
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
async def get_current_user(token: str = Depends(oauth2_scheme)):
# Validate token and get user
# This is a simplified example
if token != "valid_token":
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid authentication credentials",
headers={"WWW-Authenticate": "Bearer"},
)
return {"username": "testuser"}
The models
directory
This contains your database models, typically using SQLAlchemy or other ORMs:
# app/models/user.py
from sqlalchemy import Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
email = Column(String, unique=True, index=True)
hashed_password = Column(String)
is_active = Column(Boolean, default=True)
The routers
directory
Routers help organize your API endpoints by feature or resource:
# app/routers/users.py
from fastapi import APIRouter, Depends, HTTPException
from app.dependencies import get_current_user
from app.schemas.user import User, UserCreate
router = APIRouter(
prefix="/users",
tags=["users"],
dependencies=[Depends(get_current_user)],
responses={404: {"description": "Not found"}},
)
@router.get("/", response_model=list[User])
async def read_users():
# Logic to fetch users from database
return [{"id": 1, "email": "[email protected]", "is_active": True}]
@router.post("/", response_model=User)
async def create_user(user: UserCreate):
# Logic to create a new user
return {"id": 2, "email": user.email, "is_active": True}
The schemas
directory
Schemas (using Pydantic) define the structure of request and response data:
# app/schemas/user.py
from pydantic import BaseModel, EmailStr
class UserBase(BaseModel):
email: EmailStr
class UserCreate(UserBase):
password: str
class User(UserBase):
id: int
is_active: bool
class Config:
orm_mode = True
The services
directory
Services contain business logic separated from your route handlers:
# app/services/user_service.py
from app.models.user import User
from app.schemas.user import UserCreate
from sqlalchemy.orm import Session
from passlib.context import CryptContext
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
def get_user(db: Session, user_id: int):
return db.query(User).filter(User.id == user_id).first()
def create_user(db: Session, user: UserCreate):
hashed_password = pwd_context.hash(user.password)
db_user = User(email=user.email, hashed_password=hashed_password)
db.add(db_user)
db.commit()
db.refresh(db_user)
return db_user
Advanced Project Structure
For larger applications, you might want a more comprehensive structure:
my_fastapi_app/
├── app/
│ ├── __init__.py
│ ├── main.py
│ ├── api/
│ │ ├── __init__.py
│ │ ├── dependencies.py
│ │ └── v1/
│ │ ├── __init__.py
│ │ ├── endpoints/
│ │ │ ├── __init__.py
│ │ │ ├── items.py
│ │ │ └── users.py
│ │ └── router.py
│ ├── core/
│ │ ├── __init__.py
│ │ ├── config.py
│ │ └── security.py
│ ├── db/
│ │ ├── __init__.py
│ │ ├── base.py
│ │ ├── session.py
│ │ └── init_db.py
│ ├── models/
│ │ ├── __init__.py
│ │ ├── item.py
│ │ └── user.py
│ ├── schemas/
│ │ ├── __init__.py
│ │ ├── item.py
│ │ └── user.py
│ └── services/
│ ├── __init__.py
│ ├── item_service.py
│ └── user_service.py
├── alembic/
│ ├── versions/
│ ├── env.py
│ ├── README
│ ├── script.py.mako
│ └── alembic.ini
├── tests/
│ ├── __init__.py
│ ├── conftest.py
│ ├── api/
│ │ ├── __init__.py
│ │ ├── test_items.py
│ │ └── test_users.py
│ └── services/
│ ├── __init__.py
│ ├── test_item_service.py
│ └── test_user_service.py
├── .env
├── .env.example
├── .gitignore
├── Dockerfile
├── docker-compose.yml
├── requirements.txt
├── requirements-dev.txt
└── README.md
This structure includes additional components:
API Versioning
Versioning your API helps maintain backward compatibility:
# app/api/v1/router.py
from fastapi import APIRouter
from app.api.v1.endpoints import items, users
router = APIRouter()
router.include_router(items.router)
router.include_router(users.router)
# app/main.py
from fastapi import FastAPI
from app.api.v1.router import router as api_v1_router
app = FastAPI(title="My Advanced FastAPI App")
app.include_router(api_v1_router, prefix="/api/v1")
Configuration Management
Centralize configuration settings:
# app/core/config.py
import secrets
from pydantic import BaseSettings, PostgresDsn, validator
from typing import Any, Dict, Optional
class Settings(BaseSettings):
PROJECT_NAME: str = "My FastAPI App"
API_V1_STR: str = "/api/v1"
SECRET_KEY: str = secrets.token_urlsafe(32)
ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 * 24 * 8 # 8 days
POSTGRES_SERVER: str
POSTGRES_USER: str
POSTGRES_PASSWORD: str
POSTGRES_DB: str
SQLALCHEMY_DATABASE_URI: Optional[PostgresDsn] = None
@validator("SQLALCHEMY_DATABASE_URI", pre=True)
def assemble_db_connection(cls, v: Optional[str], values: Dict[str, Any]) -> Any:
if isinstance(v, str):
return v
return PostgresDsn.build(
scheme="postgresql",
user=values.get("POSTGRES_USER"),
password=values.get("POSTGRES_PASSWORD"),
host=values.get("POSTGRES_SERVER"),
path=f"/{values.get('POSTGRES_DB') or ''}",
)
class Config:
env_file = ".env"
case_sensitive = True
settings = Settings()
Database Setup
Organize database-related code:
# app/db/session.py
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from app.core.config import settings
engine = create_engine(settings.SQLALCHEMY_DATABASE_URI)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
# Dependency for endpoints
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
Real-World Example
Let's look at a complete example of a FastAPI application that follows these best practices:
Main Application File
# app/main.py
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from app.api.v1.router import router as api_v1_router
from app.core.config import settings
from app.db.init_db import create_first_superuser
app = FastAPI(
title=settings.PROJECT_NAME,
openapi_url=f"{settings.API_V1_STR}/openapi.json",
)
# Set up CORS
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Include routers
app.include_router(api_v1_router, prefix=settings.API_V1_STR)
@app.get("/")
def root():
return {"message": "Welcome to the API"}
@app.on_event("startup")
async def startup_event():
await create_first_superuser()
Router Example
# app/api/v1/endpoints/users.py
from typing import Any, List
from fastapi import APIRouter, Body, Depends, HTTPException
from fastapi.encoders import jsonable_encoder
from sqlalchemy.orm import Session
from app.api.dependencies import get_current_active_superuser, get_current_active_user, get_db
from app.models.user import User
from app.schemas.user import User as UserSchema, UserCreate, UserUpdate
from app.services.user_service import create_user, get_users, update_user
router = APIRouter()
@router.get("/", response_model=List[UserSchema])
def read_users(
db: Session = Depends(get_db),
skip: int = 0,
limit: int = 100,
current_user: User = Depends(get_current_active_superuser),
) -> Any:
"""
Retrieve users.
"""
users = get_users(db, skip=skip, limit=limit)
return users
@router.post("/", response_model=UserSchema)
def create_new_user(
*,
db: Session = Depends(get_db),
user_in: UserCreate,
current_user: User = Depends(get_current_active_superuser),
) -> Any:
"""
Create new user.
"""
user = create_user(db, obj_in=user_in)
return user
@router.put("/me", response_model=UserSchema)
def update_user_me(
*,
db: Session = Depends(get_db),
user_update: UserUpdate = Body(...),
current_user: User = Depends(get_current_active_user),
) -> Any:
"""
Update own user.
"""
user = update_user(db, db_obj=current_user, obj_in=user_update)
return user
Best Practices for FastAPI Project Structure
Here are some best practices to follow:
-
Separation of concerns: Keep your code organized by functionality (models, schemas, routers, services)
-
Use dependency injection: FastAPI's dependency injection system helps manage dependencies and makes testing easier
-
API versioning: Version your APIs to maintain backward compatibility
-
Keep routes clean: Move business logic to service functions
-
Use environment variables: Store configuration in environment variables, not in code
-
Error handling: Implement consistent error handling across your application
-
Logging: Set up proper logging for debugging and monitoring
-
Testing: Organize tests to mirror your application structure
-
Documentation: Use FastAPI's built-in documentation features and add docstrings
-
Type annotations: Use Python's type hints throughout your code
Summary
A well-organized FastAPI project structure is essential for building maintainable and scalable applications. By separating your code into logical components like models, schemas, routers, and services, you can make your codebase easier to understand and extend.
The basic structure works well for small applications, while the advanced structure is suitable for larger, more complex projects. Regardless of the size, following best practices like separation of concerns, dependency injection, and proper configuration management will help you build robust FastAPI applications.
Additional Resources
- FastAPI Official Documentation
- FastAPI Project Generator
- SQLAlchemy Documentation
- Pydantic Documentation
Exercises
- Convert a simple Flask application to FastAPI using the basic project structure
- Implement API versioning in an existing FastAPI project
- Extract business logic from route handlers into service functions
- Add proper dependency injection for database connections
- Set up a testing environment with pytest for a FastAPI application
With these guidelines and examples, you should now be able to structure your FastAPI applications effectively, regardless of their size and complexity.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)