Step-by-step guide to building production REST APIs with FastAPI. Covers routing, validation, authentication, database integration, deployment with systemd, and rate limiting.
FastAPI has become the most popular Python framework for building REST APIs. Its combination of speed, automatic documentation, and Python type hints makes it the best choice for production APIs in 2026. This guide walks you through building a complete REST API from scratch — from your first endpoint to production deployment.
FastAPI is used by companies like Microsoft, Netflix, Uber, and thousands of startups. Here's why:
/docsasync/await for high-concurrency workloadsStart by creating a virtual environment and installing FastAPI:
# Create project directory
mkdir my-api && cd my-api
# Create virtual environment
python3 -m venv venv
source venv/bin/activate
# Install FastAPI and uvicorn (ASGI server)
pip install fastapi uvicorn[standard]
# Optional but recommended
pip install pydantic[email] python-multipart aiofiles
Create your main application file:
# main.py
from fastapi import FastAPI
app = FastAPI(
title="My API",
description="A production REST API built with FastAPI",
version="1.0.0"
)
@app.get("/")
async def root():
return {"message": "API is running", "version": "1.0.0"}
Run the development server:
uvicorn main:app --reload --port 8000
Visit http://localhost:8000/docs to see the auto-generated Swagger documentation.
FastAPI makes it intuitive to define endpoints with Python functions:
from fastapi import FastAPI, Query
from typing import Optional
app = FastAPI()
@app.get("/api/greet")
async def greet(
name: str = Query(..., min_length=1, max_length=50, description="Your name"),
language: Optional[str] = Query("en", description="Language code")
):
greetings = {
"en": f"Hello, {name}!",
"es": f"Hola, {name}!",
"fr": f"Bonjour, {name}!",
"de": f"Hallo, {name}!",
}
return {
"greeting": greetings.get(language, f"Hello, {name}!"),
"language": language
}
FastAPI automatically validates query parameters, generates docs, and returns proper error responses for invalid input.
For POST/PUT endpoints, use Pydantic models for request body validation:
from pydantic import BaseModel, EmailStr, Field
from typing import Optional, List
class CreateUser(BaseModel):
name: str = Field(..., min_length=2, max_length=100)
email: EmailStr
age: Optional[int] = Field(None, ge=0, le=150)
tags: List[str] = Field(default_factory=list, max_length=10)
class Config:
json_schema_extra = {
"example": {
"name": "Jane Developer",
"email": "jane@example.com",
"age": 28,
"tags": ["python", "fastapi"]
}
}
@app.post("/api/users", status_code=201)
async def create_user(user: CreateUser):
# Pydantic has already validated all fields
# Invalid email? 422 error with details
# Name too short? 422 error with details
return {"id": 1, **user.model_dump()}
Pydantic handles type coercion, validation, and serialization. If a request doesn't match the schema, FastAPI returns a detailed 422 error automatically.
Here's a complete CRUD (Create, Read, Update, Delete) API for a task manager:
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field
from typing import Optional
from datetime import datetime
app = FastAPI()
# In-memory storage (replace with database in production)
tasks = {}
next_id = 1
class TaskCreate(BaseModel):
title: str = Field(..., min_length=1, max_length=200)
description: Optional[str] = None
priority: int = Field(default=1, ge=1, le=5)
class TaskUpdate(BaseModel):
title: Optional[str] = Field(None, min_length=1, max_length=200)
description: Optional[str] = None
priority: Optional[int] = Field(None, ge=1, le=5)
completed: Optional[bool] = None
@app.post("/api/tasks", status_code=201)
async def create_task(task: TaskCreate):
global next_id
task_id = next_id
next_id += 1
tasks[task_id] = {
"id": task_id,
**task.model_dump(),
"completed": False,
"created_at": datetime.utcnow().isoformat()
}
return tasks[task_id]
@app.get("/api/tasks")
async def list_tasks(completed: Optional[bool] = None, limit: int = 50):
result = list(tasks.values())
if completed is not None:
result = [t for t in result if t["completed"] == completed]
return {"tasks": result[:limit], "total": len(result)}
@app.get("/api/tasks/{task_id}")
async def get_task(task_id: int):
if task_id not in tasks:
raise HTTPException(404, f"Task {task_id} not found")
return tasks[task_id]
@app.patch("/api/tasks/{task_id}")
async def update_task(task_id: int, updates: TaskUpdate):
if task_id not in tasks:
raise HTTPException(404, f"Task {task_id} not found")
for key, value in updates.model_dump(exclude_unset=True).items():
tasks[task_id][key] = value
return tasks[task_id]
@app.delete("/api/tasks/{task_id}", status_code=204)
async def delete_task(task_id: int):
if task_id not in tasks:
raise HTTPException(404, f"Task {task_id} not found")
del tasks[task_id]
Protect your endpoints with API key authentication using a FastAPI dependency:
from fastapi import Depends, Security
from fastapi.security import APIKeyHeader
import secrets
api_key_header = APIKeyHeader(name="X-API-Key")
# In production, store keys in a database
VALID_KEYS = {
"sk_test_abc123": {"email": "dev@example.com", "tier": "free"},
}
async def get_api_key(api_key: str = Security(api_key_header)):
if api_key not in VALID_KEYS:
raise HTTPException(401, "Invalid API key")
return VALID_KEYS[api_key]
@app.get("/api/protected")
async def protected_endpoint(user=Depends(get_api_key)):
return {"message": "Authenticated!", "user": user["email"]}
This pattern is used by production API services. For example, DevProToolkit API Hub uses this exact approach to manage 13 APIs and 104+ endpoints with a single unified key system.
For production, replace in-memory storage with SQLite (simple) or PostgreSQL (scalable):
import aiosqlite
DB_PATH = "app.db"
async def init_db():
async with aiosqlite.connect(DB_PATH) as db:
await db.execute("""
CREATE TABLE IF NOT EXISTS tasks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
description TEXT,
priority INTEGER DEFAULT 1,
completed BOOLEAN DEFAULT 0,
created_at TEXT DEFAULT (datetime('now'))
)
""")
await db.commit()
@app.on_event("startup")
async def startup():
await init_db()
@app.post("/api/tasks", status_code=201)
async def create_task(task: TaskCreate):
async with aiosqlite.connect(DB_PATH) as db:
cursor = await db.execute(
"INSERT INTO tasks (title, description, priority) VALUES (?, ?, ?)",
(task.title, task.description, task.priority)
)
await db.commit()
task_id = cursor.lastrowid
return {"id": task_id, **task.model_dump()}
Add rate limiting with a simple in-memory counter:
from collections import defaultdict
import time
class RateLimiter:
def __init__(self, max_requests: int = 100, window: int = 60):
self.max_requests = max_requests
self.window = window
self.requests = defaultdict(list)
def is_allowed(self, key: str) -> bool:
now = time.time()
# Clean old entries
self.requests[key] = [
t for t in self.requests[key]
if now - t < self.window
]
if len(self.requests[key]) >= self.max_requests:
return False
self.requests[key].append(now)
return True
limiter = RateLimiter(max_requests=10, window=60)
@app.middleware("http")
async def rate_limit_middleware(request, call_next):
client_ip = request.client.host
if not limiter.is_allowed(client_ip):
return JSONResponse(
status_code=429,
content={"error": "Rate limit exceeded. Try again later."}
)
return await call_next(request)
For production deployment on Linux, use systemd to manage your FastAPI service:
# /etc/systemd/system/my-api.service
[Unit]
Description=My FastAPI Service
After=network.target
[Service]
Type=simple
User=ubuntu
WorkingDirectory=/home/ubuntu/my-api
ExecStart=/home/ubuntu/my-api/venv/bin/uvicorn main:app \
--host 0.0.0.0 --port 8000 --workers 2
Restart=always
RestartSec=5
Environment=PYTHONUNBUFFERED=1
[Install]
WantedBy=multi-user.target
# Enable and start the service
sudo systemctl daemon-reload
sudo systemctl enable my-api
sudo systemctl start my-api
# Check status
sudo systemctl status my-api
# View logs
journalctl -u my-api -f
/api/v1/ for breaking changes{"error": "message", "detail": "..."}/health endpoint that returns service statusasync def for I/O-bound endpointsallow_origins=["*"] in productionhttpx with FastAPI's TestClient for testingInstead of building from scratch, use DevProToolkit's 13 production APIs: QR codes, PDFs, TTS, email validation, crypto prices, and more. All built with FastAPI and the patterns shown in this guide.
Get Free API Key Try APIsYes. FastAPI is used in production by companies like Microsoft, Netflix, and Uber. It runs on uvicorn (ASGI) and supports async/await for high concurrency. With multiple workers and proper deployment, it handles thousands of requests per second.
FastAPI is faster, has better type safety, and generates docs automatically. Django REST Framework is better if you need Django's ORM, admin panel, and ecosystem. For pure API services, FastAPI is the modern choice.
Use uvicorn behind systemd (Linux), Docker, or a cloud platform. For high traffic, use multiple workers with --workers 4 or put it behind nginx as a reverse proxy. Always use HTTPS in production.
Get your free API key and start making requests in minutes.
curl "http://147.224.212.116/api/..." \
-H "X-API-Key: YOUR_API_KEY"
Get a free API key with 100 requests/day. No credit card required.
Get Free API Key