FastAPI Day 6: Error Handling and Middleware – 3 Practical Steps to Harden Your API
The article shows how to create a custom exception with a global handler, override FastAPI's default validation error format, add a request‑level middleware that records processing time, and configure CORS, providing three concrete techniques to make APIs more robust and consistent.
1. Custom Exception and Global Handler
Define a business exception class ItemNotFoundException inheriting from Exception, then register a global exception handler with @app.exception_handler(ItemNotFoundException). The handler returns a JSONResponse with status code 404 and a uniform payload containing code and message. In route functions you can simply raise ItemNotFoundException(item_id) without try/except; FastAPI routes the exception to the handler, ensuring consistent error format.
class ItemNotFoundException(Exception):
def __init__(self, item_id: int):
self.item_id = item_id
@app.exception_handler(ItemNotFoundException)
async def item_not_found_handler(request: Request, exc: ItemNotFoundException):
return JSONResponse(
status_code=404,
content={"code": 404, "message": f"Item {exc.item_id} not found"},
)2. Overriding the Default Validation Error Format
FastAPI’s default RequestValidationError response may be inconsistent. By adding another exception handler for RequestValidationError, you can return a unified JSON payload with fields code, message, and detail (the list of validation errors). This makes every 422 validation failure look the same regardless of missing fields, type mismatches, or rule violations.
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
return JSONResponse(
status_code=422,
content={"code": 422, "message": "Validation error", "detail": exc.errors()},
)3. Middleware for Request‑Level Interception
A middleware can run code before the request is processed and after the response is generated. The example records the start time, calls the next handler with call_next(request), computes the elapsed time, and adds an X-Process-Time header (in milliseconds) to the response. This header is useful for front‑end debugging and monitoring.
@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
start_time = time.time()
response = await call_next(request)
process_time = time.time() - start_time
response.headers["X-Process-Time"] = str(round(process_time * 1000, 2))
return response4. CORS Configuration
For front‑end/back‑end separation, configure CORSMiddleware to allow specific origins (e.g., http://localhost:3000). The example sets allow_credentials=True, allows all methods and headers, but notes that in production you should replace ["*"] with explicit domain names.
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)5. Recap of Day 6
Custom ItemNotFoundException and global handler
Override RequestValidationError to a unified format
Middleware adds X-Process-Time header
CORS middleware allowing localhost:3000
All five new test cases pass, bringing total to 42
Next Steps
Day 7 will cover security authentication, demonstrating JWT token generation and validation, and how to wire the login‑registration flow with dependency injection.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Tech Ocean
Focused on AI programming, sharing ready-to-use development efficiency solutions.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
