REST API
Зачем нужно
REST (Representational State Transfer) — архитектурный стиль для построения API. REST определяет правила именования URL, использования HTTP-методов и структуры ответов. Почти все веб-API построены по принципам REST: GitHub API, Twitter API, любой бэкенд для SPA.
Где используется
- Бэкенд для SPA и мобильных приложений
- Публичные API (GitHub, Stripe, Twilio)
- Микросервисная архитектура
- Интеграция между системами
Принципы REST
- Client-Server — клиент и сервер независимы
- Stateless — сервер не хранит состояние клиента между запросами
- Cacheable — ответы можно кэшировать
- Uniform Interface — единообразный интерфейс (URL = ресурс, HTTP-метод = действие)
- Layered System — клиент не знает, общается ли он с сервером напрямую или через прокси
- Code on Demand (опционально) — сервер может отправить код для выполнения на клиенте
Ресурсы и Endpoints
REST оперирует ресурсами (существительные), а не действиями (глаголы):
✅ Правильно (существительные):
GET /api/users — список пользователей
GET /api/users/42 — конкретный пользователь
POST /api/users — создать пользователя
PUT /api/users/42 — обновить пользователя
DELETE /api/users/42 — удалить пользователя
❌ Неправильно (глаголы):
GET /api/getUsers
POST /api/createUser
POST /api/deleteUser/42
GET /api/getUserById?id=42
Вложенные ресурсы
GET /api/users/42/posts — посты пользователя 42
POST /api/users/42/posts — создать пост от пользователя 42
GET /api/users/42/posts/7 — пост #7 пользователя 42
GET /api/posts/7/comments — комментарии к посту 7
POST /api/posts/7/comments — добавить комментарий к посту 7
Фильтрация, сортировка, пагинация
GET /api/users?role=admin&status=active — фильтрация
GET /api/users?sort=name&order=asc — сортировка
GET /api/users?page=2&limit=20 — пагинация
GET /api/users?fields=id,name,email — выбор полей
GET /api/products?category=electronics&min_price=1000&sort=-price&page=1&limit=10
CRUD-маппинг
| Операция | HTTP-метод | URL | Тело запроса | Ответ |
|---|---|---|---|---|
| Create | POST | /api/users | {name, email} |
201 + созданный объект |
| Read (все) | GET | /api/users | — | 200 + массив |
| Read (один) | GET | /api/users/42 | — | 200 + объект |
| Update (полное) | PUT | /api/users/42 | {name, email, age} |
200 + обновлённый |
| Update (частичное) | PATCH | /api/users/42 | {email} |
200 + обновлённый |
| Delete | DELETE | /api/users/42 | — | 204 No Content |
Структура ответов
// Успешный ответ — один объект
// GET /api/users/42 → 200
{
"id": 42,
"name": "Антон",
"email": "anton@mail.ru",
"createdAt": "2024-01-15T10:30:00Z"
}
// Успешный ответ — список с пагинацией
// GET /api/users?page=2&limit=10 → 200
{
"data": [
{ "id": 41, "name": "Мария" },
{ "id": 42, "name": "Антон" }
],
"pagination": {
"page": 2,
"limit": 10,
"total": 156,
"totalPages": 16
}
}
// Ошибка
// POST /api/users → 422
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Ошибка валидации",
"details": [
{ "field": "email", "message": "Некорректный email" },
{ "field": "name", "message": "Обязательное поле" }
]
}
}
Statelessness (без состояния)
// ❌ Stateful — сервер помнит состояние
// Запрос 1: POST /api/login → сервер запоминает сессию
// Запрос 2: GET /api/profile → сервер ищет сессию в памяти
// ✅ Stateless — каждый запрос самодостаточен
// Каждый запрос содержит токен авторизации
fetch('/api/profile', {
headers: { 'Authorization': 'Bearer eyJ...' },
});
// Сервер верифицирует токен, не обращаясь к сессиям
HATEOAS
Hypermedia As The Engine Of Application State — ответ содержит ссылки на связанные ресурсы:
// GET /api/users/42 → 200
{
"id": 42,
"name": "Антон",
"email": "anton@mail.ru",
"_links": {
"self": { "href": "/api/users/42" },
"posts": { "href": "/api/users/42/posts" },
"avatar": { "href": "/api/users/42/avatar" },
"update": { "href": "/api/users/42", "method": "PATCH" },
"delete": { "href": "/api/users/42", "method": "DELETE" }
}
}
Версионирование API
// Вариант 1: В URL (самый распространённый)
GET /api/v1/users
GET /api/v2/users
// Вариант 2: В заголовке
GET /api/users
Accept: application/vnd.myapp.v2+json
// Вариант 3: Query parameter
GET /api/users?version=2
Реализация REST API (Express)
const express = require('express');
const app = express;
app.use(express.json());
let users = [
{ id: 1, name: 'Антон', email: 'anton@mail.ru' },
{ id: 2, name: 'Мария', email: 'maria@mail.ru' },
];
let nextId = 3;
// GET /api/users — список
app.get('/api/users', (req, res) => {
const { role, sort, page = 1, limit = 10 } = req.query;
let result = [...users];
// Фильтрация
if (role) result = result.filter(u => u.role === role);
// Сортировка
if (sort) result.sort((a, b) => a[sort]?.localeCompare(b[sort]));
// Пагинация
const start = (page - 1) * limit;
const paged = result.slice(start, start + Number(limit));
res.json({
data: paged,
pagination: { page: Number(page), limit: Number(limit), total: result.length },
});
});
// GET /api/users/:id — один
app.get('/api/users/:id', (req, res) => {
const user = users.find(u => u.id === Number(req.params.id));
if (!user) return res.status(404).json({ error: 'Not found' });
res.json(user);
});
// POST /api/users — создать
app.post('/api/users', (req, res) => {
const { name, email } = req.body;
if (!name || !email) {
return res.status(422).json({
error: { message: 'name и email обязательны' },
});
}
const user = { id: nextId++, name, email };
users.push(user);
res.status(201).json(user);
});
// PATCH /api/users/:id — частичное обновление
app.patch('/api/users/:id', (req, res) => {
const user = users.find(u => u.id === Number(req.params.id));
if (!user) return res.status(404).json({ error: 'Not found' });
Object.assign(user, req.body);
res.json(user);
});
// DELETE /api/users/:id — удалить
app.delete('/api/users/:id', (req, res) => {
const index = users.findIndex(u => u.id === Number(req.params.id));
if (index === -1) return res.status(404).json({ error: 'Not found' });
users.splice(index, 1);
res.status(204).send;
});
app.listen(3000);
Частые ошибки
- Глаголы в URL —
/api/getUsersвместоGET /api/users - POST для всего — используют POST даже для получения данных
- Неправильные статус-коды — всегда 200, даже при ошибках
- Нет пагинации — возвращают все 10000 записей одним ответом
- Непоследовательный формат — один endpoint возвращает
{data: [...]}, другой — массив напрямую - Множественное число —
/api/user/42вместо/api/users/42
Практика
- Спроектировать REST API для блога (пользователи, посты, комментарии)
- Реализовать CRUD на Express с правильными статус-кодами
- Добавить фильтрацию, сортировку и пагинацию
- Написать единый формат ошибок
- Протестировать API через Postman или curl
Связанные темы
- HTTP протокол — HTTP-методы и статус-коды
- GraphQL basics — альтернатива REST
- CORS — доступ к API с другого домена
- Клиент-серверное взаимодействие — полный цикл работы с API