Introduction: Why Validation and Error Handling?
Chapter 1: Introduction: Why Validation and Error Handling?
Welcome to Lesson 18 of our Python and FastAPI journey. In the previous lessons, we built a working API connected to a SQLite database. We can create, read, update, and delete tasks. But what happens when a user sends bad data? What if they try to create a task without a title, or they send a string where a number is expected? Without proper checks, our API would either crash or store garbage data. This is where validation and error handling come in.
What is Validation?
Validation is the process of checking that the data sent to your API meets your requirements before you process it. For example, you might require that a task title is at least 3 characters long, or that a due date is in the future. Validation acts as a gatekeeper: only clean, correct data gets through.
What is Error Handling?
Error handling is the way your API responds when something goes wrong. Instead of crashing or returning a confusing server error, your API should return a clear, helpful message. For example, if a user tries to fetch a task that doesn't exist, you should return a 404 status code with a message like "Task not found".
Why Do We Need Both?
Imagine you are building a task management app. A user submits a new task with an empty title. Without validation, that empty title gets saved to your database. Later, when you try to display all tasks, your app might break because it expects a non-empty title. Without error handling, the user sees a generic "500 Internal Server Error" and has no idea what went wrong. By combining validation and error handling, you protect your data and give users a smooth experience.
How FastAPI Helps
FastAPI comes with built-in validation through Pydantic models. When you define a Pydantic model with type hints and constraints, FastAPI automatically validates incoming request data. If validation fails, FastAPI returns a clear 422 Unprocessable Entity error with details about what went wrong. For custom error handling, FastAPI allows you to raise HTTP exceptions with specific status codes and messages.
Practical Example: Adding Validation to Our Task Model
Let's improve our existing Pydantic model for tasks. We'll add constraints to ensure data quality.
# models.py
from pydantic import BaseModel, Field
from typing import Optional
from datetime import datetime
class TaskCreate(BaseModel):
title: str = Field(..., min_length=3, max_length=100, description="Task title must be 3-100 characters")
description: Optional[str] = Field(None, max_length=500)
due_date: Optional[datetime] = None
completed: bool = False
class TaskUpdate(BaseModel):
title: Optional[str] = Field(None, min_length=3, max_length=100)
description: Optional[str] = Field(None, max_length=500)
due_date: Optional[datetime] = None
completed: Optional[bool] = None
class TaskResponse(BaseModel):
id: int
title: str
description: Optional[str]
due_date: Optional[datetime]
completed: bool
created_at: datetime
In this example, we use Field to add constraints. The ... means the field is required. min_length=3 ensures the title has at least 3 characters. If a user sends a title with only 2 characters, FastAPI will automatically reject it with a 422 error.
Practical Example: Custom Error Handling in Endpoints
Now let's add proper error handling to our endpoints. We'll use HTTPException to return meaningful errors.
# main.py (relevant parts)
from fastapi import FastAPI, HTTPException, status
from database import get_db
from models import TaskCreate, TaskUpdate, TaskResponse
app = FastAPI()
@app.get("/tasks/{task_id}", response_model=TaskResponse)
def get_task(task_id: int):
db = get_db()
cursor = db.cursor()
cursor.execute("SELECT * FROM tasks WHERE id = ?", (task_id,))
task = cursor.fetchone()
if not task:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Task with id {task_id} not found"
)
return {
"id": task[0],
"title": task[1],
"description": task[2],
"due_date": task[3],
"completed": task[4],
"created_at": task[5]
}
@app.post("/tasks", response_model=TaskResponse, status_code=status.HTTP_201_CREATED)
def create_task(task: TaskCreate):
db = get_db()
cursor = db.cursor()
# Additional business validation: check if title already exists
cursor.execute("SELECT id FROM tasks WHERE title = ?", (task.title,))
if cursor.fetchone():
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail=f"A task with title '{task.title}' already exists"
)
cursor.execute(
"INSERT INTO tasks (title, description, due_date, completed) VALUES (?, ?, ?, ?)",
(task.title, task.description, task.due_date, task.completed)
)
db.commit()
new_id = cursor.lastrowid
# Fetch and return the created task
cursor.execute("SELECT * FROM tasks WHERE id = ?", (new_id,))
new_task = cursor.fetchone()
return {
"id": new_task[0],
"title": new_task[1],
"description": new_task[2],
"due_date": new_task[3],
"completed": new_task[4],
"created_at": new_task[5]
}
Step-by-Step Explanation
- Step 1: We import
HTTPExceptionandstatusfrom FastAPI. - Step 2: In the
get_taskendpoint, we check if the task exists. If not, we raise a 404 error with a clear message. - Step 3: In the
create_taskendpoint, we add business logic validation: we check if a task with the same title already exists. If it does, we raise a 409 Conflict error. - Step 4: FastAPI's built-in validation (from Pydantic) handles type and length checks automatically. If a user sends an invalid title, FastAPI returns a 422 error before our code even runs.
Common Mistakes Beginners Make
- Not validating input: Trusting user input leads to bugs and security issues. Always validate.
- Returning generic errors: A simple "Error" message is not helpful. Always include details.
- Forgetting to handle missing resources: Every endpoint that fetches a specific item should check if it exists.
- Using wrong HTTP status codes: Use 404 for not found, 409 for conflicts, 400 for bad requests, and 422 for validation errors.
Practice Task
Your Turn: Add validation and error handling to the update_task and delete_task endpoints in your project.
- For
update_task: Check if the task exists (404 if not). Validate that the title (if provided) is at least 3 characters. Check for duplicate titles if the title is being changed. - For
delete_task: Check if the task exists (404 if not). Return a success message after deletion.
Test your endpoints using Swagger UI at /docs. Try sending invalid data and see the error responses.
Summary
Validation and error handling are essential for building reliable APIs. FastAPI gives you powerful tools out of the box: Pydantic for data validation and HTTPException for custom error responses. By combining these, you ensure your API is robust, user-friendly, and secure. In the next lesson, we'll learn how to test our API thoroughly using Swagger and curl.
Loading ratings...