REST API на Express: строим по правилам
Содержание
О проверке кода. Примеры не запускались в этой сессии (по договорённости — фокус на контенте, а не прогонах). Говорим прямо, а не ставим бейдж «проверено».
REST — самый распространённый стиль веб-API, и Express идеально под него заточен. Дело не в технологии, а в соглашениях: следуешь им — API понятен без документации. Разберём правила.
Ресурсы и методы
Ключевая идея REST: путь — это ресурс (существительное), действие выражает метод HTTP.
app.get('/users', list); // получить всех пользователей
app.get('/users/:id', getOne); // получить одного
app.post('/users', create); // создать
app.put('/users/:id', replace); // заменить целиком
app.patch('/users/:id', update); // изменить частично
app.delete('/users/:id', remove); // удалить
Один путь /users/:id с разными методами — это разные операции над одним ресурсом. Не нужно придумывать /getUser, /updateUser, /deleteUser — метод уже говорит, что делаем. Это делает API предсказуемым: зная ресурс, вы угадываете все операции.
Чтение и создание
GET, POST
GET /users— списокGET /users/:id— одинPOST /users— создать (201)- GET не меняет данные!
Изменение и удаление
PUT, PATCH, DELETE
PUT /users/:id— заменить целикомPATCH /users/:id— частьDELETE /users/:id— удалить (204)- Действие — в методе, не в пути
Полный CRUD-пример
const router = express.Router();
// список
router.get('/', async (req, res) => {
const users = await db.users.findAll();
res.json(users); // 200 по умолчанию
});
// один
router.get('/:id', async (req, res) => {
const user = await db.users.findById(req.params.id);
if (!user) return res.status(404).json({ error: 'Не найден' });
res.json(user);
});
// создать
router.post('/', async (req, res) => {
const user = await db.users.create(req.body);
res.status(201).json(user); // 201 Created + созданный объект
});
// обновить
router.patch('/:id', async (req, res) => {
const user = await db.users.update(req.params.id, req.body);
if (!user) return res.status(404).json({ error: 'Не найден' });
res.json(user);
});
// удалить
router.delete('/:id', async (req, res) => {
await db.users.remove(req.params.id);
res.status(204).end(); // 204 No Content — без тела
});
app.use('/api/v1/users', router);
Обратите внимание на проверку if (!user) → 404 и правильные коды: 201 при создании, 204 при удалении. Всё асинхронно (async/await), с обработкой ошибок через middleware (не показана, но обязательна).
Правильные коды ответов
Код ответа — не формальность, клиенты API на него полагаются:
| Код | Когда | Пример |
|---|---|---|
| 200 OK | успешное чтение/обновление | GET /users |
| 201 Created | ресурс создан | POST /users |
| 204 No Content | успех без тела ответа | DELETE /users/:id |
| 400 Bad Request | неверные данные запроса | провалилась валидация |
| 401 Unauthorized | нет/неверная авторизация | нет токена |
| 403 Forbidden | авторизован, но нет прав | не админ |
| 404 Not Found | ресурс не существует | GET /users/999 |
| 500 Internal Error | ошибка на сервере | сбой БД |
Возвращать 200 при ошибке — грубая ошибка проектирования: клиент решит, что всё хорошо. Коды 4xx — вина клиента (неверный запрос), 5xx — вина сервера. Точный код позволяет клиенту правильно реагировать: повторить, показать ошибку валидации, отправить на логин.
Структура ответа
// список с метаданными пагинации
res.json({
data: users,
meta: { page: 2, total: 145, perPage: 20 },
});
// ошибка — единообразный формат
res.status(400).json({
error: 'Ошибка валидации',
details: { email: 'неверный формат' },
});
Держите формат ответа единообразным по всему API: одинаковая обёртка для данных, одинаковая структура ошибок. Клиенту проще, когда все эндпоинты отвечают предсказуемо. Для списков добавляют метаданные пагинации (page, total).
Версионирование
app.use('/api/v1/users', usersV1);
app.use('/api/v2/users', usersV2); // новая версия, старые клиенты не сломаны
Версия в пути (/api/v1) позволяет развивать API, не ломая существующих клиентов. Меняете формат или логику — выпускаете v2, а v1 продолжает работать для тех, кто ещё не перешёл. Для публичных API это обязательно; для внутренних — по ситуации. Добавляется одной строкой префикса.
Частые ошибки
- глаголы в путях —
/getUser,/createOrderвместо/users/:id,/orders. Действие — в методе; - неверные коды — 200 при ошибке, отсутствие 404 для несуществующего;
- GET, меняющий данные —
GET /deleteUserнедопустим: GET обязан быть безопасным (только чтение); - разнородные ответы — каждый эндпоинт отвечает по-своему, клиенту мучение;
- отсутствие валидации — проверять вход обязательно, нельзя доверять клиенту.
Следование конвенциям REST — это то, что отличает API, с которым приятно работать, от того, к которому нужна толстая инструкция.
Смежные темы: маршрутизация — методы и пути; middleware; валидация — проверка входа; обработка ошибок; JWT-авторизация. Полный список — в уроках Node.js.