Проектирование REST API
Проектирование REST API
REST (Representational State Transfer) - это архитектурный стиль для построения распределённых систем поверх HTTP. В этом уроке мы изучим, как создавать понятные, масштабируемые и удобные API.
Принципы REST
1. Client-Server
Клиент и сервер разделены. Клиент не знает о хранении данных, сервер не знает об UI.
2. Stateless
Каждый запрос содержит всю информацию для его обработки. Сервер не хранит состояние клиента между запросами.
# Плохо - сервер помнит контекст
POST /api/search
{ "query": "phones" }
GET /api/next-page # Сервер должен помнить предыдущий поиск
# Хорошо - каждый запрос самодостаточен
GET /api/products?query=phones&page=1
GET /api/products?query=phones&page=2
3. Cacheable
Ответы должны указывать, можно ли их кешировать.
HTTP/1.1 200 OK
Cache-Control: max-age=3600, public
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
4. Uniform Interface
Единообразный интерфейс для всех ресурсов.
5. Layered System
Архитектура может состоять из иерархических слоёв.
6. Code on Demand (опционально)
Сервер может отправлять исполняемый код клиенту.
Ресурсы и URL
Используйте существительные, не глаголы
# Плохо
GET /getUsers
POST /createUser
POST /deleteUser/123
# Хорошо
GET /users
POST /users
DELETE /users/123
Используйте множественное число
# Плохо
GET /user
GET /user/123
# Хорошо
GET /users
GET /users/123
Иерархия ресурсов
# Компании
GET /companies
GET /companies/123
# Сотрудники компании
GET /companies/123/employees
GET /companies/123/employees/456
# Можно также обратиться напрямую
GET /employees/456
HTTP методы и их семантика
GET - получение ресурса
GET /users/123 HTTP/1.1
HTTP/1.1 200 OK
{
"id": 123,
"name": "John Doe",
"email": "john@example.com"
}
POST - создание ресурса
POST /users HTTP/1.1
Content-Type: application/json
{
"name": "Jane Doe",
"email": "jane@example.com"
}
HTTP/1.1 201 Created
Location: /users/124
{
"id": 124,
"name": "Jane Doe",
"email": "jane@example.com"
}
PUT - полное обновление
PUT /users/123 HTTP/1.1
Content-Type: application/json
{
"name": "John Smith",
"email": "john.smith@example.com"
}
HTTP/1.1 200 OK
{
"id": 123,
"name": "John Smith",
"email": "john.smith@example.com"
}
PATCH - частичное обновление
PATCH /users/123 HTTP/1.1
Content-Type: application/json
{
"email": "newemail@example.com"
}
HTTP/1.1 200 OK
{
"id": 123,
"name": "John Smith",
"email": "newemail@example.com"
}
DELETE - удаление
DELETE /users/123 HTTP/1.1
HTTP/1.1 204 No Content
Фильтрация, сортировка и пагинация
Фильтрация
# Простая фильтрация
GET /products?category=electronics&price_min=100&price_max=500
# Сложная фильтрация
GET /products?filter[category]=electronics&filter[price][gte]=100&filter[price][lte]=500
Сортировка
# Простая сортировка
GET /products?sort=price
GET /products?sort=-price # По убыванию
# Множественная сортировка
GET /products?sort=-price,name
Пагинация
Offset-based:
GET /products?offset=20&limit=10
{
"data": [...],
"meta": {
"total": 100,
"offset": 20,
"limit": 10
}
}
Page-based:
GET /products?page=3&per_page=10
{
"data": [...],
"meta": {
"total": 100,
"page": 3,
"per_page": 10,
"total_pages": 10
}
}
Cursor-based:
GET /products?cursor=eyJpZCI6MTIzfQ==&limit=10
{
"data": [...],
"meta": {
"next_cursor": "eyJpZCI6MTMzfQ==",
"has_more": true
}
}
Версионирование API
1. В URL
GET https://api.example.com/v1/users
GET https://api.example.com/v2/users
2. В заголовке
GET https://api.example.com/users
Accept: application/vnd.myapi.v2+json
3. В query параметре
GET https://api.example.com/users?version=2
Стратегия версионирования
// Поддержка нескольких версий
app.use('/api/v1', v1Routes);
app.use('/api/v2', v2Routes);
// Deprecation заголовки
app.use('/api/v1', (req, res, next) => {
res.set('Sunset', '2024-12-31');
res.set('Deprecation', 'true');
res.set('Link', '</api/v2>; rel="successor-version"');
next();
});
Обработка ошибок
Используйте правильные HTTP коды
// 400 Bad Request - неверный формат запроса
{
"error": {
"code": "INVALID_REQUEST",
"message": "Invalid request format",
"details": {
"email": "Invalid email format"
}
}
}
// 401 Unauthorized - нет аутентификации
{
"error": {
"code": "UNAUTHORIZED",
"message": "Authentication required"
}
}
// 403 Forbidden - нет прав
{
"error": {
"code": "FORBIDDEN",
"message": "You don't have permission to access this resource"
}
}
// 404 Not Found - ресурс не найден
{
"error": {
"code": "NOT_FOUND",
"message": "User not found"
}
}
// 422 Unprocessable Entity - ошибка валидации
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Validation failed",
"errors": [
{
"field": "email",
"code": "invalid_format",
"message": "Email must be a valid email address"
}
]
}
}
Консистентный формат ошибок
class APIError {
constructor(code, message, status, details = null) {
this.error = {
code,
message,
...(details && { details })
};
this.status = status;
}
}
// Использование
throw new APIError(
'VALIDATION_ERROR',
'Validation failed',
422,
{ email: 'Invalid format' }
);
HATEOAS
HATEOAS (Hypermedia as the Engine of Application State) - клиент взаимодействует с приложением через гиперссылки.
{
"id": 123,
"name": "John Doe",
"email": "john@example.com",
"_links": {
"self": {
"href": "/users/123"
},
"posts": {
"href": "/users/123/posts"
},
"following": {
"href": "/users/123/following"
},
"followers": {
"href": "/users/123/followers"
}
}
}
Документирование API
OpenAPI (Swagger)
openapi: 3.0.0
info:
title: User API
version: 1.0.0
description: API для управления пользователями
paths:
/users:
get:
summary: Получить список пользователей
parameters:
- name: page
in: query
schema:
type: integer
default: 1
- name: limit
in: query
schema:
type: integer
default: 10
responses:
'200':
description: Успешный ответ
content:
application/json:
schema:
type: object
properties:
data:
type: array
items:
$ref: '#/components/schemas/User'
meta:
$ref: '#/components/schemas/Pagination'
post:
summary: Создать пользователя
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateUser'
responses:
'201':
description: Пользователь создан
content:
application/json:
schema:
$ref: '#/components/schemas/User'
components:
schemas:
User:
type: object
properties:
id:
type: integer
name:
type: string
email:
type: string
format: email
CreateUser:
type: object
required:
- name
- email
properties:
name:
type: string
email:
type: string
format: email
Pagination:
type: object
properties:
page:
type: integer
limit:
type: integer
total:
type: integer
Инструменты для документации
- Swagger UI - интерактивная документация
- Postman - коллекции и документация
- Redoc - красивая документация из OpenAPI
- API Blueprint - markdown-based документация
Best Practices
1. Используйте правильные Content-Type
# JSON
Content-Type: application/json
# XML
Content-Type: application/xml
# Form data
Content-Type: application/x-www-form-urlencoded
# Multipart (файлы)
Content-Type: multipart/form-data
2. Поддерживайте content negotiation
# Клиент запрашивает JSON
GET /users/123
Accept: application/json
# Клиент запрашивает XML
GET /users/123
Accept: application/xml
3. Используйте правильные статус коды
// Создание ресурса
res.status(201).json(user);
// Успешное удаление
res.status(204).send();
// Ресурс не найден
res.status(404).json({ error: 'Not found' });
// Ошибка сервера
res.status(500).json({ error: 'Internal server error' });
4. Идемпотентность
- GET, PUT, DELETE должны быть идемпотентными
- POST не идемпотентен
// PUT идемпотентен - многократные вызовы дают тот же результат
PUT /users/123
{ "name": "John", "email": "john@example.com" }
// POST не идемпотентен - каждый вызов создаёт новый ресурс
POST /users
{ "name": "John", "email": "john@example.com" }
5. Безопасность
// Rate limiting
app.use(rateLimit({
windowMs: 15 * 60 * 1000,
max: 100
}));
// CORS
app.use(cors({
origin: process.env.ALLOWED_ORIGINS.split(','),
credentials: true
}));
// Security headers
app.use(helmet());
// Input validation
app.post('/users',
body('email').isEmail(),
body('name').isLength({ min: 2 }),
handleValidation
);
Итоги
Хорошо спроектированный REST API:
- Интуитивно понятен
- Следует стандартам HTTP
- Имеет предсказуемое поведение
- Хорошо документирован
- Безопасен и производителен
Проектирование API - это искусство балансирования между простотой, функциональностью и производительностью. Следуйте принципам REST, но не бойтесь отступать от них, когда это оправдано бизнес-требованиями.
- PHP - REST API: маршрутизация, валидация, статус-коды - практическая реализация REST на PHP: 201 Created, 422, IDOR, пагинация