API Design - REST Principles, Versioning, Documentation

API DevelopmentAPI DesignFree Lesson

Advertisement

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

  1. Design a RESTful API for a blog with posts and comments
  2. Implement pagination with cursor-based navigation
  3. Add filtering and sorting to list endpoints
  4. Create API documentation with examples
  5. Implement API versioning with deprecation strategy

Advertisement

Need Expert Python Help?

Get personalized tutoring, project support, or professional consulting.

Advertisement