Паттерны проектирования 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-коды состояния:
| Код | Значение | Когда |
|---|---|---|
| 200 | OK | Успешный GET, PUT |
| 201 | Created | Успешный POST |
| 204 | No Content | Успешный DELETE |
| 400 | Bad Request | Ошибки валидации |
| 401 | Unauthorized | Отсутствующая/невалидная авторизация |
| 403 | Forbidden | Валидная авторизация, недостаточно прав |
| 404 | Not Found | Ресурс не существует |
| 409 | Conflict | Дублирование ресурса, конфликт версий |
| 422 | Unprocessable | Семантически невалидный |
| 429 | Too Many Requests | Превышен лимит запросов |
| 500 | Internal 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 для удаления поля.
Связанные ресурсы
- JSON форматтер — Форматирование ответов API для удобства чтения
- Руководство по валидации JSON Schema — Валидация данных API с помощью JSON Schema
- JWT токены: объяснение — Защитите ваши API с помощью JSON Web Tokens