REST API: проєктування
20 питань · ~20 хв · Версія v3.0
Увійдіть, щоб продовжити
Ресурси й методи HTTP, коди статусів, пагінація й фільтри, версіонування, ідемпотентність, контракт і документація - питання всіх рівнів, від junior до lead.
- За спробу
- 20
- У пулі
- 78
- Проходжень
- 0
- Середній бал
- -
- Пройшли на 70%+
- -
Питання для підготовки
36 питаньPOST- створити ресурс (або виконати дію). Сервер сам обирає адресу нового ресурсу:POST /orders→201 CreatedзLocation: /orders/42.PUT- замінити ресурс цілком тим, що в тілі запиту. Поля, яких немає в тілі, вважаються видаленими чи скинутими.PATCH- частково змінити ресурс: лише передані поля.
PUT /users/42
{"name": "Оля", "email": "olia@example.com", "phone": null}
PATCH /users/42
{"phone": "+380501234567"}
Ідемпотентність - ключова відмінність, про яку питають:
PUTіDELETEідемпотентні: повторний однаковий запит лишає ресурс у тому самому стані. Їх можна безпечно повторити при обриві з'єднання.POSTне ідемпотентний: повтор створить друге замовлення.PATCH- залежить від змісту: «встановити phone» ідемпотентний, «збільшити лічильник на 1» - ні.GET,HEAD,OPTIONS- ще й безпечні: не змінюють стан узагалі.
На практиці:
- Багато API використовують лише
PATCHдля оновлень, бо клієнту рідко потрібно надсилати весь ресурс. - Для створення з повторами (платежі)
POSTроблять ідемпотентним через заголовокIdempotency-Key. - Дії, що не вкладаються в CRUD (
/orders/42/cancel), зазвичай оформлюють якPOSTна під-ресурс.
Запит складається з рядка запиту, заголовків і (необов'язково) тіла:
POST /api/orders HTTP/1.1
Host: shop.example.com
Accept: application/json
Content-Type: application/json
Authorization: Bearer eyJhbGciOi...
Idempotency-Key: 8f14e45f-ceea-467f-a0d6-2a1e6c0c4f11
{"product_id": 42, "qty": 2}
- метод (
GET,POST,PATCH...) - що зробити; - шлях і рядок запиту - з яким ресурсом;
- заголовки - метадані: формат, автентифікація, кешування;
- тіло - дані (для
GETтіла зазвичай немає).
Відповідь - рядок статусу, заголовки, тіло:
HTTP/1.1 201 Created
Content-Type: application/json
Location: /api/orders/1057
Cache-Control: no-store
{"data": {"id": 1057, "status": "new"}}
Заголовки, які варто знати для API:
| Заголовок | Навіщо |
|---|---|
Content-Type |
формат тіла, яке надсилається |
Accept |
формат, який клієнт хоче отримати |
Authorization |
облікові дані (Bearer-токен) |
Location |
адреса створеного ресурсу (з 201) чи статусу операції (з 202) |
Cache-Control, ETag |
кешування й умовні запити |
Retry-After |
коли повторити (з 429, 503) |
X-Request-Id / traceparent |
наскрізний ідентифікатор для логів і трасування |
Типові помилки:
Content-Typeне відповідає тілу - сервер не розбере JSON, надісланий якtext/plain;- відсутній
Accept: application/jsonу запитах до Laravel - при помилці валідації замість JSON 422 прийде редирект на попередню сторінку; - статус 200 з
{"error": ...}у тілі - клієнти, проксі й моніторинг орієнтуються на код статусу, тож помилка має бути помилкою на рівні HTTP; - власні заголовки з префіксом
X-- застарілий звичай (RFC 6648); нові заголовки називають без нього.
Налагодження: curl -i показує заголовки відповіді, curl -v - і запиту; у браузері - вкладка Network.
Успіх (2xx):
- 200 OK - звичайна успішна відповідь з тілом.
- 201 Created - ресурс створено; бажано з заголовком
Location. - 202 Accepted - запит прийнято, обробка асинхронна (поставлено в чергу).
- 204 No Content - успіх без тіла: видалення, оновлення без повернення даних.
Помилки клієнта (4xx):
- 400 Bad Request - запит некоректний: зламаний JSON, неправильний формат.
- 401 Unauthorized - не автентифіковано: немає токена чи він недійсний. Попри назву, це про «хто ви?».
- 403 Forbidden - автентифіковано, але немає прав на цю дію.
- 404 Not Found - ресурсу немає (або ви не маєте права знати, що він існує).
- 409 Conflict - конфлікт стану: дублікат, застаріла версія при оптимістичному блокуванні.
- 422 Unprocessable Content - синтаксис правильний, але дані не пройшли валідацію. Laravel так і відповідає на помилки валідації.
- 429 Too Many Requests - перевищено ліміт запитів, з
Retry-After.
Помилки сервера (5xx): 500 - непередбачена помилка; 502/503/504 - проблеми з upstream, перевантаження, обслуговування.
Типові помилки:
200 OKз{"success": false, "error": "..."}- клієнти, проксі й моніторинг вважатимуть запит успішним.500на помилку валідації - це не помилка сервера.403для чужого ресурсу інколи розкриває, що він існує; тоді свідомо віддають404.
Клієнту API потрібно не лише знати, що пішло не так, а й обробити це програмно. Для цього помилки мають бути в одному передбачуваному форматі на весь API.
Problem Details (RFC 9457, замінив RFC 7807) - стандартний формат з типом application/problem+json:
{
"type": "https://api.example.com/problems/insufficient-funds",
"title": "Недостатньо коштів",
"status": 422,
"detail": "На рахунку 30 грн, а списання - 50 грн.",
"instance": "/payments/8f3a",
"balance": 3000
}
type- URI типу помилки, стабільний ідентифікатор для програмної обробки (бажано - з документацією за цією адресою).title- короткий опис типу, однаковий для всіх його випадків.status- HTTP-код (дублює заголовок для зручності).detail- пояснення саме цього випадку.instance- ідентифікатор конкретного випадку.- Можна додавати власні поля - як
balanceвище чи список помилок валідації за полями.
Принципи, незалежно від формату:
- Правильний HTTP-код - формат тіла його не замінює.
- Машиночитний код помилки (
typeчиcode), а не лише текст: текст змінюють і перекладають, а клієнти на нього не мають покладатися. - Помилки валідації за полями, щоб клієнт підсвітив поля форми.
- Жодних стек-трейсів, SQL і шляхів до файлів у відповіді на проді.
- ID запиту в тілі чи заголовку - щоб знайти помилку в логах за зверненням клієнта.
Докладніше в документації: RFC 9457: Problem Details for HTTP APIs
Основні правила:
- Іменники, а не дієслова: дія задається HTTP-методом.
GET /orders,POST /orders,DELETE /orders/42- а не/getOrders,/createOrder. - Множина для колекцій:
/ordersі/orders/42- узгоджено для всього API. - Вкладеність для відношень, але неглибока:
/users/42/orders- так;/users/42/orders/7/items/3/reviews- ні. Ресурс з власним ID краще адресувати напряму:/order-items/3. - Нижній регістр і дефіси:
/payment-methods, а не/paymentMethodsчи/payment_methods. - Фільтрація, сортування, пагінація - у query-параметрах:
/orders?status=paid&sort=-created_at&page[size]=20. - Стабільні ідентифікатори в URL: ID чи UUID, а не назви, які можуть змінитися.
Дії, що не вкладаються в CRUD:
- як під-ресурс стану:
POST /orders/42/cancellation; - як дія-під-ресурс:
POST /orders/42/cancel- прагматично й зрозуміло, і так роблять великі API (Stripe, GitHub); - головне - узгодженість у межах API.
Відповіді:
- Узгоджене іменування полів (
snake_caseчиcamelCase- одне на весь API). - Дати - в ISO 8601 з часовим поясом.
- Гроші - цілими числами в мінімальних одиницях або рядками, з валютою.
- Посилання на пов'язані ресурси чи їхні ID, а не дублювання цілих об'єктів без потреби.
Найважливіше - передбачуваність: розробник, побачивши два ендпоінти, має вгадати третій. Опис в OpenAPI допомагає тримати цю узгодженість і генерувати клієнти.
Прочитати - ще не значить знати
20 питань, по одному на екран, ~20 хв. Після завершення - розбір кожної помилки з посиланням на питання.