Error Handling Basics with FastAPI and Pydantic¶
🎯 What You’ll Learn¶
- How FastAPI handles validation errors automatically
- How to raise custom HTTP exceptions
- How to structure and return meaningful error messages
- How errors appear in the auto-generated documentation
🚨 Automatic Validation Errors¶
FastAPI uses Pydantic to validate request bodies, query parameters, and path parameters. If the incoming data doesn’t match the expected types or structure, FastAPI automatically returns a 422 Unprocessable Entity error.
🧪 Example: Invalid Request Body¶
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Task(BaseModel):
title: str
completed: bool
@app.post("/tasks")
def create_task(task: Task):
return {"message": "Task created", "task": task}
🧭 Try sending this JSON:
{
"title": 123,
"completed": "nope"
}
FastAPI responds with:
{
"detail": [
{
"loc": ["body", "title"],
"msg": "str type expected",
"type": "type_error.str"
},
{
"loc": ["body", "completed"],
"msg": "value could not be parsed to a boolean",
"type": "type_error.bool"
}
]
}
🔍 Breakdown of Error Response¶
loc: Location of the error (body,query,path, etc.)msg: Human-readable error messagetype: Error category (e.g.,type_error.str)
These errors are:
- Automatically generated
- Consistent and structured
- Visible in
/docsand/redoc
🔧 Raising Custom HTTP Exceptions¶
FastAPI lets you raise custom errors using HTTPException.
🧪 Example: Manual Error Handling¶
from fastapi import HTTPException
@app.get("/divide")
def divide(x: float, y: float):
if y == 0:
raise HTTPException(status_code=400, detail="Division by zero is not allowed")
return {"result": x / y}
🧭 Try:
/divide?x=10&y=2→ ✅{"result": 5.0}/divide?x=10&y=0→ ❌{"detail": "Division by zero is not allowed"}
🔍 Custom Error Fields¶
status_code: Any valid HTTP status (e.g., 400, 404, 403)detail: Message shown to the client- You can also include
headers(e.g., for authentication errors)
🧪 Example: Resource Not Found¶
@app.get("/tasks/{task_id}")
def get_task(task_id: int):
fake_db = {1: "Buy milk", 2: "Write code"}
if task_id not in fake_db:
raise HTTPException(status_code=404, detail="Task not found")
return {"task_id": task_id, "title": fake_db[task_id]}
🧭 Try:
/tasks/1→ ✅{"task_id": 1, "title": "Buy milk"}/tasks/99→ ❌{"detail": "Task not found"}
🧠 Best Practices¶
- Use automatic validation for input errors
- Use
HTTPExceptionfor business logic errors - Always return clear, actionable error messages
- Avoid leaking internal details (e.g., stack traces, database errors)
🧪 Practice Challenge¶
Create an endpoint:
/loginthat accepts ausernameandpasswordvia a Pydantic model- If the username is not
"admin"or the password is not"secret", raise a 401 error - Return a success message otherwise