Introduction
Good API design follows REST principles and provides clear versioning and documentation. This tutorial covers REST best practices, URL versioning, and OpenAPI documentation.
REST Principles
# RESTful resource design
# Bad: /getAllUsers, /createNewUser
# Good: GET /users, POST /users
# Route structure
from fastapi import FastAPI, HTTPException
from typing import List, Optional
app = FastAPI()
class User:
def __init__(self, id: int, name: str, email: str):
self.id = id
self.name = name
self.email = email
users_db = []
@app.get("/users", response_model=List[dict])
async def list_users(limit: int = 10, offset: int = 0):
"""List all users with pagination."""
return users_db[offset:offset + limit]
@app.get("/users/{user_id}")
async def get_user(user_id: int):
"""Get a single user by ID."""
user = next((u for u in users_db if u['id'] == user_id), None)
if not user:
raise HTTPException(status_code=404, detail="User not found")
return user
@app.post("/users", status_code=201)
async def create_user(user: dict):
"""Create a new user."""
new_id = max([u['id'] for u in users_db], default=0) + 1
user['id'] = new_id
users_db.append(user)
return user
@app.put("/users/{user_id}")
async def update_user(user_id: int, user: dict):
"""Update an existing user."""
for u in users_db:
if u['id'] == user_id:
u.update(user)
return u
raise HTTPException(status_code=404, detail="User not found")
@app.delete("/users/{user_id}", status_code=204)
async def delete_user(user_id: int):
"""Delete a user."""
global users_db
users_db = [u for u in users_db if u['id'] != user_id]
API Versioning
# URL-based versioning
# /api/v1/users
# /api/v2/users
from fastapi import APIRouter
v1_router = APIRouter(prefix="/api/v1")
v2_router = APIRouter(prefix="/api/v2")
@v1_router.get("/users")
async def list_users_v1():
return [{"id": 1, "name": "John"}]
@v2_router.get("/users")
async def list_users_v2():
return [{"id": 1, "name": "John", "created_at": "2024-01-01"}]
app.include_router(v1_router)
app.include_router(v2_router)
# Header-based versioning
@app.get("/users", headers={"X-API-Version": "2"})
async def list_users_header(version: str = None):
if version == "2":
return [{"id": 1, "name": "John", "created_at": "2024-01-01"}]
return [{"id": 1, "name": "John"}]
OpenAPI Documentation
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr
from typing import Optional
app = FastAPI(
title="User Management API",
description="API for managing users and their permissions",
version="2.0.0",
docs_url="/docs",
redoc_url="/redoc"
)
class UserCreate(BaseModel):
name: str = ..., min_length=1, max_length=100
email: EmailStr
age: Optional[int] = Field(None, ge=0, le=150)
class Config:
example = {
"name": "John Doe",
"email": "john@example.com",
"age": 30
}
class UserResponse(BaseModel):
id: int
name: str
email: str
age: Optional[int]
@app.post(
"/users",
response_model=UserResponse,
status_code=201,
summary="Create a new user",
tags=["Users"]
)
async def create_user(user: UserCreate):
"""Create a new user in the system."""
return {"id": 1, **user.dict()}
@app.get(
"/users/{user_id}",
response_model=UserResponse,
tags=["Users"],
responses={404: {"description": "User not found"}}
)
async def get_user(user_id: int):
"""Get a user by ID."""
return {"id": user_id, "name": "John", "email": "john@example.com"}
Best Practices
# Consistent response format
class APIResponse(BaseModel):
success: bool
data: Optional[Any] = None
message: Optional[str] = None
errors: Optional[List[dict]] = None
@app.get("/users/{user_id}")
async def get_user(user_id: int):
return APIResponse(
success=True,
data={"id": user_id, "name": "John"},
message="User retrieved successfully"
)
# Error responses
@app.exception_handler(Exception)
async def global_exception_handler(request, exc):
return APIResponse(
success=False,
message="Internal server error",
errors=[{"detail": str(exc)}]
)
Practice Problems
- Design a RESTful API for a blog with posts and comments
- Implement pagination with cursor-based navigation
- Add filtering and sorting to list endpoints
- Create API documentation with examples
- Implement API versioning with deprecation strategy