FastAPI combines the best of modern Python features with excellent performance and developer experience. In this guide, I'll walk you through building a complete REST API from scratch, including authentication, database integration, and deployment considerations. We'll also explore advanced features like dependency injection and background tasks.
Why FastAPI?
FastAPI has quickly become the preferred choice for building APIs in Python. Here's what makes it special:
- Performance: One of the fastest Python frameworks available
- Type Hints: Built-in support for Python type hints
- Automatic Documentation: Interactive API docs with OpenAPI
- Async Support: Native async/await support
- Easy to Learn: Intuitive and well-documented
Setting Up Your Project
Let's start by setting up a new FastAPI project with all the necessary dependencies.
# Install FastAPI and dependencies
pip install fastapi uvicorn sqlalchemy pydantic python-multipart
# Create project structure
mkdir fastapi-project
cd fastapi-project
mkdir app
touch app/__init__.py
touch app/main.py
Basic FastAPI Application
Here's a simple FastAPI application to get you started:
from fastapi import FastAPI
from pydantic import BaseModel
from typing import List, Optional
app = FastAPI(title="My API", version="1.0.0")
# Pydantic model for data validation
class Item(BaseModel):
id: int
name: str
description: Optional[str] = None
price: float
is_offer: bool = False
# Sample data
items = [
Item(id=1, name="Laptop", description="High-performance laptop", price=999.99),
Item(id=2, name="Mouse", description="Wireless mouse", price=29.99)
]
@app.get("/")
async def root():
return {"message": "Hello World"}
@app.get("/items/", response_model=List[Item])
async def get_items():
return items
@app.get("/items/{item_id}", response_model=Item)
async def get_item(item_id: int):
for item in items:
if item.id == item_id:
return item
return {"error": "Item not found"}
@app.post("/items/", response_model=Item)
async def create_item(item: Item):
items.append(item)
return item
Database Integration with SQLAlchemy
Most real-world applications need a database. Let's integrate SQLAlchemy with FastAPI:
from sqlalchemy import create_engine, Column, Integer, String, Float, Boolean
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
# Database setup
SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db"
engine = create_engine(SQLALCHEMY_DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
# Database model
class ItemDB(Base):
__tablename__ = "items"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, index=True)
description = Column(String)
price = Column(Float)
is_offer = Column(Boolean, default=False)
# Create tables
Base.metadata.create_all(bind=engine)
# Dependency to get database session
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
Authentication and Security
Security is crucial for any API. Here's how to implement JWT authentication:
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jose import JWTError, jwt
from passlib.context import CryptContext
from datetime import datetime, timedelta
# Security configuration
SECRET_KEY = "your-secret-key"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
def create_access_token(data: dict):
to_encode = data.copy()
expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
def verify_token(token: str = Depends(oauth2_scheme)):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
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
Advanced Features
FastAPI offers many advanced features that make development easier:
Dependency Injection
from fastapi import Depends
def get_current_user(token: str = Depends(oauth2_scheme)):
return verify_token(token)
@app.get("/users/me")
async def read_users_me(current_user: str = Depends(get_current_user)):
return {"username": current_user}
Background Tasks
from fastapi import BackgroundTasks
def send_email_notification(email: str, message: str):
# Simulate sending email
print(f"Sending email to {email}: {message}")
@app.post("/send-notification/")
async def send_notification(
background_tasks: BackgroundTasks,
email: str,
message: str
):
background_tasks.add_task(send_email_notification, email, message)
return {"message": "Notification sent in background"}
Testing Your API
Testing is essential for maintaining code quality. Here's how to test your FastAPI application:
from fastapi.testclient import TestClient
import pytest
client = TestClient(app)
def test_read_main():
response = client.get("/")
assert response.status_code == 200
assert response.json() == {"message": "Hello World"}
def test_create_item():
item_data = {
"id": 3,
"name": "Keyboard",
"description": "Mechanical keyboard",
"price": 89.99,
"is_offer": True
}
response = client.post("/items/", json=item_data)
assert response.status_code == 200
assert response.json()["name"] == "Keyboard"
Deployment Considerations
When deploying your FastAPI application to production, consider these important factors:
- ASGI Server: Use Uvicorn or Gunicorn with Uvicorn workers
- Environment Variables: Store sensitive configuration securely
- Database Migrations: Use Alembic for database schema changes
- Monitoring: Implement logging and health checks
- Load Balancing: Use multiple workers for high traffic
Performance Optimization
FastAPI is already fast, but here are some tips to optimize further:
- Use async functions for I/O operations
- Implement proper database indexing
- Use connection pooling for databases
- Cache frequently accessed data
- Monitor and profile your application
Best Practices
Here are some best practices I've learned from building FastAPI applications:
- Use Pydantic models for request/response validation
- Implement proper error handling and logging
- Write comprehensive tests
- Document your API endpoints
- Follow RESTful conventions
- Use environment variables for configuration
Conclusion
FastAPI has transformed how we build APIs in Python. Its combination of performance, developer experience, and modern Python features makes it an excellent choice for both small projects and large-scale applications.
The key to success is starting with a solid foundation and gradually adding complexity as needed. Focus on code quality, testing, and documentation from the beginning.