JSON API Best Practices for Developers

Design better JSON APIs with consistent naming, structured responses, proper pagination, error handling, and performance optimizations used by top engineering teams.

Команда JSONTechFebruary 1, 202510 min read

Почему важен дизайн API

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

Шаблоны в этом руководстве не теоретические — они основаны на API таких компаний, как Stripe, GitHub, Slack и других, известных отличным опытом для разработчиков. Давайте рассмотрим каждый из них.

Конвенции именования

Два доминирующих стиля для имен свойств JSON — это camelCase и snake_case. Оба варианта приемлемы, если вы выберете один и будете использовать его повсеместно.

КонвенцияПримерИспользуетсяЭкосистема
camelCasefirstName, createdAtGoogle APIs, AzureJavaScript / TypeScript фронтенды
snake_casefirst_name, created_atStripe, GitHub, SlackPython / Ruby бэкенды

Рекомендация: Выберите конвенцию, которая соответствует вашей основной экосистеме потребителей. Если ваш API в основном используется JavaScript фронтендами, camelCase избегает трений. Если ваш бэкенд на Python или Ruby и API B2B, snake_case является идиоматичным.

Что бы вы ни выбрали, будьте абсолютно последовательны. Смешивание user_name и firstName в одном ответе разрушает доверие быстрее, чем почти что-либо другое.

Последовательная структура ответа

Каждый ответ от вашего API должен следовать предсказуемой оболочке. Вот шаблон, который работает для большинства API:

Успешный ответ

{
  "data": {
    "id": "usr_abc123",
    "email": "alice@example.com",
    "name": "Алиса Джонсон",
    "created_at": "2025-01-15T09:30:00Z"
  },
  "meta": {
    "request_id": "req_7f2a9c"
  }
}

Ответ со списком

{
  "data": [
    { "id": "usr_abc123", "name": "Алиса Джонсон" },
    { "id": "usr_def456", "name": "Боб Смит" }
  ],
  "meta": {
    "total": 142,
    "page": 1,
    "per_page": 20,
    "request_id": "req_8b3d1e"
  }
}

Ответ с ошибкой

{
  "error": {
    "code": "validation_error",
    "message": "Тело запроса содержит недопустимые поля.",
    "details": [
      {
        "field": "email",
        "message": "Должен быть действительный адрес электронной почты."
      },
      {
        "field": "age",
        "message": "Должен быть не менее 13."
      }
    ]
  },
  "meta": {
    "request_id": "req_4c8e2a"
  }
}

Ключевой принцип: data присутствует при успехе, error присутствует при неудаче, а meta появляется в обоих для отслеживания запросов и пагинации. Потребители могут написать один обработчик ответа, который сначала проверяет error.

Попробуйте сами: Вставьте ответ API в наш JSON Formatter, чтобы проверить структуру и выявить несоответствия.

Шаблоны пагинации

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

1. Пагинация с использованием смещения

Самый простой подход. Клиент указывает номер страницы или смещение:

GET /api/users?page=3&per_page=20

{
  "data": [...],
  "meta": {
    "total": 142,
    "page": 3,
    "per_page": 20,
    "total_pages": 8
  }
}

2. Пагинация с использованием курсора

Вместо номеров страниц сервер возвращает непрозрачный курсор, указывающий на следующий набор результатов:

GET /api/users?limit=20&after=cursor_abc123

{
  "data": [...],
  "meta": {
    "has_more": true,
    "next_cursor": "cursor_def456"
  }
}

3. Пагинация с использованием ключей

Использует реальное значение столбца (например, временную метку или ID) в качестве курсора. Это пагинация с курсором без непрозрачности:

GET /api/users?limit=20&created_after=2025-01-15T09:30:00Z

{
  "data": [...],
  "meta": {
    "has_more": true,
    "next_created_after": "2025-01-16T14:22:00Z"
  }
}

Сравнение пагинации

АспектСмещениеКурсорКлючи
Переход на страницу NДаНетНет
Общее количествоЛегко включитьДорогоДорого
Производительность на больших таблицахУхудшается (OFFSET сканирует строки)ПостояннаяПостоянная
Обработка вставок/удаленийМожет пропустить или дублировать элементыСтабильнаяСтабильная
Сложность реализацииНизкаяСредняяСредняя
ИспользуетсяТрадиционные веб-приложенияStripe, SlackTwitter / X

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

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

Хорошие ответы на ошибки сообщают потребителю что пошло не так, где и, желательно, как это исправить. Вот структура, которую мы рекомендуем:

{
  "error": {
    "code": "rate_limit_exceeded",
    "message": "Вы превысили лимит запросов в 100 запросов в минуту.",
    "details": [],
    "doc_url": "https://docs.example.com/errors/rate-limit"
  }
}

code — это строка, читаемая машиной (не HTTP статус-код — он идет в фактическом HTTP ответе). message — читаемая человеком. Массив details предоставляет ошибки на уровне полей для неудач валидации. А doc_url ссылается на документацию об ошибке.

Распространенные коды ошибок и их соответствия HTTP статусам:

HTTP статусКод ошибкиКогда использовать
400validation_errorТело запроса не проходит валидацию схемы
401unauthorizedОтсутствует или недействительная аутентификация
403forbiddenАутентифицирован, но недостаточно прав
404not_foundРесурс не существует
409conflictДублирующий ресурс или конфликт состояния
422unprocessable_entityДействительный JSON, но семантически неверный
429rate_limit_exceededСлишком много запросов
500internal_errorНеожиданная ошибка сервера

Типы данных и форматирование

Правильное определение типов данных предотвращает целую категорию ошибок интеграции:

Даты и время

Всегда используйте формат ISO 8601 с информацией о временной зоне:

{
  "created_at": "2025-01-15T09:30:00Z",
  "expires_at": "2025-02-15T23:59:59+05:30"
}

Никогда не используйте Unix временные метки в телах ответов — они не читаемы для человека и создают неоднозначность временной зоны. Если вам нужны Unix временные метки (например, для JWT утверждений), документируйте это четко.

Большие числа

Тип Number в JavaScript теряет точность за пределами 2^53 - 1 (9,007,199,254,740,991). Если ваши ID или денежные значения превышают это, используйте строки:

{
  "id": "9223372036854775807",
  "amount": "99.99",
  "currency": "USD"
}

Stripe, Twitter и Discord все используют строковые ID именно по этой причине.

Null против отсутствия

Определите четкую конвенцию и документируйте ее:

  • null — Поле существует, но не имеет значения (например, пользователь еще не установил биографию).
  • Отсутствующий ключ — Поле не имеет отношения к этому ресурсу или не было запрошено (например, в частичном ответе).

Многие API включают поля null, чтобы поддерживать согласованную структуру, что облегчает клиентам написание типизированных интерфейсов без использования опциональной цепочки повсюду.

Булевы значения

Используйте настоящие булевы значения JSON, а не строки или целые числа:

// Хорошо
{ "is_active": true }

// Плохо
{ "is_active": "true" }
{ "is_active": 1 }

Стратегии версионирования

API развиваются. Вам нужна стратегия для внесения изменений без нарушения работы существующих потребителей. Три основных подхода:

  • Версионирование URL: /v1/users, /v2/users. Самый распространенный и самый заметный подход. Используется Stripe, GitHub и Twilio. Легко понять, легко маршрутизировать, но может привести к дублированию кода.
  • Версионирование заголовков: Accept: application/vnd.api+json;version=2. Более чистые URL, но менее доступные. Используется GitHub (в качестве альтернативы) и Azure.
  • Параметр запроса: /users?version=2. Простой, но загрязняет строку запроса. Менее распространен на практике.

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

Сжатие

JSON — это текст и отлично сжимается. Всегда включайте сжатие для ответов API:

АлгоритмКоэффициент сжатияСкоростьПоддержка браузерами
gzipХорошо (70–80% сокращение)БыстроУниверсальная
Brotli (br)Лучше (75–85% сокращение)Немного медленнее при сжатииВсе современные браузеры

Для типичного JSON ответа размером 50KB, gzip уменьшает его до примерно 10–15KB, а Brotli до примерно 8–12KB. Сервер должен уважать заголовок Accept-Encoding и выбирать соответственно. Большинство обратных прокси (Nginx, Cloudflare) обрабатывают это автоматически.

Советы по производительности

Частичные ответы (Выбор полей)

Позвольте потребителям запрашивать только те поля, которые им нужны. Это уменьшает размер полезной нагрузки и нагрузку на базу данных:

GET /api/users?fields=id,name,email

{
  "data": [
    { "id": "usr_abc123", "name": "Алиса", "email": "alice@example.com" }
  ]
}

Google APIs называют это "частичными ответами" и поддерживают это на всех конечных точках. Это особенно ценно для мобильных клиентов на медленных соединениях.

Редкие наборы полей

Для API, которые возвращают связанные ресурсы, позвольте потребителям контролировать, какие поля включены для каждого типа ресурса:

GET /api/articles?include=author&fields[article]=title,body&fields[author]=name

Это шаблон из спецификации JSON:API. Он устраняет проблему N+1 запросов на клиенте, сохраняя при этом минимальные полезные нагрузки.

ETags и условные запросы

Возвращайте заголовок ETag с ответами. Клиенты могут отправлять If-None-Match в последующих запросах, и вы возвращаете 304 Not Modified (пустое тело), если ничего не изменилось. Это экономит пропускную способность и время обработки для ресурсов, которые не часто меняются.

Контрольный список резюме

Перед отправкой вашего API пройдите через этот контрольный список:

  • Последовательная конвенция именования (camelCase или snake_case) на всех конечных точках.
  • Предсказуемая оболочка ответа с data, error и meta.
  • Пагинация на всех конечных точках списка с документированными ограничениями.
  • Структурированные ответы на ошибки с машинно-читаемыми кодами, сообщениями и деталями на уровне полей.
  • Даты ISO 8601, строковые ID для больших чисел, настоящие булевы значения.
  • Стратегия версионирования API задокументирована и соблюдается.
  • Включено сжатие gzip или Brotli.
  • Выбор полей или редкие наборы полей для потребителей, чувствительных к производительности.

Попробуйте сами: Отформатируйте и проверьте свои ответы API с помощью нашего JSON Formatter, чтобы убедиться, что они следуют чистой, последовательной структуре.

Похожие инструменты