Error Handling
HTTPException — the main error tool
Section titled “HTTPException — the main error tool”In Express you call res.status(404).json(...) or pass an error to next(err). In FastAPI you raise an HTTPException — a Python exception that FastAPI catches and converts to an HTTP response. This keeps error paths looking like normal Python code.
// Express — return an error responseapp.get('/items/:id', async (req, res) => { const item = await db.findById(req.params.id); if (!item) return res.status(404).json({ message: 'Item not found' }); res.json(item);});
// NestJS — throw a built-in exception@Get(':id')async findOne(@Param('id') id: string) { const item = await this.itemsService.findOne(+id); if (!item) throw new NotFoundException('Item not found'); return item;}# FastAPI — raise HTTPExceptionfrom fastapi import FastAPI, HTTPException
app = FastAPI()
@app.get("/items/{item_id}")async def get_item(item_id: int): item = await db.find_by_id(item_id) if not item: raise HTTPException(status_code=404, detail="Item not found") return item
# detail can be a dict for structured errorsraise HTTPException( status_code=422, detail={"field": "price", "message": "must be positive"},)Custom exception handlers
Section titled “Custom exception handlers”For application-wide error handling — the equivalent of Express’s (err, req, res, next) error middleware — FastAPI provides @app.exception_handler.
// Express global error middlewareapp.use((err, req, res, next) => { console.error(err.stack); if (err.name === 'ValidationError') { return res.status(400).json({ error: err.message }); } res.status(500).json({ error: 'Internal Server Error' });});# FastAPI custom exception handlerfrom fastapi import FastAPI, Requestfrom fastapi.responses import JSONResponse
class ItemNotFoundError(Exception): def __init__(self, item_id: int): self.item_id = item_id
app = FastAPI()
@app.exception_handler(ItemNotFoundError)async def item_not_found_handler(request: Request, exc: ItemNotFoundError): return JSONResponse( status_code=404, content={"error": f"Item {exc.item_id} does not exist"}, )
@app.get("/items/{item_id}")async def get_item(item_id: int): raise ItemNotFoundError(item_id=item_id) # automatically caughtOverriding the default 422 handler
Section titled “Overriding the default 422 handler”FastAPI’s built-in validation error handler returns Pydantic’s RequestValidationError. You can override it to match your API’s error envelope.
// Zod error shaping in Expressapp.use((err, req, res, next) => { if (err instanceof ZodError) { return res.status(400).json({ errors: err.errors.map(e => ({ path: e.path, message: e.message })), }); } next(err);});# Override FastAPI's default validation error handlerfrom fastapi import FastAPI, Request, statusfrom fastapi.exceptions import RequestValidationErrorfrom fastapi.responses import JSONResponse
app = FastAPI()
@app.exception_handler(RequestValidationError)async def validation_exception_handler( request: Request, exc: RequestValidationError): return JSONResponse( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, content={ "errors": [ {"field": e["loc"][-1], "message": e["msg"]} for e in exc.errors() ] }, )Try it — Python exception hierarchy
Section titled “Try it — Python exception hierarchy”FastAPI HTTPException is a Python exception like any other. Explore how raise and except work with the stdlib:
# Python exceptions — the same mechanism FastAPI usesclass AppError(Exception): \"\"\"Base class for application errors.\"\"\" def __init__(self, message: str, status_code: int = 500): self.message = message self.status_code = status_code super().__init__(message)
class NotFoundError(AppError): def __init__(self, resource: str, resource_id: int): super().__init__(f"{resource} {resource_id} not found", 404) self.resource = resource self.resource_id = resource_id
class ValidationError(AppError): def __init__(self, field: str, message: str): super().__init__(f"[{field}] {message}", 422)
def find_item(item_id: int): if item_id <= 0: raise ValidationError("item_id", "must be a positive integer") if item_id > 100: raise NotFoundError("Item", item_id) return {"id": item_id, "name": "Widget"}
for test_id in [0, 42, 999]: try: result = find_item(test_id) print(f"OK {test_id:4d} -> {result}") except AppError as e: print(f"ERR {test_id:4d} -> {e.status_code} {e.message}")Loading Python runtime (first run only)…