When you first start with FastAPI, itβs tempting to throw everything into a single file β and it works fineβ¦ until your app grows. Then suddenly, your codebase becomes spaghetti.
In this post, weβll go through how to structure a FastAPI project properly, using clean architecture principles that scale easily.
π Why Project Structure Matters
A good structure makes your project:
- Easier to maintain and scale.
- Easier for new developers to understand.
- Ready for testing, CI/CD, and deployment.
If your FastAPI project looks like this:
main.py
models.py
routes.py
schemas.py
database.py
β¦itβs time for an upgrade.
ποΈ Recommended Folder Structure
Hereβs a clean, production-ready structure I use for real projects:
app/
βββ api/
β βββ routes/
β β βββ users.py
β β βββ items.py
β β βββ __init__.py
β βββ deps.py
β βββ __init__.py
β βββ api_v1.py
βββ core/
β βββ config.py
β βββ security.py
β βββ __init__.py
βββ db/
β βββ base.py
β βββ session.py
β βββ __init__.py
βββ models/
β βββ user.py
β βββ item.py
β βββ __init__.py
βββ schemas/
β βββ user.py
β βββ item.py
β βββ __init__.py
βββ services/
β βββ user_service.py
β βββ __init__.py
βββ main.py
βββ __init__.py
βοΈ Letβs Break It Down
core/
Contains global configurations β environment variables, security settings, and constants.
# app/core/config.py
from pydantic import BaseSettings
class Settings(BaseSettings):
APP_NAME: str = "My FastAPI App"
DATABASE_URL: str
class Config:
env_file = ".env"
settings = Settings()
db/
Handles all database setup β connection, session management, and base models.
# app/db/session.py
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from app.core.config import settings
engine = create_engine(settings.DATABASE_URL)
SessionLocal = sessionmaker(bind=engine, autocommit=False, autoflush=False)
api/routes/
Organize endpoints by domain β one file per feature.
# app/api/routes/users.py
from fastapi import APIRouter, Depends
from app.schemas.user import User
from app.services.user_service import get_all_users
router = APIRouter(prefix="/users", tags=["users"])
@router.get("/", response_model=list[User])
async def list_users():
return await get_all_users()
services/
Business logic lives here β keep it separate from routes.
# app/services/user_service.py
from app.db.session import SessionLocal
from app.models.user import User
async def get_all_users():
db = SessionLocal()
return db.query(User).all()
main.py
The entry point that ties everything together.
# app/main.py
from fastapi import FastAPI
from app.api.api_v1 import api_router
from app.core.config import settings
app = FastAPI(title=settings.APP_NAME)
app.include_router(api_router)
π§ Tips for Scalable FastAPI Projects
β
Use Pydantic models for input/output validation
β
Split logic into layers β routes, services, models
β
Use dependency injection for clean imports
β
Add logging early β donβt debug with print()
β
Separate environment variables from code
π§° Example Repository
Iβve uploaded an example of this structure here:
π GitHub - fastapi-clean-structure (insert your repo link)
You can clone it, install dependencies, and start your app in seconds:
git clone https://github.com/yourusername/fastapi-clean-structure.git
cd fastapi-clean-structure
poetry install
poetry run uvicorn app.main:app --reload
π Conclusion
Clean project structure isnβt just about being βorganizedβ β itβs about future-proofing your codebase.
FastAPI makes things fast, but structure makes them sustainable.
If you found this useful, drop a β€οΈ or follow me β I post about Python, FastAPI, and backend architecture regularly.
Top comments (0)