Skip to content

Chapter 3.6: Error Handling and Validation

Time to learn: 50 minutes


1. Error Handling: The Spaceship's "Emergency Shields"

Even on the most advanced ship, unforeseen situations can occur:

  • Incorrect command from Mission Control: The client sent invalid data.
  • Loss of communication with a module: The resource was not found in the database.
  • Reactor failure: An internal server error.

Proper error handling is a system of "emergency shields." It prevents the ship from falling apart and instead sends a clear signal to Mission Control about what went wrong.

💡 Space Analogy:

Instead of just sending a "FAILURE!" signal to Mission Control, a good onboard computer would send a structured report:

{
  "error_code": "ENGINE_OVERHEAT",
  "message": "Engine #2 temperature has exceeded normal levels",
  "suggested_action": "Activate cooling system"
}
This allows engineers on Earth to quickly understand the problem and take action.


2. Pydantic Validation: The Built-in "Onboard Computer"

We have already encountered the magic of Pydantic. If you try to create a ship with an incorrect data type (e.g., launch_year as a string), FastAPI will automatically return a 422 Unprocessable Entity error with a detailed description of which field failed validation and why.

Example request to POST /spaceships:

{
  "name": "X-Wing",
  "type": "Fighter",
  "launch_year": "a long time ago",  // <-- Invalid type!
  "status": "In service"
}

Automatic response from FastAPI:

{
  "detail": [
    {
      "loc": [
        "body",
        "launch_year"
      ],
      "msg": "value is not a valid integer",
      "type": "type_error.integer"
    }
  ]
}
This is incredibly powerful! You don't need to write code to check types—FastAPI and Pydantic do it for you.


3. Handling "Resource Not Found": The HTTPException

We have already used this in our CRUD operations. HTTPException is the standard FastAPI way to interrupt the execution of a request and immediately return an error response to the client.

Let's recall the code from GET /spaceships/{ship_id}:

# main.py
from fastapi import FastAPI, HTTPException # Make sure HTTPException is imported

# ...

@app.get("/spaceships/{ship_id}", response_model=Spaceship, tags=["Spacecraft"])
def get_spaceship(ship_id: int):
    ship = db_spaceships.get(ship_id)
    if not ship:
        # If the ship is not found, "raise" a 404 exception
        raise HTTPException(status_code=404, detail=f"Spacecraft with ID {ship_id} not found")
    return ship

  • raise HTTPException(...): This call stops the function's execution.
  • status_code=404: Sets the HTTP status of the response.
  • detail: The message that will be sent to the client in the JSON response body.

4. Custom Validators: "Special Checks" Before Launch

What if we want to add our own, more complex business logic? For example, to prohibit launching ships named "Death Star."

For this, Pydantic has a powerful tool: validators.

Step 1: Add a validator to the SpaceshipCreate model

# main.py
from pydantic import BaseModel, Field, validator

class SpaceshipCreate(BaseModel):
    name: str = Field(..., min_length=3, max_length=50)
    type: str
    launch_year: int = Field(..., gt=1950)
    status: str

    @validator('name')
    def name_must_not_be_forbidden(cls, v):
        """Checks that the ship's name is not on the forbidden list."""
        if 'Death Star' in v:
            raise ValueError('Names like "Death Star" are forbidden by Imperial decree!')
        return v.title() # Also, capitalize the name

  • @validator('name'): A decorator that "binds" this function to the name field.
  • cls, v: The method receives the class itself (cls) and the field's value (v).
  • raise ValueError(...): If the check fails, we raise a standard Python exception. FastAPI will catch it and turn it into a nice 422 error.
  • return v.title(): If everything is fine, we must return the value. We can even modify it on the fly (for example, standardize its format).

Step 2: Test it Restart uvicorn and try to create a ship with the forbidden name via /docs. You will get a 422 error with your custom message!


5. Global Error Handling: The Station's "Emergency Protocol"

Sometimes you need to catch unexpected errors (like a connection failure to a real database) and return a single, standardized response format.

For this, the @app.exception_handler decorator is used.

Example: Catching all ValueError exceptions

# main.py
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse

# ...

@app.exception_handler(ValueError)
async def value_error_exception_handler(request: Request, exc: ValueError):
    """
    A global handler for all ValueError exceptions
    to return a standardized JSON.
    """
    return JSONResponse(
        status_code=400,
        content={"message": f"Invalid data provided: {str(exc)}"},
    )

  • @app.exception_handler(ValueError): Tells FastAPI that this function should handle all ValueError exceptions that were not caught earlier.
  • async def ...: Exception handlers should be asynchronous (async).
  • JSONResponse: Allows you to fully control the response body and status.

Now, when our custom validator is triggered, the response will have the more user-friendly format that we defined.


Review Quiz

1. If a client sends data of the wrong type (a string instead of a number), FastAPI will automatically return the status...

2. `raise HTTPException(status_code=404)` is used to...

3. The `@validator('field_name')` decorator in Pydantic is for:

4. What must a Pydantic validator function do if the data is valid?

5. `@app.exception_handler()` allows you to...


🚀 Chapter Summary:

You have installed a powerful defense system and emergency protocols on your API ship. It can now:

  • 🛡️ Automatically repel "invalid data" attacks using Pydantic.
  • 🚨 Properly report missing resources (404 Not Found) via HTTPException.
  • ⚙️ Perform "special checks" with custom validators.
  • 🧯 Globally catch unexpected failures and provide standardized responses.

Your "hyperdrive" is not only fast, but also incredibly reliable!

📌 Checkpoint:

  • Try to create a ship named "Death Star" and ensure you get a 400 error with your custom message (from the global handler).
  • Try to request GET /spaceships/999 and ensure you get a 404 error.
  • Try to send a POST request with launch_year as a string and ensure you get a 422 error.

⚠️ If you have errors:

  • Make sure all necessary modules (HTTPException, validator, Request, JSONResponse) are imported.
  • Check that the @validator and @app.exception_handler decorators are written without typos.

Congratulations on completing Chapter 3! You have built, launched, and secured a powerful, well-documented API from scratch using FastAPI. You are ready to take on real space missions