Когда браузер или мобильное приложение делает 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 и обработка ошибок — глобальная обработка исключений.