Express / 39 минут

Router и параметры

Собирайте маршруты в модули и валидируйте параметры пути.

В процессе Теория: впереди Практика: впереди Тест: впереди
04 / 12
После этой главы
  • Собирайте маршруты в модули и валидируйте параметры пути.
  • Применить тему в задаче «Router и параметры» и объяснить граничные случаи.

Около 9 минут на чтение и разбор примеров. Практика — отдельно, в своём темпе.

Локальная группа маршрутов

express.Router позволяет описать связанную группу endpoints и middleware отдельно от корневого приложения. Это полезно для users, notes или других предметных возможностей. Router не является отдельным сервером и сам по себе не открывает порт. При монтировании через app.use он становится частью общей цепочки обработки с указанным префиксом.

Группировка должна отражать обязанность модуля, а не случайное число строк. Небольшой router отвечает за HTTP-контракт своей возможности и вызывает сервис. Если внутри него находятся все запросы к БД, проверка сессий, форматирование писем и файловые операции, один файл с именем router ещё не создал архитектурных границ.

JavaScript
const express = require('express');
const app = express();
const notes = express.Router();
notes.get('/', (_req, res) => res.json([]));
notes.get('/:id', (req, res) => res.json({ id: req.params.id }));
app.use('/api/notes', notes);

Итоговый путь и вложенность

Путь внутри router задаётся относительно места монтирования. Если router подключён на /api, его маршрут /items/:id становится /api/items/:id. Повторение /api внутри создаст другой URL и приведёт к 404 по ожидаемому адресу. Это частая ошибка при переносе маршрутов из одного большого файла в модуль.

JavaScript
const api = express.Router();
api.get('/items/:id', (req, res) => res.json({ id: req.params.id }));
app.use('/api', api);

При нескольких уровнях вложенности полезно записать итоговые пути в таблице или интеграционном тесте. Имя файла не влияет на реальную маршрутизацию автоматически. В отличие от файловых роутеров некоторых frontend-фреймворков, Express использует именно зарегистрированную цепочку app.use и методов маршрута.

Параметры остаются внешними строками

Параметры пути обычно представлены строками. Строка '42' не становится числом только из-за имени id. Перед преобразованием проверяют полную допустимую форму и диапазон. Если идентификатор является UUID или внешним строковым ключом, числовое преобразование вообще может быть ошибкой и потерять значимые символы.

JavaScript
api.get('/orders/:id', (req, res) => {
  const raw = req.params.id;
  if (!/^[0-9]+$/.test(raw)) return res.sendStatus(400);
  const id = Number(raw);
  if (!Number.isSafeInteger(id) || id < 1) return res.sendStatus(400);
  res.json({ id });
});

Валидация идентификатора не подтверждает существование записи и не даёт права на неё. После синтаксической проверки сервис может вернуть отсутствие, а проверка авторизации — отказ. У этих этапов разные причины и публичные ответы. Не превращайте любую проблему в один случайный статус ради удобства handler.

Параметры родительского router

Во вложенном router доступ к параметрам родительского монтирования требует понимания его настроек. mergeParams позволяет сохранить соответствующие параметры в подходящем сценарии. Без явного решения дочерний слой может не увидеть ожидаемый parentId. С другой стороны, чрезмерное неявное наследование параметров затрудняет чтение контракта модуля.

JavaScript
const comments = express.Router({ mergeParams: true });
comments.get('/', (req, res) => {
  res.json({ noteId: req.params.noteId });
});
app.use('/notes/:noteId/comments', comments);

Опишите, какие параметры router ожидает извне, и проверьте их через реальный запрос к итоговому URL. Тест одного дочернего handler без места монтирования не обнаружит ошибку передачи parent-параметра. Это один из примеров, почему unit и HTTP-интеграционные проверки нужны для разных рисков.

Middleware уровня router

Локальная цепочка позволяет один раз применить подходящие проверки ко всей группе. Например, все операции заметок могут требовать подтверждённого пользователя, а изменение конкретной заметки — ещё и проверки владельца. Общая проверка входа не заменяет авторизацию ресурса, но помогает расположить обязанности последовательно.

JavaScript
notes.use((req, res, next) => {
  res.locals.feature = 'notes';
  next();
});

Не храните параметры конкретного запроса в глобальной переменной модуля router. Несколько запросов используют один и тот же зарегистрированный router, поэтому такое состояние не изолировано. Передавайте request-контекст явно через req/res.locals или аргументы вызова сервиса в соответствии с архитектурой приложения.

Практика префикса /api

Нужно смонтировать Router на /api и зарегистрировать внутри /items/:id. solve делает запрос к полному пути и возвращает body со строковым id. В этой задаче abc допустим так же, как 42: контракт не требует числового преобразования. Не добавляйте неоговорённую валидацию, которая изменит допустимые входы.

Проверьте числовую строку и буквенный идентификатор. Затем временно повторите /api в дочернем пути и объясните, почему ожидаемый запрос больше не попадает в handler. Для самостоятельного API разделите router, сервис и хранилище заметок и напишите HTTP-тест для реального итогового адреса, включая случай отсутствия ресурса.

Загружаем редактор…

Музыка: Reverie — Scott Buckley · CC BY 4.0