Build a REST API in Python with FastAPI: Step-by-Step Guide
FastAPI is the fastest and most modern Python web framework for building APIs. It auto-generates OpenAPI documentation, validates data with Pydantic and offers native async/await support.
1. Installation and first API
pip install fastapi uvicorn[standard]
# main.py
from fastapi import FastAPI
app = FastAPI(
title="My API",
description="Sample FastAPI application",
version="1.0.0",
)
@app.get("/")
def root():
return {"message": "Hello, FastAPI!"}
@app.get("/health")
def health():
return {"status": "ok"}
uvicorn main:app --reload
# Interactive docs auto-generated at:
# http://localhost:8000/docs (Swagger UI)
# http://localhost:8000/redoc (ReDoc)
2. Path and query parameters
from fastapi import FastAPI, HTTPException, Query
from typing import Optional
app = FastAPI()
books_db = {
1: {"title": "Clean Code", "author": "Martin", "price": 39.99},
2: {"title": "Python Tricks", "author": "Bader", "price": 29.99},
3: {"title": "Fluent Python", "author": "Ramalho", "price": 49.99},
}
@app.get("/books/{book_id}")
def get_book(book_id: int):
if book_id not in books_db:
raise HTTPException(status_code=404, detail="Book not found")
return books_db[book_id]
@app.get("/books")
def list_books(
skip: int = Query(default=0, ge=0),
limit: int = Query(default=10, ge=1, le=100),
author: Optional[str] = None,
sort: str = Query(default="title", enum=["title", "price"]),
):
books = list(books_db.values())
if author:
books = [b for b in books if author.lower() in b["author"].lower()]
books.sort(key=lambda b: b[sort])
return {"total": len(books), "books": books[skip:skip+limit]}
3. Pydantic models: automatic validation
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field, validator
from typing import Optional
from datetime import datetime
app = FastAPI()
class BookCreate(BaseModel):
title: str = Field(..., min_length=1, max_length=200)
author: str = Field(..., min_length=1)
price: float = Field(..., gt=0, le=9999.99)
isbn: Optional[str] = Field(None, pattern=r"^\d{13}$")
published: Optional[datetime] = None
@validator("title")
def strip_title(cls, v):
return v.strip()
class BookResponse(BookCreate):
id: int
created_at: datetime
books: dict[int, dict] = {}
next_id = 1
@app.post("/books", response_model=BookResponse, status_code=201)
def create_book(book: BookCreate):
global next_id
new_book = {
"id": next_id,
"title": book.title,
"author": book.author,
"price": book.price,
"isbn": book.isbn,
"published": book.published,
"created_at": datetime.now(),
}
books[next_id] = new_book
next_id += 1
return new_book
@app.put("/books/{book_id}", response_model=BookResponse)
def update_book(book_id: int, data: BookCreate):
if book_id not in books:
raise HTTPException(404, "Book not found")
books[book_id].update(data.dict(exclude_unset=True))
return books[book_id]
@app.delete("/books/{book_id}", status_code=204)
def delete_book(book_id: int):
if book_id not in books:
raise HTTPException(404, "Book not found")
del books[book_id]
4. Dependency injection
from fastapi import FastAPI, Depends, HTTPException, Header
app = FastAPI()
def pagination(page: int = 1, per_page: int = 10):
if page < 1:
raise HTTPException(400, "page must be >= 1")
return {"skip": (page - 1) * per_page, "limit": per_page}
@app.get("/products")
def list_products(pag: dict = Depends(pagination)):
return {"pagination": pag, "products": []}
API_KEYS = {"my-secret-api-key", "another-key"}
def verify_api_key(x_api_key: str = Header(...)):
if x_api_key not in API_KEYS:
raise HTTPException(status_code=401, detail="Invalid API key")
return x_api_key
@app.get("/admin/stats", dependencies=[Depends(verify_api_key)])
def stats():
return {"users": 1234, "conversions_today": 567}
5. Async endpoints
import asyncio, httpx
from fastapi import FastAPI
app = FastAPI()
@app.get("/weather/{city}")
async def get_weather(city: str):
async with httpx.AsyncClient() as client:
r = await client.get(
"https://api.open-meteo.com/v1/forecast",
params={"latitude": 51.5, "longitude": -0.1, "current_weather": True},
timeout=10,
)
r.raise_for_status()
return {"city": city, "weather": r.json().get("current_weather")}
@app.get("/batch")
async def batch_requests():
async with httpx.AsyncClient() as client:
tasks = [
client.get("https://httpbin.org/anything/1"),
client.get("https://httpbin.org/anything/2"),
client.get("https://httpbin.org/anything/3"),
]
responses = await asyncio.gather(*tasks)
return [r.json() for r in responses]
6. CORS
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["https://myfrontend.com", "http://localhost:3000"],
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "DELETE"],
allow_headers=["*"],
)
7. File upload
from fastapi import FastAPI, UploadFile, File, HTTPException
from pathlib import Path
app = FastAPI()
ALLOWED_EXTENSIONS = {".jpg", ".png", ".pdf", ".docx"}
MAX_SIZE = 10 * 1024 * 1024 # 10 MB
@app.post("/upload")
async def upload_file(file: UploadFile = File(...)):
ext = Path(file.filename).suffix.lower()
if ext not in ALLOWED_EXTENSIONS:
raise HTTPException(400, f"Extension not allowed: {ext}")
content = await file.read()
if len(content) > MAX_SIZE:
raise HTTPException(413, "File too large (max 10 MB)")
dest = Path("uploads") / file.filename
dest.parent.mkdir(exist_ok=True)
dest.write_bytes(content)
return {"filename": file.filename, "size": len(content), "type": file.content_type}
8. Global error handling
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
app = FastAPI()
@app.exception_handler(ValueError)
async def value_error_handler(request: Request, exc: ValueError):
return JSONResponse(status_code=400, content={"error": str(exc)})
@app.exception_handler(Exception)
async def generic_error(request: Request, exc: Exception):
return JSONResponse(status_code=500, content={"error": "Internal server error"})
9. Routers: organize endpoints in modules
# routers/books.py
from fastapi import APIRouter
router = APIRouter(prefix="/books", tags=["Books"])
@router.get("/")
def list_books(): return []
# main.py
from fastapi import FastAPI
from routers import books
app = FastAPI()
app.include_router(books.router)
10. Deployment
# Production: Gunicorn + Uvicorn workers
pip install gunicorn
gunicorn main:app -w 4 -k uvicorn.workers.UvicornWorker --bind 0.0.0.0:8000
Feature comparison
| Feature | FastAPI | Flask | Django REST |
|---|---|---|---|
| Native async | Yes | No | Partial |
| Auto validation | Pydantic | Manual | Serializers |
| OpenAPI docs | Auto | Extension | Extension |
| Speed | High | Medium | Medium |
| Learning curve | Low | Very low | Medium |
Related conversions
Frequent conversions across the catalogue: