Skip to main content

Defining Path Operations

In this codebase, path operations are the core building blocks of an API. They define how the application responds to specific HTTP methods at specific URL paths. This is primarily achieved using the APIRouter class and its associated decorators.

The Role of APIRouter

The APIRouter class (found in fastapi/routing.py) is used to group path operations. It allows you to organize your application into multiple files and modules, which can then be included in the main FastAPI application.

When you define a path operation on a router, you are essentially registering a function (the "endpoint") to be called when a request matches a specific path and HTTP method.

from fastapi import APIRouter

router = APIRouter()

@router.get("/items/")
async def read_items():
return [{"name": "Item 1"}, {"name": "Item 2"}]

HTTP Method Decorators

APIRouter provides several decorator methods that correspond to standard HTTP methods. Each of these methods internally calls self.api_route, which eventually creates an instance of APIRoute.

Available decorators include:

  • @router.get()
  • @router.post()
  • @router.put()
  • @router.delete()
  • @router.patch()
  • @router.options()
  • @router.head()
  • @router.trace()

Configuration Parameters

These decorators accept a wide range of parameters to configure the behavior and documentation of the path operation:

  • path: The URL path (e.g., "/items/{item_id}").
  • status_code: The default HTTP status code for the response (e.g., 201 for creation).
  • response_model: A Pydantic model or type used for response serialization, validation, and OpenAPI schema generation.
  • tags: A list of strings used to group operations in the generated OpenAPI UI.
  • dependencies: A list of dependencies (using Depends()) that must be executed before the endpoint.

Configuring Responses

The way an endpoint returns data is controlled by response_model, status_code, and response_class.

Status Codes

You can define the default status code for a successful response. If the status code is one that should not have a body (like 204 No Content), APIRoute ensures this is enforced.

@router.post("/items/", status_code=201)
async def create_item(name: str):
return {"name": name}

Response Models

The response_model parameter is critical for data integrity. When provided, APIRoute creates a response_field (a Pydantic ModelField) to:

  1. Validate the data returned by your function.
  2. Serialize the data into JSON.
  3. Filter the data based on the model definition (e.g., excluding internal fields).
from pydantic import BaseModel

class Item(BaseModel):
name: str
description: str | None = None

@router.get("/items/{item_id}", response_model=Item)
async def read_item(item_id: str):
# Even if this dict has extra fields, only 'name' and 'description'
# will be returned to the client based on the Item model.
return {"name": "Portal Gun", "id": item_id, "internal_id": "secret"}

Modular Routing with APIRouter

For larger applications, APIRouter supports shared configuration that applies to all routes defined within it. This is demonstrated in docs_src/bigger_applications/app_an_py310/routers/items.py:

router = APIRouter(
prefix="/items",
tags=["items"],
dependencies=[Depends(get_token_header)],
responses={404: {"description": "Not found"}},
)

Path Prefixes

The prefix parameter automatically prepends a string to all paths in the router.

  • Requirement: Prefixes must start with / and must not end with /.
  • Internal Logic: In APIRouter.add_api_route, the final path is calculated as self.prefix + path.

Shared Dependencies and Tags

Dependencies and tags defined at the APIRouter level are merged with those defined at the individual path operation level. In APIRouter.add_api_route, the router's tags and dependencies are copied and extended with the specific route's configuration before being passed to the APIRoute constructor.

Internal Implementation: APIRoute

While APIRouter is the interface for developers, APIRoute is the internal class that manages the execution logic.

When an APIRoute is initialized:

  1. It compiles the path into a regex for matching.
  2. It extracts the endpoint's docstring to use as a default description.
  3. It creates a dependant object (via get_dependant) to manage dependency injection for that specific endpoint.
  4. It determines the response_class. If not explicitly provided, it defaults to JSONResponse.

This separation allows APIRouter to act as a factory, while APIRoute handles the complexities of request/response processing for a single path.