7. Production Ready¶
Deploy your API confidently with testing, security, and performance best practices.
Environment Variables¶
Never hardcode secrets. Use environment variables:
import os
from dotenv import load_dotenv
load_dotenv()
MONGO_URL = os.getenv("MONGO_URL", "mongodb://localhost:27017")
JWT_SECRET = os.getenv("JWT_SECRET", "dev-secret")
API_KEY = os.getenv("API_KEY", "dev-key")
DEBUG = os.getenv("DEBUG", "false").lower() == "true"
client = motor.motor_asyncio.AsyncIOMotorClient(MONGO_URL)
Create .env:
MONGO_URL=mongodb+srv://username:password@cluster.mongodb.net/music_app
JWT_SECRET=your-secret-key-here
API_KEY=api-key-here
DEBUG=false
Important: Add .env to .gitignore!
.env
.env.local
Testing¶
Unit Tests¶
Test your models:
import pytest
from my_app import UserModel, UserModelOut
@pytest.mark.asyncio
async def test_user_model():
user = UserModel(name="Alice", email="alice@example.com", password="secret")
assert user.name == "Alice"
assert user.id is None # Not saved yet
@pytest.mark.asyncio
async def test_user_model_out():
user_out = UserModelOut(id="507f", name="Alice", email="alice@example.com")
assert "password" not in user_out.__dict__ # Sensitive field not included
Integration Tests¶
Test actual API endpoints:
import pytest
from fastapi.testclient import TestClient
from my_app import app
client = TestClient(app)
@pytest.mark.asyncio
async def test_create_user():
response = client.post(
"/users",
json={"name": "Alice", "email": "alice@example.com", "password": "secret"}
)
assert response.status_code == 201
data = response.json()
assert data["name"] == "Alice"
assert "password" not in data # Output schema hides it
@pytest.mark.asyncio
async def test_get_user():
response = client.get("/users/507f1f77bcf86cd799439011")
assert response.status_code in [200, 400] # Either found or not found
@pytest.mark.asyncio
async def test_delete_user():
response = client.delete("/users/507f1f77bcf86cd799439011")
assert response.status_code == 204
Run Tests¶
pip install pytest pytest-asyncio
pytest tests/
Security Best Practices¶
1. Never Expose Sensitive Fields¶
Always use output schemas:
# ❌ BAD: Exposes password
class UserBadOut(MongoModel):
id: str
name: str
email: str
password: str # ← Exposed!
# ✅ GOOD: Hides password
class UserGoodOut(MongoModel):
id: str
name: str
email: str
# password not included
2. Hash Passwords¶
Store hashed passwords, never plain text:
from passlib.context import CryptContext
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
@app.post("/register")
async def register(user: UserModel):
user.password = pwd_context.hash(user.password) # Hash before saving
return await service.create_one(user)
@app.post("/login")
async def login(email: str, password: str):
user = await service.find_one({"email": email})
if not pwd_context.verify(password, user.password): # Verify hash
raise HTTPException(status_code=400, detail="Invalid credentials")
return {"access_token": create_token(user.id)}
3. Use HTTPS¶
Always use HTTPS in production:
# Redirect HTTP to HTTPS
@app.middleware("http")
async def redirect_https(request, call_next):
if request.url.scheme == "http" and not DEBUG:
url = request.url.replace(scheme="https")
return RedirectResponse(url=url, status_code=301)
return await call_next(request)
Or let your hosting provider handle it (recommended).
4. Implement Rate Limiting¶
Prevent abuse:
from slowapi import Limiter
from slowapi.util import get_remote_address
limiter = Limiter(key_func=get_remote_address)
app.state.limiter = limiter
@app.get("/users")
@limiter.limit("100/minute")
async def get_users(request: Request):
return await service.find_all()
Allows max 100 requests per minute per IP.
5. Add CORS for Frontend¶
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["https://example.com", "https://app.example.com"], # Specific domains
allow_credentials=True,
allow_methods=["GET", "POST", "PATCH", "DELETE"],
allow_headers=["*"],
)
6. Validate All Inputs¶
Use Pydantic models (already done!):
class UserModel(MongoModel):
name: str # Required
email: str # Type-checked
password: str # Must be string
# FastAPI automatically rejects invalid data:
# POST /users with missing name → 422 error
# POST /users with email="not-an-email" → 422 error
7. Add API Keys¶
For programmatic access:
from fastapi import Header, HTTPException
async def verify_api_key(x_api_key: str = Header(...)):
if x_api_key != os.getenv("API_KEY"):
raise HTTPException(status_code=403, detail="Invalid API key")
return x_api_key
@app.get("/admin/stats")
async def get_stats(api_key: str = Depends(verify_api_key)):
return {"users": await db["users"].count_documents({})}
Client must include header:
curl -H "X-API-Key: your-api-key" https://api.example.com/admin/stats
Deployment¶
Docker¶
Create Dockerfile:
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
Docker Compose¶
Create docker-compose.yml:
version: '3.8'
services:
api:
build: .
ports:
- "8000:8000"
environment:
- MONGO_URL=mongodb://mongo:27017
- DEBUG=false
depends_on:
- mongo
mongo:
image: mongo:7
ports:
- "27017:27017"
volumes:
- mongo_data:/data/db
volumes:
mongo_data:
Run:
docker-compose up
Cloud Deployment¶
Popular options:
- Railway (simple, MongoDB included)
- Render (free tier available)
- Fly.io (fast, affordable)
- AWS (most control, most complex)
All support FastAPI + MongoDB.
Monitoring¶
Health Checks¶
Add a health endpoint:
@app.get("/health")
async def health_check():
try:
await db.client.server_info() # Check MongoDB connection
return {"status": "healthy", "db": "connected"}
except Exception as e:
return {"status": "unhealthy", "error": str(e)}, 500
Monitoring services check this regularly:
curl https://api.example.com/health
Logging¶
Use Python logging:
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
@app.post("/users")
async def create_user(user: UserModel):
logger.info(f"Creating user: {user.email}")
created = await service.create_one(user)
logger.info(f"User created: {created.id}")
return created
Metrics¶
Export metrics for monitoring tools (Prometheus, DataDog, etc.):
from prometheus_client import Counter, Histogram
import time
request_count = Counter("http_requests_total", "Total requests")
request_duration = Histogram("http_request_duration_seconds", "Request duration")
@app.middleware("http")
async def track_metrics(request, call_next):
request_count.inc()
start = time.time()
response = await call_next(request)
duration = time.time() - start
request_duration.observe(duration)
return response
Performance Checklist¶
- ✅ Add MongoDB indexes for frequently queried fields
- ✅ Use pagination for large datasets
- ✅ Cache popular queries/responses
- ✅ Enable GZIP compression
- ✅ Use connection pooling for database
- ✅ Monitor response times
- ✅ Use CDN for static content
- ✅ Optimize database queries
Security Checklist¶
- ✅ Use output schemas (never expose sensitive data)
- ✅ Hash passwords
- ✅ Use HTTPS
- ✅ Validate all inputs
- ✅ Add rate limiting
- ✅ Add API authentication
- ✅ Use environment variables for secrets
- ✅ Add CORS restrictions
- ✅ Add request logging
- ✅ Keep dependencies updated
Maintenance¶
Update Dependencies¶
Regularly check for updates:
pip list --outdated
pip install --upgrade fastapi motor pydantic
Database Backups¶
For MongoDB Atlas (cloud): - Atlas handles automatic backups - Download backups from dashboard
For self-hosted MongoDB:
# Backup
mongodump --uri "mongodb://localhost:27017" --out backup/
# Restore
mongorestore --uri "mongodb://localhost:27017" backup/
Monitoring Errors¶
Use error tracking (Sentry, Rollbar):
import sentry_sdk
sentry_sdk.init(
dsn="https://key@sentry.io/project",
traces_sample_rate=1.0,
)
Now all errors are tracked automatically.
Conclusion¶
You now know how to:
✅ Protect sensitive data
✅ Secure your API
✅ Test thoroughly
✅ Deploy with confidence
✅ Monitor in production
Your API is production-ready!
Next Steps¶
Additional learning:
- Read FastAPI Security docs
- Read MongoDB Best Practices
-
Explore Motor async docs