FastAPI Routing Basics
Introduction
Routing is one of the fundamental concepts in web development that determines how your application responds to client requests at different URL endpoints. In FastAPI, routing is handled elegantly through Python decorators that connect specific URL paths to functions that handle requests.
In this guide, we'll explore the basics of FastAPI routing, understand how to define routes for different HTTP methods, and learn how to structure your API endpoints logically.
What is Routing in FastAPI?
Routing in FastAPI refers to the mechanism of directing HTTP requests to the appropriate handler function based on the URL path and HTTP method. Each route in FastAPI is defined using a decorator that specifies:
- The URL path
- The HTTP method (GET, POST, PUT, DELETE, etc.)
- The function that handles the request
FastAPI's routing system is built on top of Starlette, providing a clean and intuitive way to define endpoints in your API.
Setting Up a Basic FastAPI Application
Before diving into routing, let's set up a simple FastAPI application:
# Import the FastAPI class
from fastapi import FastAPI
# Create an instance of FastAPI
app = FastAPI()
# Define a simple route
@app.get("/")
async def root():
return {"message": "Hello World"}
In the example above, we've created a route at the path /
that responds to HTTP GET requests. When you visit the root URL of your application, it returns a JSON response with a "Hello World" message.
Basic Route Definition
Routes in FastAPI are defined using decorators that correspond to HTTP methods:
# GET request
@app.get("/items")
async def read_items():
return {"items": ["Item1", "Item2"]}
# POST request
@app.post("/items")
async def create_item(item_name: str):
return {"message": f"Item '{item_name}' created"}
# PUT request
@app.put("/items/{item_id}")
async def update_item(item_id: int, item_name: str):
return {"item_id": item_id, "item_name": item_name}
# DELETE request
@app.delete("/items/{item_id}")
async def delete_item(item_id: int):
return {"message": f"Item {item_id} deleted"}
Path Parameters
Path parameters are parts of the URL that are captured and passed to your function. They're defined using curly braces {}
in the path:
@app.get("/users/{user_id}")
async def read_user(user_id: int):
return {"user_id": user_id}
When you access /users/123
, the user_id
parameter will receive the value 123
as an integer. FastAPI automatically converts and validates the parameter based on the type annotation.
Example: Multiple Path Parameters
You can define multiple path parameters in a single route:
@app.get("/users/{user_id}/items/{item_id}")
async def read_user_item(user_id: int, item_id: int):
return {"user_id": user_id, "item_id": item_id}
Accessing /users/1/items/42
would result in:
{
"user_id": 1,
"item_id": 42
}
Query Parameters
Query parameters are specified after the ?
in a URL. FastAPI automatically parses these parameters from the URL:
@app.get("/items/")
async def read_items(skip: int = 0, limit: int = 10):
return {"skip": skip, "limit": limit}
With this route, you can access:
/items/?skip=20&limit=30
which results in{"skip": 20, "limit": 30}
/items/
which uses the default values and returns{"skip": 0, "limit": 10}
Optional Query Parameters
You can make query parameters optional by setting their default value to None
or using the Optional
type hint:
from typing import Optional
@app.get("/items/")
async def read_items(name: Optional[str] = None):
if name:
return {"message": f"Searching for items with name: {name}"}
return {"message": "Returning all items"}
Combining Path and Query Parameters
You can use both path and query parameters in a single route:
@app.get("/users/{user_id}/items/")
async def read_user_items(
user_id: int, skip: int = 0, limit: int = 10
):
return {"user_id": user_id, "skip": skip, "limit": limit}
Request Body
For methods like POST, PUT, and PATCH, you often need to send data in the request body. FastAPI uses Pydantic models to validate and serialize request bodies:
from pydantic import BaseModel
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tax: Optional[float] = None
@app.post("/items/")
async def create_item(item: Item):
item_dict = item.dict()
if item.tax:
price_with_tax = item.price + item.tax
item_dict.update({"price_with_tax": price_with_tax})
return item_dict
Example request:
{
"name": "Laptop",
"description": "A powerful workstation",
"price": 999.99,
"tax": 180.0
}
Example response:
{
"name": "Laptop",
"description": "A powerful workstation",
"price": 999.99,
"tax": 180.0,
"price_with_tax": 1179.99
}
Practical Example: Building a Simple Todo API
Let's build a simple Todo API to demonstrate routing in action:
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import List, Optional
app = FastAPI()
# Pydantic model for Todo items
class TodoItem(BaseModel):
id: Optional[int] = None
title: str
description: Optional[str] = None
completed: bool = False
# In-memory database
todos = {}
counter = 0
# Create a todo
@app.post("/todos/", response_model=TodoItem)
async def create_todo(todo: TodoItem):
global counter
counter += 1
todo.id = counter
todos[counter] = todo
return todo
# Get all todos
@app.get("/todos/", response_model=List[TodoItem])
async def get_todos():
return list(todos.values())
# Get a specific todo
@app.get("/todos/{todo_id}", response_model=TodoItem)
async def get_todo(todo_id: int):
if todo_id not in todos:
raise HTTPException(status_code=404, detail="Todo not found")
return todos[todo_id]
# Update a todo
@app.put("/todos/{todo_id}", response_model=TodoItem)
async def update_todo(todo_id: int, todo: TodoItem):
if todo_id not in todos:
raise HTTPException(status_code=404, detail="Todo not found")
todo.id = todo_id
todos[todo_id] = todo
return todo
# Delete a todo
@app.delete("/todos/{todo_id}")
async def delete_todo(todo_id: int):
if todo_id not in todos:
raise HTTPException(status_code=404, detail="Todo not found")
del todos[todo_id]
return {"message": "Todo deleted successfully"}
This example demonstrates a RESTful API with CRUD operations (Create, Read, Update, Delete) for Todo items.
Running a FastAPI Application
To run your FastAPI application, save your code to a file (e.g., main.py
) and use Uvicorn:
pip install fastapi uvicorn
uvicorn main:app --reload
Your API will be available at http://127.0.0.1:8000
, and you can access the automatic interactive documentation at http://127.0.0.1:8000/docs
.
Summary
In this guide, we've covered the essentials of FastAPI routing:
- Basic route definition with path decorators (
@app.get()
,@app.post()
, etc.) - Path parameters for capturing values from URLs
- Query parameters for optional values
- Request body handling with Pydantic models
- Practical implementation of a simple Todo API
FastAPI's routing system provides a clean and type-safe way to define your API endpoints, ensuring that your application is both robust and well-documented.
Additional Resources
Exercises
- Create a simple blog API with endpoints for listing, creating, updating, and deleting blog posts.
- Extend the Todo API to include categories for todos and endpoints to filter todos by category.
- Implement pagination for the "get all todos" endpoint using query parameters.
- Add search functionality to the Todo API to find todos by title or description.
- Implement an endpoint that returns statistics about todos (e.g., number of completed vs. incomplete todos).
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)