Backend Typescript 1.0.0 Help

API-контракты

В этом топике разбираем, как формировать понятные и предсказуемые API-контракты без привязки к фреймворку. Мы пройдёмся по правилам именования endpoint-ов и выбору HTTP-глаголов, разберём пагинацию, батч-запросы, получение сущностей по ID, критерии выбора query - и path-параметров, когда использовать заголовки, как оформлять тело ответа (data-обёртки) и ошибочные ответы (поле _error), а также какие статусы возвращать в разных ситуациях. Завершим кратким введением в OpenAPI и Swagger.

Базовые принципы и глаголы HTTP

  • Ресурсный подход (resource-oriented): endpoint описывает коллекцию или отдельный ресурс. Пример коллекции: /users, элемент коллекции: /users/{id}.

  • Глаголы определяются методом:

    • GET — чтение (без побочных эффектов, идемпотентно, безопасно).

    • POST — создание или небезопасное действие (обычно не идемпотентно).

    • PUT — полная замена ресурса (идемпотентно).

    • PATCH — частичное обновление (может быть идемпотентным по контракту).

    • DELETE — удаление ресурса (идемпотентно: повторное удаление возвращает тот же результат состояния).

  • Именуйте пути существительными во множественном числе: /orders, /orders/{orderId}, а не /getOrders или /createOrder.

  • Гнездите под-ресурсы, когда есть жёстная иерархия: /orders/{orderId}/items.

  • Версионирование: фиксируйте мажорную версию в пути (/v1/...) или через заголовок версии — главное, чтобы было стабильно и прозрачно.

Path vs Query vs Headers: что куда?

  • Path-параметры — часть URI, однозначно идентифицируют ресурс: /users/{id}, /orders/{orderId}/items/{itemId}.

  • Query-параметры — фильтрация, поиск, сортировка, пагинация, не меняют идентичность ресурса: /users?role=admin&sort=createdAt&order=desc.

  • Заголовки — сквозные технические аспекты: авторизация (Authorization), Idempotency-Key, Accept/Content-Type, ключи трассировки, локаль (Accept-Language).

Получение по ID

Классический шаблон: GET /{collection}/{id}. Если ресурс не найден — возвращайте 404. Для выборки нескольких конкретных ID используйте батч (см. ниже) или GET /users?ids=1,2,3 с ограничениями на длину URL.

GET /users/2f7d89a1-9b3c-4f1f-86f1-6fb8d3f2c012 HTTP/1.1 Accept: application/json
{ "id": "2f7d89a1-9b3c-4f1f-86f1-6fb8d3f2c012", "email": "alice@example.com", "role": "admin", "createdAt": "2025-09-20T12:34:56Z" }

Пагинация: варианты и контракт

Есть три распространённых подхода: page/size, limit/offset, cursor. Выбирайте осознанно.

  • page/size — удобно для UI, но плохо при вставках/удалениях: элементы могут «прыгать» между страницами.

  • limit/offset — просто реализовать, но на больших оффсетах неэффективно в БД и подвержено сдвигу данных.

  • cursor — надёжно и эффективно для бесконечных списков: сервер возвращает nextCursor и/или prevCursor.

GET /orders?limit=20&cursor=eyJpZCI6IjEyMyIsImNyZWF0ZWRBdCI6IjIwMjUtMDktMjRUMTI6MDA6MDBaIn0%3D Accept: application/json
{ "data": [ { "id": "124", "total": 1200, "createdAt": "2025-09-24T12:10:00Z" } ], "meta": { "limit": 20, "nextCursor": "eyJpZCI6IjEyNCIsImNyZWF0ZWRBdCI6IjIwMjUtMDktMjRUMTI6MTA6MDBaIn0=", "hasMore": true } }

Батч-запросы (batch)

Батч уменьшает сетевые издержки и лимиты RPS, позволяя за один HTTP-запрос обработать множество однородных операций. Два подхода:

  • Однотипный батч: POST /users:batchGet, POST /users:batchUpdate.

  • Смешанный батч: POST /batch с описанием разных операций (сложнее валидировать и трассировать).

POST /users:batchGet HTTP/1.1 Content-Type: application/json
{ "ids": [ "a1", "a2", "a3" ] }
{ "data": [ { "id": "a1", "email": "a1@example.com" }, { "id": "a2", "email": "a2@example.com" } ], "_error": [ { "id": "a3", "code": "NOT_FOUND", "message": "User not found" } ] }

Когда использовать заголовки

  • Аутентификация/Авторизация: Authorization: Bearer ....

  • Идемпотентность: Idempotency-Key для безопасного повтора POST.

  • Контент-негациация: Accept, Content-Type, Accept-Encoding.

  • Трассировка: корреляционные ID (X-Request-Id).

  • Локализация: Accept-Language.

Data-обёртка и метаданные ответа

Единая структура ответа облегчает жизнь клиентам. Распространённый шаблон:

{ "data": { "id": "u1", "email": "alice@example.com" }, "meta": { "requestId": "f0e1...", "traceId": "9ab2..." } }
  • data — полезная нагрузка (объект или массив).

  • meta — технические метаданные: пагинация, трассировка, статистика.

Структура ошибок и поле _error

Ошибки должны быть машинно-парсабельными и человекочитаемыми. Если вы приняли формат с полем _error, делайте его единообразным:

{ "_error": { "code": "VALIDATION_FAILED", "message": "Field \"email\" is invalid", "details": [ { "field": "email", "rule": "email" } ], "requestId": "f0e1..." } }
  • code — короткий машинный идентификатор.

  • message — безопасное описание для пользователя/разработчика.

  • details — массив конкретных нарушений.

  • requestId — для поддержки и трассировки.

Какие статус-коды когда использовать

  • 200 OK — успешное чтение/операция с телом ответа.

  • 201 Created — создан ресурс; добавьте Location на новый ресурс.

  • 202 Accepted — операция принята асинхронно (проверьте status по отдельному ресурсу).

  • 204 No Content — успех без тела (например, DELETE).

  • 206 Partial Content/207 Multi-Status — частичный результат (стриминг/батч).

  • 304 Not Modified — при условных запросах (If-None-Match, If-Modified-Since).

  • 400 Bad Request — ошибка запроса (валидация, синтаксис).

  • 401 Unauthorized — нет/невалидная авторизация.

  • 403 Forbidden — прав не хватает.

  • 404 Not Found — ресурс не найден.

  • 405 Method Not Allowed — метод не поддержан для ресурса.

  • 409 Conflict — конфликт версий, уникальности, состояния.

  • 410 Gone — ресурс удалён и не вернётся.

  • 412 Precondition Failed — нарушены условия (If-Match и т.п.).

  • 415 Unsupported Media Type — неверный Content-Type.

  • 422 Unprocessable Entity — корректный JSON, но семантическая ошибка домена.

  • 429 Too Many Requests — превышен лимит; добавьте Retry-After.

  • 500 Internal Server Error — внутренняя ошибка сервера.

  • 503 Service Unavailable — перегрузка/техработы; добавьте Retry-After.

Фильтрация, сортировка, поля ответа (query-параметры)

Частые параметры:

  • filter[<field>]=... — фильтры (можно несколько).

  • sort=createdAt и order=asc|desc — сортировка.

  • fields=... — проекция полей (экономит трафик).

  • include=... — инклюды связанных представлений, если нужно.

GET /users?filter[role]=admin&fields=id,email,createdAt&sort=createdAt&order=desc Accept: application/json

Идемпотентность и повтор запросов

Для небезопасных операций POST поддерживайте Idempotency-Key в заголовке: сервер обязан обеспечить, что повтор того же ключа не создаст дубликат.

POST /payments HTTP/1.1 Content-Type: application/json Idempotency-Key: 5b2f1b1e-b7f3-4a0a-9a9f-0f0a3c9e11aa

Примеры хороших и плохих путей

  • Хорошо: /v1/users, /v1/users/{id}, /v1/users/{id}/roles.

  • Плохо: /v1/getUsers, /v1/createUser, /v1/users/deleteById?id=....

OpenAPI и Swagger

OpenAPI — язык описания API (обычно YAML/JSON), позволяющий формализовать пути, параметры, схемы, ответы и ошибки. Swagger UI визуализирует и позволяет «пощёлкать» по спецификации, а Swagger Codegen/OpenAPI Generator — генерировать клиенты/стабы.

openapi: 3.0.3 info: title: Users API version: 1.0.0 servers: - url: https://api.example.com/v1 paths: /users: get: summary: List users parameters: - in: query name: limit schema: { type: integer, minimum: 1, maximum: 100 } - in: query name: cursor schema: { type: string } responses: "200": description: OK content: application/json: schema: $ref: "#/components/schemas/UserListResponse" post: summary: Create user requestBody: required: true content: application/json: schema: { $ref: "#/components/schemas/UserCreateRequest" } responses: "201": description: Created headers: Location: schema: { type: string } content: application/json: schema: $ref: "#/components/schemas/UserResponse" /users/{id}: get: summary: Get user by ID parameters: - in: path name: id required: true schema: { type: string, format: uuid } responses: "200": description: OK content: application/json: schema: $ref: "#/components/schemas/UserResponse" "404": description: Not found content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" components: schemas: User: type: object required: [ id, email, createdAt ] properties: id: { type: string, format: uuid } email: { type: string, format: email } role: { type: string, enum: [ user, admin ] } createdAt: { type: string, format: date-time } UserResponse: type: object properties: data: $ref: "#/components/schemas/User" meta: type: object additionalProperties: true UserListResponse: type: object properties: data: type: array items: { $ref: "#/components/schemas/User" } meta: type: object properties: limit: { type: integer } nextCursor: { type: string, nullable: true } hasMore: { type: boolean } UserCreateRequest: type: object required: [ email ] properties: email: { type: string, format: email } role: { type: string } ErrorResponse: type: object properties: _error: type: object required: [ code, message ] properties: code: { type: string } message: { type: string } details: type: array items: type: object properties: field: { type: string } rule: { type: string }

Практические рекомендации

  • Единый стиль путей и параметров по всему API — зафиксируйте в гайдлайне.

  • Всегда документируйте ответ с ошибкой. Клиенты должны знать структуру _error и набор code.

  • Добавляйте примеры (example/examples) в OpenAPI — это ускорит интеграцию студентов и новичков.

  • Покрывайте контракты контрактными тестами (consumer-driven, например, Pact).

  • Фиксируйте ограничения: максимальные limit, поддерживаемые поля фильтров, сортировки, размеры батча.

Last modified: 01 October 2025