A well - structured project makes it easier for developers to understand the codebase, collaborate effectively, and add new features or fix bugs without introducing regressions. It also improves the overall performance and security of the application.
@app.get
, @app.post
, etc., to handle different HTTP requests.A common project structure for a FastAPI project is as follows:
project/
│
├── app/
│ ├── __init__.py
│ ├── main.py
│ ├── routes/
│ │ ├── __init__.py
│ │ ├── users.py
│ │ └── items.py
│ ├── models/
│ │ ├── __init__.py
│ │ ├── users.py
│ │ └── items.py
│ └── dependencies/
│ ├── __init__.py
│ └── auth.py
├── tests/
│ ├── __init__.py
│ └── test_users.py
├── requirements.txt
└── .env
Here is a simple example of defining a route in FastAPI:
# app/routes/users.py
from fastapi import APIRouter
router = APIRouter()
@router.get("/users/")
async def read_users():
return {"message": "This is a list of users"}
And integrating it into the main application:
# app/main.py
from fastapi import FastAPI
from app.routes.users import router as users_router
app = FastAPI()
app.include_router(users_router)
@app.get("/")
async def root():
return {"message": "Hello World"}
Dependencies are used to share common functionality across routes. For example, an authentication dependency:
# app/dependencies/auth.py
from fastapi import Depends, HTTPException
from fastapi.security import APIKeyHeader
api_key_header = APIKeyHeader(name="X - API - Key")
async def get_api_key(api_key: str = Depends(api_key_header)):
if api_key != "secret_api_key":
raise HTTPException(status_code=401, detail="Invalid API Key")
return api_key
Using the dependency in a route:
# app/routes/items.py
from fastapi import APIRouter, Depends
from app.dependencies.auth import get_api_key
router = APIRouter()
@router.get("/items/", dependencies=[Depends(get_api_key)])
async def read_items():
return {"message": "This is a list of items"}
FastAPI provides built - in support for error handling. You can raise HTTPException
to return custom error responses.
from fastapi import FastAPI, HTTPException
app = FastAPI()
@app.get("/items/{item_id}")
async def read_item(item_id: int):
if item_id < 1:
raise HTTPException(status_code=404, detail="Item not found")
return {"item_id": item_id}
Logging is essential for debugging and monitoring. You can use the Python standard logging
module.
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
@app.get("/")
async def root():
logger.info("Root endpoint accessed")
return {"message": "Hello World"}
You can use the TestClient
from fastapi.testclient
to write unit and integration tests.
from fastapi.testclient import TestClient
from app.main import app
client = TestClient(app)
def test_read_users():
response = client.get("/users/")
assert response.status_code == 200
assert response.json() == {"message": "This is a list of users"}
For database integration, you can use SQLAlchemy with FastAPI. Here is a simple example:
# app/models/items.py
from sqlalchemy import Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class Item(Base):
__tablename__ = "items"
id = Column(Integer, primary_key=True, index=True)
name = Column(String)
# app/dependencies/database.py
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db"
engine = create_engine(SQLALCHEMY_DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
# app/routes/items.py
from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
from app.models.items import Item
from app.dependencies.database import get_db
router = APIRouter()
@router.get("/items/")
async def read_items(db: Session = Depends(get_db)):
items = db.query(Item).all()
return items
Use OAuth2 with JWT (JSON Web Tokens) for authentication and authorization. FastAPI has built - in support for OAuth2.
# app/dependencies/auth.py
from fastapi import Depends, HTTPException
from fastapi.security import OAuth2PasswordBearer
from jose import JWTError, jwt
from datetime import datetime, timedelta
from typing import Optional
SECRET_KEY = "your - secret - key"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=15)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
async def get_current_user(token: str = Depends(oauth2_scheme)):
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
if username is None:
raise credentials_exception
except JWTError:
raise credentials_exception
return username
Keep your code modular by separating concerns into different files and directories. Use functions and classes to encapsulate functionality and make the code more reusable.
Structuring a FastAPI project properly is essential for its long - term success. By following the best practices outlined in this blog, including proper project directory setup, handling dependencies, error handling, logging, testing, database integration, authentication, and code modularity, you can build robust, scalable, and maintainable FastAPI applications.