FastAPI

Robust Exception Handling in FastAPI: Best Practices and Real-World Examples

In FastAPI, exception handling is crucial to ensure that your API gracefully handles errors and provides meaningful responses to users. FastAPI provides a robust mechanism for catching exceptions and returning custom responses. Here’s a detailed guide on exception handling in FastAPI:

1. Using HTTPException

FastAPI has a built-in HTTPException class, which you can raise when you need to return a specific HTTP status code along with a message.

Example of HTTPException:

from fastapi import FastAPI, HTTPException

app = FastAPI()

@app.get("/items/{item_id}")
async def read_item(item_id: int):
    if item_id == 3:
        raise HTTPException(status_code=404, detail="Item not found")
    return {"item_id": item_id}

In this example, if the user requests item ID 3, the server will return a 404 status with the message "Item not found".

2. Custom Exception Handling

You can define your custom exception classes and handle them using FastAPI’s dependency injection and exception handlers.

Example of Custom Exception:

from fastapi import FastAPI, HTTPException
from typing import Union

app = FastAPI()

class ItemNotFoundException(Exception):
    def __init__(self, name: str):
        self.name = name

@app.get("/items/{item_name}")
async def get_item(item_name: str):
    if item_name == "broken_item":
        raise ItemNotFoundException(name=item_name)
    return {"item_name": item_name}

Or Simple Override the HttpException as

from fastapi import FastAPI, HTTPException
app = FastAPI()

class ItemNotFoundException(HTTPException):
    def __init__(self, item_name: str):
        super().__init__(
            status_code=404,
            detail={
                "error_code": "ITEM_NOT_FOUND",
                "message": f"'{item_name}' Not Found.",
                "redirect": True,
                "redirect_to": "product_list"
            },
        )

@app.get("/items/{item_name}")
async def get_item(item_name: str):
    if item_name == "broken_item":
        # Raise the custom exception when the item is "broken_item"
        raise ItemNotFoundException(item_name=item_name)
    return {"item_name": item_name}

3. Global Exception Handlers

FastAPI allows you to register global exception handlers using the @app.exception_handler decorator.

Example of a Global Exception Handler:

from fastapi import FastAPI, HTTPException
from fastapi.responses import JSONResponse

app = FastAPI()

# Custom exception
class ItemNotFoundException(Exception):
    def __init__(self, name: str):
        self.name = name

# Global handler
@app.exception_handler(ItemNotFoundException)
async def item_not_found_exception_handler(request, exc: ItemNotFoundException):
    return JSONResponse(
        status_code=404,
        content={"message": f"Item {exc.name} not found"},
    )

@app.get("/items/{item_name}")
async def get_item(item_name: str):
    if item_name == "broken_item":
        raise ItemNotFoundException(name=item_name)
    return {"item_name": item_name}

In this example, the global exception handler will catch any ItemNotFoundException and return a custom JSON response.

4. Handling Validation Errors

FastAPI automatically raises validation errors when the input data doesn’t match the expected type or format. You can catch and handle these errors using FastAPI’s built-in error handlers.

Example of Validation Error Handling:

from fastapi import FastAPI, Body, HTTPException
from pydantic import BaseModel

app = FastAPI()

class Item(BaseModel):
    name: str
    price: float

@app.post("/items/")
async def create_item(item: Item):
    if item.price < 0:
        raise HTTPException(status_code=400, detail="Price must be greater than 0")
    return {"name": item.name, "price": item.price}

Here, FastAPI will automatically handle the validation errors if the request body doesn't match the Item schema.

5. Custom Error Responses

FastAPI also allows you to define custom error responses . You can define a custom exception handler for HTTPException or specifically for your custom error, and within that handler, you can use the response_model to structure the error response.

Example of Custom Error Responses:

from fastapi import FastAPI, HTTPException
from fastapi.responses import JSONResponse
from pydantic import BaseModel

app = FastAPI()

# Define the response model for errors
class ErrorResponse(BaseModel):
    detail: str

# Exception handler for HTTPException
@app.exception_handler(HTTPException)
async def http_exception_handler(request, exc: HTTPException):
    return JSONResponse(
        status_code=exc.status_code,
        content={"detail": exc.detail}
    )

@app.get("/error")
async def error_endpoint():
    raise HTTPException(status_code=400, detail="Custom error")

In this example, a custom error response is defined.

6. Catch-All Exception Handler

You can also define a catch-all exception handler to catch any unexpected exceptions that are not explicitly handled.

Example of Catch-All Exception Handler:

from fastapi import FastAPI, HTTPException
from fastapi.responses import JSONResponse

app = FastAPI()

@app.exception_handler(Exception)
async def global_exception_handler(request, exc: Exception):
    return JSONResponse(
        status_code=500,
        content={"message": "An unexpected error occurred","exception":str(exc)},
    )

@app.get("/cause_error")
async def cause_error():
    raise ValueError("This is an unexpected error!")

7. Handling Database Errors

If you're using a database, you can catch exceptions that are specific to your database (e.g., SQLAlchemy exceptions). FastAPI allows you to handle these exceptions in the same way as other exceptions.

Example of Handling Database Errors:

from fastapi import FastAPI, HTTPException
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import Session
from database import SessionLocal, engine

app = FastAPI()

@app.post("/add_item")
async def add_item(item_name: str, db: Session = Depends(get_db)):
    try:
        db_item = Item(name=item_name)
        db.add(db_item)
        db.commit()
        return {"message": "Item added"}
    except IntegrityError:
        db.rollback()
        raise HTTPException(status_code=400, detail="Item already exists")

In this example, if there is a database constraint violation (like adding an item that already exists), an IntegrityError is caught, and a custom HTTP error is returned.

Summary:

  • HTTPException: Use this for standard HTTP error responses.
  • Custom Exceptions: Define and raise your own custom exceptions.
  • Global Handlers: Define custom exception handlers for specific exceptions.
  • Validation Errors: Automatically handled by FastAPI for request data validation.
  • Catch-All Handlers: Handle all unexpected exceptions globally.
  • Database Errors: Handle database-specific errors like IntegrityError.

 


About author

author image

Amrit panta

Fullstack developer, content creator



Scroll to Top