DEV Community

archi-jain
archi-jain

Posted on

Day 8/100: API Documentation & Error Handling - The Professional Polish

Part of my 100 Days of Code journey. Today we transform working code into production-ready API.

The Challenge

Add comprehensive documentation and standardized error handling to make the API developer-friendly and production-ready.

The Problem:
My API works great... for me. But what about:

  • New developers trying to integrate?
  • Frontend developers needing to consume the API?
  • DevOps needing to monitor health?
  • Users getting cryptic error messages?

The Solution:
Professional documentation, structured errors, CORS support, and health monitoring.

Why Documentation Matters

True story: I once worked with an API that had zero documentation. Every endpoint was a mystery:

  • What parameters does it accept?
  • What response format does it return?
  • What errors can it throw?
  • How do I authenticate?

Result:

  • 3 weeks to integrate (should've been 3 days)
  • Countless support emails
  • Trial-and-error development
  • Frustrated developers

Good documentation changes everything.

Step-by-Step Implementation

Step 1: Enhanced FastAPI Configuration

from fastapi import FastAPI

app = FastAPI(
    title="Task Management API",
    description="""
    ## 🚀 Complete Task Management System

    A production-ready REST API for managing tasks with authentication, 
    tags, priorities, due dates, and advanced filtering.

    ### Features

    * **🔐 JWT Authentication** - Secure user registration and login
    * **📋 Task Management** - Full CRUD operations with rich metadata
    * **🏷️ Tag System** - Organize tasks with custom tags and colors
    * **⚡ Priority Levels** - High, medium, low priorities
    * **📅 Due Dates** - Track deadlines and overdue tasks
    * **🔍 Advanced Filtering** - Search, sort, filter by multiple criteria
    * **📊 Analytics** - Task statistics and insights
    * **⚙️ Bulk Operations** - Efficient multi-task updates

    ### Getting Started

    1. Register a new account at `/auth/register`
    2. Login at `/auth/login` to get your access token
    3. Click the 🔒 Authorize button and paste your token
    4. Start managing your tasks!
    """,
    version="1.0.0",
    contact={
        "name": "Archi Jain",
        "url": "https://github.com/archi-jain/100-days-of-python",
        "email": "your-email@example.com"
    },
    license_info={
        "name": "MIT License",
        "url": "https://opensource.org/licenses/MIT"
    }
)
Enter fullscreen mode Exit fullscreen mode

What this does:

When developers visit /docs, they see:

  • Clear API overview
  • Feature list
  • Getting started guide
  • Contact information
  • License details

Professional first impression!

Step 2: Endpoint Organization with Tags

app = FastAPI(
    # ... previous config ...
    openapi_tags=[
        {
            "name": "Authentication",
            "description": "User registration, login, and profile management"
        },
        {
            "name": "Tasks",
            "description": "Task CRUD operations, filtering, sorting, and bulk actions"
        },
        {
            "name": "Tags",
            "description": "Tag management for organizing tasks"
        },
        {
            "name": "System",
            "description": "API health checks and system information"
        }
    ]
)
Enter fullscreen mode Exit fullscreen mode

Result:
Endpoints grouped logically in documentation. Developers can find what they need quickly.

Step 3: Detailed Endpoint Documentation

Before:

@app.post("/tasks")
def create_task(task: TaskCreate, ...):
    # code
Enter fullscreen mode Exit fullscreen mode

After:

@router.post(
    "/",
    response_model=schemas.TaskResponse,
    status_code=status.HTTP_201_CREATED,
    summary="Create a new task",
    description="""
    Create a new task with optional tags, priority, and due date.

    **Request Body:**
    - **title** (required): Task name/description
    - **description** (optional): Detailed information
    - **priority** (optional): high, medium, or low (default: medium)
    - **due_date** (optional): Deadline in ISO 8601 format
    - **tag_ids** (optional): List of tag UUIDs to associate

    **Returns:**
    - Newly created task with generated ID and timestamps

    **Errors:**
    - 400: Invalid tag IDs or past due date
    - 401: Not authenticated
    - 422: Validation error (missing title, invalid format)
    """,
    responses={
        201: {
            "description": "Task created successfully",
            "content": {
                "application/json": {
                    "example": {
                        "id": "123e4567-e89b-12d3-a456-426614174000",
                        "title": "Complete project",
                        "completed": False,
                        "priority": "high",
                        "due_date": "2026-03-15T17:00:00Z"
                    }
                }
            }
        }
    }
)
def create_task(task: schemas.TaskCreate, ...):
    """Create a new task for the authenticated user"""
    # code
Enter fullscreen mode Exit fullscreen mode

What developers see:

  • Clear summary
  • Detailed description
  • Parameter explanations
  • Success example
  • Error cases
  • Sample request/response

Step 4: Schema Examples

class TaskCreate(BaseModel):
    title: str = Field(
        ..., 
        max_length=100,
        description="Task title (required)",
        examples=["Complete project proposal", "Review pull requests"]
    )
    description: Optional[str] = Field(
        None, 
        max_length=500,
        description="Detailed task description (optional)",
        examples=["Prepare Q1 financial review slides"]
    )

    model_config = {
        "json_schema_extra": {
            "examples": [
                {
                    "title": "Submit quarterly report",
                    "description": "Prepare Q1 2026 financial report",
                    "priority": "high",
                    "due_date": "2026-03-15T17:00:00Z"
                }
            ]
        }
    }
Enter fullscreen mode Exit fullscreen mode

Result:
When developers click "Try it out", the form is pre-filled with valid example data!

Step 5: CORS Configuration

from fastapi.middleware.cors import CORSMiddleware

app.add_middleware(
    CORSMiddleware,
    allow_origins=[
        "http://localhost:3000",  # React default
        "http://localhost:5173",  # Vite default
        "http://localhost:8080",  # Vue default
        # Add your production domains
    ],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)
Enter fullscreen mode Exit fullscreen mode

Why this matters:

Without CORS:

// Frontend code
fetch('http://localhost:8000/tasks')

// Browser blocks:
// "CORS policy: No 'Access-Control-Allow-Origin' header"
Enter fullscreen mode Exit fullscreen mode

With CORS:

fetch('http://localhost:8000/tasks')
// ✅ Works!
Enter fullscreen mode Exit fullscreen mode

Frontend developers can integrate immediately.

Step 6: Standardized Error Responses

Before:

if not task:
    raise HTTPException(404, "Task not found")

# Response: {"detail": "Task not found"}
Enter fullscreen mode Exit fullscreen mode

After:

class APIError(HTTPException):
    def __init__(self, status_code, error_code, message, details=None):
        super().__init__(
            status_code=status_code,
            detail={
                "error": {
                    "code": error_code,
                    "message": message,
                    "details": details,
                    "timestamp": datetime.utcnow().isoformat()
                }
            }
        )

class TaskNotFoundError(APIError):
    def __init__(self, task_id: str):
        super().__init__(
            status_code=404,
            error_code="TASK_NOT_FOUND",
            message="Task not found",
            details=f"Task with ID {task_id} does not exist or you don't have access"
        )

# Usage:
if not task:
    raise TaskNotFoundError(str(task_id))

# Response:
{
  "error": {
    "code": "TASK_NOT_FOUND",
    "message": "Task not found",
    "details": "Task with ID abc123 does not exist or you don't have access",
    "timestamp": "2026-03-08T10:30:00Z"
  }
}
Enter fullscreen mode Exit fullscreen mode

Benefits:

  1. Error codes - Frontend can handle programmatically
  2. Detailed messages - Developers know what went wrong
  3. Timestamps - Debugging and logging
  4. Consistency - All errors follow same format

Frontend handling:

try {
  const response = await fetch('/tasks/invalid-id')
  const data = await response.json()

  if (!response.ok) {
    switch (data.error.code) {
      case 'TASK_NOT_FOUND':
        showNotification("Task doesn't exist")
        break
      case 'AUTHENTICATION_FAILED':
        redirectToLogin()
        break
      default:
        showError(data.error.message)
    }
  }
} catch (error) {
  // Handle error
}
Enter fullscreen mode Exit fullscreen mode

Step 7: Custom Validation Error Handler

from fastapi import Request
from fastapi.responses import JSONResponse
from fastapi.exceptions import RequestValidationError

@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
    errors = []
    for error in exc.errors():
        errors.append({
            "field": " -> ".join(str(x) for x in error["loc"]),
            "message": error["msg"],
            "type": error["type"]
        })

    return JSONResponse(
        status_code=422,
        content={
            "error": {
                "code": "VALIDATION_ERROR",
                "message": "Request validation failed",
                "details": errors,
                "timestamp": datetime.utcnow().isoformat()
            }
        }
    )
Enter fullscreen mode Exit fullscreen mode

Before:

{
  "detail": [
    {
      "loc": ["body", "title"],
      "msg": "field required",
      "type": "value_error.missing"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

After:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Request validation failed",
    "details": [
      {
        "field": "body -> title",
        "message": "field required",
        "type": "value_error.missing"
      }
    ],
    "timestamp": "2026-03-08T10:30:00Z"
  }
}
Enter fullscreen mode Exit fullscreen mode

Much clearer!

Step 8: Health Check Endpoint

@app.get("/health", tags=["System"])
def health_check():
    """Check API health and database connectivity"""
    from database import SessionLocal

    db_status = "healthy"
    try:
        db = SessionLocal()
        db.execute("SELECT 1")
        db.close()
    except Exception as e:
        db_status = f"unhealthy: {str(e)}"

    return {
        "status": "healthy" if db_status == "healthy" else "degraded",
        "version": "1.0.0",
        "timestamp": datetime.utcnow().isoformat(),
        "database": db_status
    }
Enter fullscreen mode Exit fullscreen mode

Why DevOps loves this:

# Kubernetes health check
livenessProbe:
  httpGet:
    path: /health
    port: 8000
  initialDelaySeconds: 30
  periodSeconds: 10

# Monitoring alert
if health_status != "healthy":
  send_alert()
Enter fullscreen mode Exit fullscreen mode

Step 9: API Information Endpoint

@app.get("/", tags=["System"])
def root():
    """Get basic API information"""
    return {
        "name": "Task Management API",
        "version": "1.0.0",
        "status": "operational",
        "documentation": {
            "swagger": "/docs",
            "redoc": "/redoc"
        },
        "endpoints": {
            "health": "/health",
            "auth": "/auth",
            "tasks": "/tasks",
            "tags": "/tags"
        },
        "features": [
            "JWT Authentication",
            "Task Management",
            "Tags & Priorities",
            "Due Dates",
            "Advanced Filtering",
            "Bulk Operations"
        ]
    }
Enter fullscreen mode Exit fullscreen mode

First thing developers see:

curl http://localhost:8000/

# Returns clear API overview with links to docs
Enter fullscreen mode Exit fullscreen mode

Testing the Enhancements

Test 1: Enhanced Documentation

# Open browser
http://localhost:8000/docs
Enter fullscreen mode Exit fullscreen mode

What you'll see:

  • Professional API overview
  • Organized endpoint groups
  • Detailed descriptions
  • Try-it-out with examples
  • Error documentation

Test 2: Health Check

curl http://localhost:8000/health
Enter fullscreen mode Exit fullscreen mode

Response:

{
  "status": "healthy",
  "version": "1.0.0",
  "timestamp": "2026-03-08T10:30:00.123456",
  "database": "healthy"
}
Enter fullscreen mode Exit fullscreen mode

Test 3: Structured Error

# Create task with missing title
curl -X POST http://localhost:8000/tasks \
  -H "Authorization: Bearer TOKEN" \
  -H "Content-Type: application/json" \
  -d '{}'
Enter fullscreen mode Exit fullscreen mode

Response:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Request validation failed",
    "details": [
      {
        "field": "body -> title",
        "message": "field required",
        "type": "value_error.missing"
      }
    ],
    "timestamp": "2026-03-08T10:30:00Z"
  }
}
Enter fullscreen mode Exit fullscreen mode

Clear, actionable error!

Test 4: CORS from Frontend

// React component
useEffect(() => {
  fetch('http://localhost:8000/tasks', {
    headers: {
      'Authorization': `Bearer ${token}`
    }
  })
  .then(res => res.json())
  .then(data => setTasks(data))
  // ✅ Works! No CORS errors
}, [])
Enter fullscreen mode Exit fullscreen mode

Real-World Impact

Before Day 8:

New Developer Experience:

  1. Opens /docs - sees basic endpoint list
  2. Tries to create task - no examples
  3. Gets error: {"detail": "Error"} - what error?
  4. Frontend can't connect - CORS errors
  5. No way to check if API is running
  6. Integration takes 2 weeks

Support burden:

  • "How do I create a task?"
  • "What's the error response format?"
  • "Why am I getting CORS errors?"
  • "Is the API down?"

After Day 8:

New Developer Experience:

  1. Opens /docs - sees professional overview
  2. Reads detailed endpoint descriptions
  3. Clicks "Try it out" - sees example pre-filled
  4. Gets structured error with clear message
  5. Frontend connects immediately (CORS configured)
  6. /health endpoint confirms API status
  7. Integration takes 2 days

Support burden:

  • Developers self-serve via documentation
  • Clear errors reduce confusion
  • CORS works out of the box
  • Health check for monitoring

Key Takeaways

1. Documentation is a Feature

Code that works but isn't documented is only half-done. Documentation is how your API gets adopted.

2. Errors Should Help, Not Confuse

# Bad
{"detail": "Error"}

# Good
{
  "error": {
    "code": "TASK_NOT_FOUND",
    "message": "Task not found",
    "details": "Task abc123 does not exist or you lack access",
    "timestamp": "2026-03-08T10:30:00Z"
  }
}
Enter fullscreen mode Exit fullscreen mode

3. CORS is Not Optional

If you want frontend developers to use your API, CORS must be configured. Period.

4. Health Checks Enable Monitoring

DevOps can't monitor what they can't query. /health endpoint = observable systems.

5. Examples Save Time

One good example is worth a thousand words. Pre-filled "Try it out" = instant understanding.

What I Learned

Technical Skills

✅ OpenAPI/Swagger configuration

✅ FastAPI metadata and descriptions

✅ Custom exception handlers

✅ CORS middleware setup

✅ Health check implementation

✅ Schema examples and validation

✅ Error response standardization

API Design Principles

✅ Developer experience is crucial

✅ Consistency reduces friction

✅ Good errors save support time

✅ Documentation = adoption

Production Readiness

✅ CORS for frontend integration

✅ Health checks for monitoring

✅ Structured errors for clients

✅ Comprehensive documentation

The Numbers

Before:

  • Basic documentation: 5/10
  • Error clarity: 3/10
  • Frontend ready: 0/10 (CORS blocked)
  • Monitoring: 0/10 (no health check)

After:

  • Professional documentation: 10/10
  • Error clarity: 10/10
  • Frontend ready: 10/10 (CORS configured)
  • Monitoring: 10/10 (health endpoint)

Time investment: 2.5 hours

Value added: Immeasurable

Tomorrow's Plans

Day 9 will add:

  • CSV/JSON export functionality
  • Data import from files
  • Batch task creation
  • Backup/restore capabilities

But today? Today I celebrate making my API production-ready and developer-friendly! 🎉

Resources

Full Code

Complete code on GitHub:
100-days-of-python/days/008


Time Investment: 2.5 hours

Knowledge Gained: Professional API development practices

Feeling: Like a real API developer 🚀

Day 8/100 complete!

Tomorrow: Data import/export features


Following my journey?

📝 Blog | 🐦 Twitter | 💼 LinkedIn

Tags: #100DaysOfCode #Python #FastAPI #APIDevelopment #Documentation #OpenAPI #Swagger #ErrorHandling #CORS #DeveloperExperience #ProductionReady #LearningInPublic

Top comments (0)