← назад к разделу

Когда браузер или мобильное приложение делает HTTP-запрос, сервер должен понять: какую функцию вызвать и какие данные из запроса ей передать. Это и есть роутинг. В FastAPI он устроен так, что большую часть работы фреймворк делает за вас — по сигнатуре функции.

Как объявить маршрут

В Flask или обычном веб-сервере маршрут — это строка, к которой отдельно привязывают функцию. В FastAPI они объединены: декоратор на функции сразу задаёт и метод, и путь.

from fastapi import FastAPI

app = FastAPI()

@app.get("/hello")
async def say_hello():
    return {"message": "Hello"}

Такой эндпоинт в FastAPI называется path operation: path — путь (/hello), operation — HTTP-метод (GET). Есть декораторы для всех стандартных методов: @app.get, @app.post, @app.put, @app.patch, @app.delete.

APIRouter — порядок в большом API

Если все маршруты описывать прямо в app, файл быстро разрастается. APIRouter позволяет разбить API на модули и собрать их в одном месте.

# products/router.py
from fastapi import APIRouter

router = APIRouter(prefix="/products", tags=["products"])

@router.get("/")
async def list_products():
    ...

@router.get("/{product_id}")
async def get_product(product_id: int):
    ...
# main.py
from fastapi import FastAPI
from products.router import router as products_router

app = FastAPI()
app.include_router(products_router)

prefix добавляет общее начало пути ко всем маршрутам роутера — не нужно повторять /products в каждом декораторе. tags группирует маршруты в автоматической документации (/docs).

Откуда берутся параметры функции

Это главная «магия» FastAPI. Фреймворк смотрит на имена и типы аргументов функции и сам решает, откуда их взять:

Параметр пути — имя аргумента совпадает с {переменной} в пути:

@router.get("/{product_id}")
async def get_product(product_id: int):
    # product_id берётся из URL: /products/42 → product_id = 42
    ...

FastAPI не просто достаёт строку из URL — он сразу преобразует её в нужный тип и валидирует. Если передать /products/abc, а тип int — клиент получит понятную ошибку до входа в функцию.

Параметр запроса — простой тип (str, int, bool), которого нет в пути. Берётся из строки запроса (?key=value):

@router.get("/")
async def list_products(category: str = "all", limit: int = 20):
    # GET /products/?category=electronics&limit=10
    ...

Параметры со значением по умолчанию — необязательные. Без значения — обязательные.

Тело запроса — аргумент с типом-моделью Pydantic. FastAPI читает JSON из тела запроса, разбирает его и передаёт готовый объект:

from pydantic import BaseModel

class CreateProductRequest(BaseModel):
    name: str
    price: float

@router.post("/")
async def create_product(body: CreateProductRequest):
    # body.name, body.price уже провалидированы
    ...

Подробно про модели Pydantic — в статье Pydantic: валидация и сериализация.

Дополнительные ограничения через Query и Path

Иногда нужно не просто получить параметр, но и ограничить его допустимые значения. Для этого используют Query и Path внутри Annotated:

from typing import Annotated
from fastapi import Query, Path

@router.get("/{product_id}")
async def get_product(
    product_id: Annotated[int, Path(ge=1)],
    limit: Annotated[int, Query(ge=1, le=100)] = 20,
):
    ...

ge=1 — «больше или равно 1», le=100 — «меньше или равно 100». FastAPI проверит эти условия и вернёт ошибку, если значение выходит за границы. Это работает до входа в функцию — не нужно писать проверки вручную.

response_model — что отдать клиенту

По умолчанию FastAPI вернёт то, что вернула функция, сериализовав в JSON. Но это не всегда безопасно: внутренняя модель может содержать поля, которые не нужно отдавать наружу (пароли, внутренние идентификаторы).

response_model решает это: FastAPI отфильтрует ответ по указанной модели — лишние поля не попадут в ответ — и опишет схему в документации.

class ProductResponse(BaseModel):
    id: int
    name: str
    price: float

@router.get("/{product_id}", response_model=ProductResponse)
async def get_product(product_id: int):
    return await service.get_by_id(product_id)
    # даже если service вернёт объект с полем internal_code — клиент его не увидит

Статус-коды

По умолчанию успешный ответ возвращается с кодом 200. Для других случаев статус задаётся в декораторе:

@router.post("/", status_code=201)      # создание → 201 Created
async def create_product(body: CreateProductRequest):
    ...

@router.delete("/{product_id}", status_code=204)  # удаление → 204 No Content
async def delete_product(product_id: int):
    ...

Ошибки возвращают через HTTPException:

from fastapi import HTTPException

@router.get("/{product_id}")
async def get_product(product_id: int):
    product = await service.find(product_id)
    if product is None:
        raise HTTPException(status_code=404, detail="Продукт не найден")
    return product

Подробнее об обработке ошибок — в статье Middleware и обработка ошибок.

Коротко

  • Маршрут объявляют декоратором: @router.get("/path"), @router.post("/path") и т.д.
  • APIRouter с prefix и tags разбивает API на модули — каждый домен в своём файле.
  • FastAPI сам определяет источник параметра: совпадает с {переменной} в пути — параметр пути; простой тип вне пути — query-параметр; модель Pydantic — тело запроса.
  • Параметры автоматически преобразуются в нужный тип и валидируются до входа в функцию.
  • Query(ge=1, le=100) и Path(ge=1) добавляют ограничения без ручных проверок.
  • response_model фильтрует ответ — лишние поля не утекают клиенту.
  • Статус-код успеха задают в декораторе (status_code=201), ошибки — через HTTPException.

Что почитать дальше

  • Dependency Injection: Depends — как передавать зависимости в эндпоинты.
  • Pydantic: валидация и сериализация — модели для тела запроса и ответа.
  • Middleware и обработка ошибок — глобальная обработка исключений.