Skip to content

API Contract Guide

Claude edited this page Sep 18, 2026 · 1 revision

🔌 API-контракт: как backend и frontend договариваются

Эта страница — для тех, кто никогда раньше не работал с API. Если вы уже писали fetch() или requests.get() и понимаете, что такое JSON и HTTP-статусы — можете сразу перейти к разделу «Как задокументировать один эндпоинт».

Если нет — читайте по порядку, здесь всё объясняется с нуля.


🤔 Что такое API вообще

API (Application Programming Interface) в контексте этого проекта — это просто способ, которым страница в браузере (frontend) «разговаривает» с программой на сервере (backend), чтобы получить или сохранить данные.

Разговор устроен так:

  1. Frontend отправляет запрос (request) на определённый адрес backend'а. Например: «дай мне список оценок студента с id=5».
  2. Backend получает этот запрос, лезет в базу данных, находит нужное, и отправляет ответ (response).
  3. Frontend получает ответ и рисует его на странице (таблицу, список, сообщение об ошибке — что угодно).

Технически это HTTP-запрос — то же самое, что происходит, когда браузер открывает обычную веб-страницу, только вместо HTML в ответ обычно приходит JSON (текстовый формат для передачи структурированных данных, похожий на Python-словарь):

{
  "id": 5,
  "name": "Иванов Иван",
  "grades": [4, 5, 3]
}

В нашем проекте это выглядит конкретно так: JS-код на странице (frontend/<модуль>/...) делает fetch("/api/v1/<модуль>/..."), а на другом конце этот запрос принимает Python-код (modules/<модуль>/router.py), который вы (если вы в backend-команде) и пишете.

У каждого запроса есть:

  • Метод — что мы хотим сделать: GET (получить данные), POST (создать что-то новое), PUT/PATCH (изменить существующее), DELETE (удалить).
  • Путь (path) — адрес, например /api/v1/gradebook/students/5/grades.
  • Иногда — тело запроса (request body) — данные, которые frontend отправляет backend'у (например, при POST: «вот новая оценка, сохрани её»).
  • Статус ответа — число вроде 200 (всё хорошо), 201 (успешно создано), 404 (не найдено), 422 (данные не прошли проверку), 500 (ошибка на сервере).
  • Тело ответа (response body) — данные, которые backend возвращает.

🧩 Почему этого объяснения недостаточно и нужен «контракт»

Мало того, что backend и frontend вообще «разговаривают» через API — они должны заранее договориться, как именно выглядит этот разговор, потому что каждая сторона пишет код независимо, глядя только на свою половину.

Представим (чисто гипотетически, не про реальный модуль) — backend-команда решила, что для получения оценок студента нужен запрос:

GET /api/v1/example/items?student_id=5

— то есть id передаётся как query-параметр (после ?). А frontend-команда, ничего не согласовав, ожидала:

GET /api/v1/example/items/5

— то есть id передаётся как часть пути. Это два совершенно разных способа, и backend, реализовавший первый вариант, просто не будет отвечать на запросы, которые шлёт frontend — оба будут получать 404 Not Found, хотя каждая команда по отдельности всё сделала «правильно».

Второй частый случай — форма ответа. Backend может вернуть просто список:

[{"id": 1, "text": "..."}, {"id": 2, "text": "..."}]

а frontend-код написан в расчёте на то, что список лежит внутри объекта:

{"items": [{"id": 1, "text": "..."}, {"id": 2, "text": "..."}]}

Код, который делает data.items.forEach(...), упадёт с ошибкой на первом варианте, а код, который делает data.forEach(...), упадёт на втором. И то, и другое — совершенно нормальный, рабочий код, если бы стороны знали заранее, что именно к ним прилетит.

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

Именно поэтому в этом проекте у backend- и frontend-команды одного модуля — отдельные файлы ТЗ (specification-backend.md и specification-frontend.md, см. Getting Started): раз файлы разные, никто не может «случайно» скопировать раздел про API из чужого файла — обе команды обязаны реально сверить, что они там написали, и именно это сверение и есть работа с контрактом.


🛠 Что должен задокументировать backend и когда

К концу Модуля 1 (см. Систему оценивания) backend-команда должна в своём specification-backend.md, в разделе «4.1. API Endpoints», расписать каждый эндпоинт, который будет нужен frontend'у. Для каждого эндпоинта нужно зафиксировать:

  1. Метод и путь — например, GET /api/v1/gradebook/students/{student_id}/grades.
  2. Параметры пути и запроса — что и в каком формате передаётся (path-параметр в фигурных скобках, query-параметры, тело запроса для POST/PUT).
  3. Тело запроса (для POST/PUT/PATCH) — какие поля, какого типа, какие обязательны.
  4. Тело успешного ответа — точная структура JSON, с примером.
  5. Статус успешного ответа — 200 для GET, обычно 201 для POST, создающего запись.
  6. Возможные ошибки — какой статус и какое тело ответа приходит, если что-то пошло не так (не найдено — 404, невалидные данные — 422, и т.д.).

Не нужно уметь всё это писать «по ГОСТу» — просто нужно, чтобы после прочтения этого раздела frontend-команда могла написать fetch()-запрос и знать заранее, что она получит в ответ, не дожидаясь, пока backend реально это напишет.

Backend пишет этот раздел первым, ориентируясь на функциональные требования модуля (что должна уметь делать система) — а не наоборот. Сначала — что нужно фронтенду для экранов модуля (это как раз стоит обсудить с frontend-командой лично, до того как писать раздел), потом — как это оформить в виде эндпоинтов.


🎨 Что нужно frontend-команде и на каком этапе

Frontend-команде контракт нужен к началу Модуля 2 — то есть к моменту, когда обе команды переходят от «писать ТЗ» к «писать код». Если контракт согласован раньше — тем лучше, можно начинать раньше.

Что именно нужно frontend'у из раздела backend'а «4.1. API Endpoints»:

  • Список всех эндпоинтов, которые понадобятся для экранов модуля (форма ввода оценки — значит, нужен POST; таблица оценок — значит, нужен GET, и т.д.).
  • Точный формат ответа каждого GET-эндпоинта — чтобы уже сейчас написать код отрисовки, а не переписывать его потом, когда backend «на самом деле» пришлёт другую структуру.
  • Формат ошибок — чтобы показать пользователю понятное сообщение, а не «упасть» молча, если, например, студент с таким id не найден.

Если backend ещё физически не готов (эндпоинт ещё не реализован, но уже описан в specification-backend.md) — frontend всё равно может начинать писать код: временно замените реальный fetch() захардкоженным объектом с той же структурой, что описана в контракте (это называется «заглушка», mock), и как только backend выложит рабочий эндпоинт — просто уберите заглушку. Так обе команды могут работать параллельно, а не ждать друг друга.

Если во время работы выясняется, что контракт нужно поменять (frontend'у не хватает какого-то поля, или неудобно передавать id так, как решил backend) — это не повод молча поменять код на своей стороне и понадеяться, что «как-нибудь сойдётся». Договоритесь заново, обновите оба файла ТЗ (у backend — что реально отдаёт эндпоинт, у frontend — что он теперь ожидает), и только после этого меняйте код. Несовпадение, обнаруженное во время сверки, а не во время защиты — это нормальная рабочая ситуация, а не провал.


📋 Как задокументировать один эндпоинт (шаблон)

Ниже — пример того, как должна выглядеть запись про один эндпоинт в разделе «4.1. API Endpoints» вашего specification-backend.md. Это реальный, рабочий эндпоинт из готового примера в репозитории — docs/examples/example_module/ (backend) и frontend/_example/ (frontend, обращается именно к нему). Запустите проект и откройте http://127.0.0.1:8000/frontend/_example/, чтобы увидеть его вживую, или посмотрите код обоих файлов напрямую.

GET /api/v1/example/items

Назначение: получить список всех записей.

Параметры: нет.

Успешный ответ — 200 OK:

[
  {
    "id": 1,
    "text": "Первая запись",
    "created_at": "2026-09-18T10:00:00"
  },
  {
    "id": 2,
    "text": "Вторая запись",
    "created_at": "2026-09-18T10:05:00"
  }
]

(обратите внимание: ответ — «голый» массив, а не объект вида {"items": [...]}. Это тоже часть контракта — если бы это было не так, JS-код на странице был бы написан иначе.)


GET /api/v1/example/items/{item_id}

Назначение: получить одну запись по id.

Параметры пути: item_id (целое число) — id записи.

Успешный ответ — 200 OK:

{
  "id": 1,
  "text": "Первая запись",
  "created_at": "2026-09-18T10:00:00"
}

Ошибка — 404 Not Found (записи с таким id нет):

{
  "detail": "Не найдено"
}

POST /api/v1/example/items

Назначение: создать новую запись.

Тело запроса:

{
  "text": "Новая запись"
}
Поле Тип Обязательно Описание
text строка да Содержимое записи

Успешный ответ — 201 Created:

{
  "id": 3,
  "text": "Новая запись",
  "created_at": "2026-09-18T10:10:00"
}

Ошибка — 422 Unprocessable Entity (например, text не передан):

FastAPI формирует такое тело ответа автоматически — просто знайте, что оно будет выглядеть примерно так и что нужно проверять response.ok на frontend, а не считать, что запрос всегда успешен.


Скопируйте эту структуру (метод + путь → назначение → параметры → успешный ответ с примером JSON → возможные ошибки) для каждого реального эндпоинта вашего модуля.


✅ Чек-лист сверки контракта (перед началом Модуля 2)

Соберитесь вдвоём — по одному представителю от backend- и frontend-команды одного модуля — и пройдитесь по обоим файлам ТЗ вместе:

  • Для каждого действия на экранах frontend (посмотреть список, открыть карточку, сохранить форму, удалить запись и т.д.) в specification-backend.md есть соответствующий эндпоинт.
  • Метод (GET/POST/...) и путь у каждого эндпоинта в обоих файлах ТЗ записаны одинаково, символ в символ.
  • Формат id и других параметров — одинаковый способ передачи (путь или query) в обоих файлах.
  • Структура успешного ответа (какие поля, вложенный объект или голый массив/значение) — одинаковая в обоих файлах.
  • Оговорено, что приходит при ошибке (хотя бы для случая «не найдено» и «невалидные данные»).

Если после этого чек-листа расхождений не осталось — контракт согласован, можно спокойно писать код параллельно, не боясь, что в конце всё придётся переделывать.


📚 См. также

  • 🚀 Начало работы — шаблоны specification-backend.md и specification-frontend.md.
  • 📊 Система оценивания — на каком этапе что должно быть готово.
  • ❓ FAQ — в том числе про CORS и типичные ошибки frontend↔backend.
  • docs/architecture.md и docs/examples/example_module/README.md в репозитории — как всё это устроено на уровне кода.

Clone this wiki locally