alltools.one
API
2025-06-30
9 min
alltools.one Team
JSONAPIRESTDesign PatternsBackend

Паттерны проектирования JSON API: создание лучших REST API

Хорошо спроектированный API — это удовольствие в использовании. Плохо спроектированный создаёт баги, раздражение и технический долг. Это руководство описывает проверенные временем паттерны для проектирования JSON REST API, которые являются согласованными, легко обнаруживаемыми и поддерживаемыми.

Именование ресурсов

Ресурсы — это существительные, а не глаголы. Используйте существительные во множественном числе для коллекций и отдельные ресурсы через ID:

GET    /api/users          # Список пользователей
POST   /api/users          # Создать пользователя
GET    /api/users/123      # Получить пользователя 123
PUT    /api/users/123      # Обновить пользователя 123
DELETE /api/users/123      # Удалить пользователя 123

Соглашения об именовании:

  • Используйте строчные буквы с дефисами: /api/blog-posts (не blogPosts и не blog_posts)
  • Вкладывайте связанные ресурсы: /api/users/123/orders
  • Ограничивайте вложенность 2 уровнями: /api/users/123/orders/456 (не глубже)
  • Избегайте глаголов в URL: /api/users/123/activate допустимо для действий, не соответствующих CRUD

Обёртка ответа

Оборачивайте ответы в единообразную обёртку:

{
  "data": {
    "id": "123",
    "type": "user",
    "attributes": {
      "name": "Alice",
      "email": "alice@example.com",
      "createdAt": "2024-01-15T10:30:00Z"
    }
  },
  "meta": {
    "requestId": "req_abc123"
  }
}

Для коллекций:

{
  "data": [
    { "id": "123", "name": "Alice" },
    { "id": "456", "name": "Bob" }
  ],
  "meta": {
    "total": 142,
    "page": 1,
    "perPage": 20
  }
}

Валидируйте ответы вашего API с помощью нашего JSON валидатора, чтобы убедиться в соответствии схеме.

Пагинация

Три распространённых подхода:

На основе смещения (простейший)

GET /api/users?page=2&per_page=20
{
  "data": [...],
  "meta": {
    "page": 2,
    "perPage": 20,
    "total": 142,
    "totalPages": 8
  }
}

Плюс: Простой, поддерживает переход на любую страницу. Минус: Несогласованные результаты при параллельных вставках/удалениях.

На основе курсора (наиболее надёжный)

GET /api/users?cursor=eyJpZCI6MTIzfQ&limit=20
{
  "data": [...],
  "meta": {
    "hasNext": true,
    "nextCursor": "eyJpZCI6MTQzfQ"
  }
}

Плюс: Согласованный при параллельных изменениях, производительный на больших наборах данных. Минус: Нельзя перейти на произвольную страницу.

На основе набора ключей (наиболее производительный)

GET /api/users?after_id=123&limit=20

Использует ID последнего элемента (или другое сортируемое поле) для получения следующей страницы. Похож на курсорный, но с прозрачными параметрами.

Рекомендация: Используйте курсорный для лент реального времени и больших наборов данных. Используйте на основе смещения для административных панелей, где важна навигация по страницам.

Фильтрация и сортировка

Фильтрация

GET /api/users?status=active&role=admin
GET /api/users?created_after=2024-01-01
GET /api/users?search=alice

Для сложных фильтров рассмотрите выделенный параметр запроса:

GET /api/users?filter[status]=active&filter[role]=admin

Сортировка

GET /api/users?sort=name          # По возрастанию
GET /api/users?sort=-created_at   # По убыванию (префикс -)
GET /api/users?sort=-created_at,name  # Несколько полей

Обработка ошибок

Единообразные ответы об ошибках критичны для удобства использования API:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Request validation failed",
    "details": [
      {
        "field": "email",
        "message": "Must be a valid email address",
        "value": "not-an-email"
      },
      {
        "field": "age",
        "message": "Must be between 0 and 150",
        "value": -5
      }
    ]
  },
  "meta": {
    "requestId": "req_abc123"
  }
}

HTTP-коды состояния:

КодЗначениеКогда
200OKУспешный GET, PUT
201CreatedУспешный POST
204No ContentУспешный DELETE
400Bad RequestОшибки валидации
401UnauthorizedОтсутствующая/невалидная авторизация
403ForbiddenВалидная авторизация, недостаточно прав
404Not FoundРесурс не существует
409ConflictДублирование ресурса, конфликт версий
422UnprocessableСемантически невалидный
429Too Many RequestsПревышен лимит запросов
500Internal ErrorНепредвиденная ошибка сервера

Версионирование

Три подхода:

URL-путь (рекомендуется)

GET /api/v1/users
GET /api/v2/users

Плюс: Явный, простая маршрутизация, простое тестирование.

На основе заголовков

GET /api/users
Accept: application/vnd.myapi.v2+json

Плюс: Чистые URL. Минус: Сложнее тестировать, менее обнаруживаемый.

Параметр запроса

GET /api/users?version=2

Плюс: Легко тестировать. Минус: Семантика необязательного параметра.

Рекомендация: Версионирование через URL-путь — самый практичный выбор. Оно явное, кэшируемое и работает с любым HTTP-инструментом.

Работа с датами и временем

Всегда используйте ISO 8601 в UTC:

{
  "createdAt": "2024-01-15T10:30:00Z",
  "updatedAt": "2024-01-15T14:22:33Z",
  "expiresAt": "2024-12-31T23:59:59Z"
}

Никогда не используйте Unix-временные метки в ответах API — они неоднозначны (секунды или миллисекунды) и не читаемы человеком. Подробнее о работе с временными метками читайте в нашем руководстве по Unix-меткам времени.

Ограничение частоты запросов

Сообщайте лимиты запросов в заголовках ответа:

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 67
X-RateLimit-Reset: 1705312800
Retry-After: 30

Возвращайте 429 Too Many Requests при превышении лимита с чётким сообщением об ошибке и заголовком Retry-After.

HATEOAS (необязательный, но мощный)

Включайте ссылки на связанные ресурсы и действия:

{
  "data": {
    "id": "123",
    "name": "Alice",
    "links": {
      "self": "/api/users/123",
      "orders": "/api/users/123/orders",
      "avatar": "/api/users/123/avatar"
    }
  }
}

Это делает ваш API самодокументируемым и сокращает построение URL на стороне клиента.

Часто задаваемые вопросы

Следует ли использовать спецификацию JSON:API или разработать собственный формат?

Спецификация JSON:API (jsonapi.org) предоставляет всеобъемлющий стандарт, но может быть многословной для простых API. Для большинства проектов проектирование более простого собственного формата по паттернам из этого руководства более практично. Используйте JSON:API, если вам нужны автоматические клиентские библиотеки и строгий стандарт.

Как обрабатывать частичные обновления (PATCH vs PUT)?

Используйте PUT для полной замены ресурса (клиент отправляет все поля). Используйте PATCH для частичных обновлений (клиент отправляет только изменённые поля). PATCH с JSON Merge Patch (RFC 7396) — простейший подход: отправьте JSON-объект только с полями для обновления и null для удаления поля.

Связанные ресурсы

Published on 2025-06-30
JSON API Design Patterns: Building Better REST APIs | alltools.one