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 предсказуемым: зная ресурс, вы угадываете все операции.

CRUD и методы HTTP

Чтение и создание

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.

Частые вопросы

Что такое REST API?
Стиль построения веб-API вокруг ресурсов и стандартных методов HTTP. Ресурс — сущность вроде пользователя, а методы GET, POST, PUT, DELETE выражают действия над ним. Предсказуемая структура делает API понятным без документации.
Какие методы HTTP за что отвечают?
GET читает данные, POST создаёт новый ресурс, PUT заменяет ресурс целиком, PATCH меняет часть, DELETE удаляет. Метод выражает намерение, поэтому GET не должен менять данные, а DELETE не должен что-то создавать.
Какой код ответа возвращать?
200 для успешного чтения и обновления, 201 при создании, 204 при удалении без тела, 400 при неверном запросе, 401 без авторизации, 404 если ресурс не найден, 500 при ошибке сервера. Правильный код важен для клиентов API.
Нужно ли версионировать API?
Да, если API используют внешние клиенты. Версия в пути вроде /api/v1 позволяет менять API, не ломая старых клиентов — они продолжают ходить в v1, а новые возможности выходят в v2. Для внутренних API это менее критично.
Какая частая ошибка при проектировании REST API?
Глаголы в путях вместо ресурсов: /getUser вместо /users/:id. В REST путь — это существительное-ресурс, а действие выражает метод HTTP. Также возвращают неверные коды, например 200 при ошибке, что путает клиентов.