Маршрутизация, теги и APIRouter

APIRouter — это переносной «мини-роутер», позволяющий разбить приложение на модули и собрать их в одно приложение через include_router с общим префиксом и тегами.

По мере роста проекта весь код не должен жить в одном файле. APIRouter группирует связанные маршруты (например, всё про пользователей) и подключается к приложению одной строкой.

Когда эндпоинтов становится десятки, держать их в одном main.py невыносимо. FastAPI предлагает разрезать API на логические модули — по ресурсам или доменам. Каждый модуль создаёт свой APIRouter, регистрирует на нём маршруты теми же декораторами, а главный app подключает роутеры. Это даёт изоляцию, переиспользование и аккуратную документацию, сгруппированную по тегам.

# users.py
from fastapi import APIRouter

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

@router.get("/")
async def list_users():
    return [{"id": 1}, {"id": 2}]

@router.get("/{user_id}")
async def get_user(user_id: int):
    return {"id": user_id}

# main.py
from fastapi import FastAPI
from users import router as users_router

app = FastAPI()
app.include_router(users_router)

Префикс /users добавляется ко всем маршрутам роутера, поэтому @router.get("/") станет GET /users/. Тег users сгруппирует эти эндпоинты в одну секцию документации. Можно также навешивать на весь роутер общие зависимости (об этом — в разделе про зависимости).

Как работает под капотом

include_router не делает ничего волшебного: он берёт маршруты роутера, приклеивает к каждому пути префикс, добавляет общие теги и зависимости и регистрирует получившиеся маршруты в таблице приложения. По сути это слияние двух таблиц маршрутов с трансформацией путей. Смоделируем на stdlib:

class Router:
    def __init__(self, prefix="", tags=None):
        self.prefix = prefix
        self.tags = tags or []
        self.routes = []
    def get(self, path):
        def deco(fn):
            self.routes.append(("GET", path, fn))
            return fn
        return deco

app_routes = []

def include_router(router):
    for method, path, fn in router.routes:
        full = router.prefix + path        # приклеиваем префикс
        app_routes.append((method, full, router.tags, fn))

users = Router(prefix="/users", tags=["users"])

@users.get("/")
def list_users():
    return ["u1", "u2"]

@users.get("/{user_id}")
def get_user():
    return "user"

include_router(users)
for method, path, tags, fn in app_routes:
    print(method, path, "| tags:", tags)

Попробуй сам ▶ Видно, как префикс /users приклеился к каждому пути при подключении роутера.

Частые ошибки

Первая — задавать префикс с завершающим слешем (prefix="/users/"), получая двойные слеши в путях. Вторая — дублировать префикс и в роутере, и в путях маршрутов. Третья — городить циклические импорты, когда модули роутеров импортируют друг друга; лучше держать зависимости направленными. Четвёртая — забывать про теги, из-за чего документация превращается в одну плоскую свалку эндпоинтов.

Best practices

  • Один роутер на ресурс/домен (users, orders, auth).
  • Префикс — без завершающего слеша; пути внутри начинаются с /.
  • Назначайте теги — документация станет читаемой и навигируемой.
  • Общие зависимости (авторизация) навешивайте на уровне роутера, а не повторяйте в каждом обработчике.

Версионирование API через роутеры

Роутеры отлично решают задачу версионирования. Когда контракт API меняется несовместимо, нельзя ломать старых клиентов — заводят новую версию пути, обычно /api/v1 и /api/v2. С APIRouter это делается естественно: каждая версия — свой набор роутеров со своим префиксом, подключаемый к приложению независимо. Старые и новые эндпоинты сосуществуют, а общую логику выносят в зависимости, чтобы не дублировать. Тот же приём помогает разделять публичное и внутреннее API, накладывая на внутренний роутер дополнительные зависимости авторизации. Главная мысль: роутер — это не просто способ разбить файлы, а единица композиции, на уровне которой удобно навешивать префиксы, теги, зависимости и версии. Чем раньше вы начнёте мыслить роутерами, тем легче проект будет масштабироваться без болезненных переписываний структуры.

Итог: APIRouter — модульная единица маршрутов; include_router приклеивает префикс, добавляет теги и общие зависимости и сливает маршруты в приложение. Это основа масштабируемой структуры проекта.

Проверьте себя
1. Что делает include_router(router) с маршрутами роутера?
AЗапускает их немедленно
BПриклеивает к путям префикс роутера, добавляет теги/зависимости и регистрирует маршруты в приложении
CСоздаёт отдельный процесс на каждый маршрут
DУдаляет дубликаты эндпоинтов
2. Зачем задавать tags при создании APIRouter?
AЧтобы ускорить запросы
BЧтобы маршруты работали
CЧтобы сгруппировать эндпоинты в логические секции автодокументации
DЧтобы включить кэширование