Hi! If you're working with Python backend frameworks like FastAPI, Django, Flask, or Sanic, you’re in the right place.
In this article, I’ll share some of the best practices and coding patterns I’ve learned over the years.
These aren’t just conventional "best practices"—they’re also my personal preferences that help make projects more maintainable, testable, and scalable.
Project Structure
Always use a well-structured layout for your backend service. Whether it’s FastAPI, Flask, or Django, having a modular structure goes a long way.
project-name/
├── app/
│ ├── api/
│ ├── services/
│ ├── models/
│ ├── db/
│ └── utils/
├── tests/
├── Dockerfile
├── requirements.txt
└── README.md
Organize your logic by domains instead of types (i.e., group by feature not function).
Dependency Injection (FastAPI)
Use FastAPI’s Depends mechanism to keep your code modular and testable.
from fastapi import Depends
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.get("/users")
def list_users(db: Session = Depends(get_db)):
return db.query(User).all()
Environment Configuration
Never hardcode secrets. Use environment variables and .env
files with something like python-dotenv
.
from dotenv import load_dotenv
import os
load_dotenv()
DATABASE_URL = os.getenv("DATABASE_URL")
Use pydantic.BaseSettings
for better structure in FastAPI:
from pydantic import BaseSettings
class Settings(BaseSettings):
database_url: str
class Config:
env_file = ".env"
settings = Settings()
Logging
Always use Python’s built-in logging
module, and configure log levels properly.
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
logger.info("App started")
Avoid using print()
in production code.
Error Handling
Use custom exception handlers to manage errors cleanly, especially in FastAPI:
from fastapi.responses import JSONResponse
from fastapi import Request
@app.exception_handler(Exception)
async def generic_exception_handler(request: Request, exc: Exception):
return JSONResponse(status_code=500, content={"detail": str(exc)})
SQLAlchemy ORM
Always use scoped_session
and sessionmaker
for DB access. Abstract queries inside repositories
or services
folders.
Background Tasks
Use Celery for background jobs:
from celery import Celery
celery_app = Celery(__name__, broker="redis://localhost:6379/0")
@celery_app.task
def send_email_task(email_data):
send_email(email_data)
Testing
Use pytest
and follow test-driven development (TDD) where possible. Separate unit and integration tests.
# test_main.py
from fastapi.testclient import TestClient
from app.main import app
client = TestClient(app)
def test_read_main():
response = client.get("/")
assert response.status_code == 200
Use factories or fixtures to mock data and avoid flaky tests.
API Versioning
Structure your API endpoints with versioning to support future updates:
/api/v1/users
/api/v2/users
Use Type Hints
Always use type hints for function arguments and return types. Improves readability and helps IDEs catch bugs early.
def get_user(user_id: int) -> Optional[User]:
...
Bonus: Auto Docs in FastAPI
Use docstrings and response models to auto-generate OpenAPI docs.
@app.get("/users", response_model=List[UserSchema])
def get_users():
"""Retrieve all users"""
...
You get beautiful Swagger UI out of the box!