Express / 39 минут
Router и параметры
Собирайте маршруты в модули и валидируйте параметры пути.
- Собирайте маршруты в модули и валидируйте параметры пути.
- Применить тему в задаче «Router и параметры» и объяснить граничные случаи.
Около 9 минут на чтение и разбор примеров. Практика — отдельно, в своём темпе.
Локальная группа маршрутов
express.Router позволяет описать связанную группу endpoints и middleware отдельно от корневого приложения. Это полезно для users, notes или других предметных возможностей. Router не является отдельным сервером и сам по себе не открывает порт. При монтировании через app.use он становится частью общей цепочки обработки с указанным префиксом.
Группировка должна отражать обязанность модуля, а не случайное число строк. Небольшой router отвечает за HTTP-контракт своей возможности и вызывает сервис. Если внутри него находятся все запросы к БД, проверка сессий, форматирование писем и файловые операции, один файл с именем router ещё не создал архитектурных границ.
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 по ожидаемому адресу. Это частая ошибка при переносе маршрутов из одного большого файла в модуль.
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 или внешним строковым ключом, числовое преобразование вообще может быть ошибкой и потерять значимые символы.
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. С другой стороны, чрезмерное неявное наследование параметров затрудняет чтение контракта модуля.
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
Локальная цепочка позволяет один раз применить подходящие проверки ко всей группе. Например, все операции заметок могут требовать подтверждённого пользователя, а изменение конкретной заметки — ещё и проверки владельца. Общая проверка входа не заменяет авторизацию ресурса, но помогает расположить обязанности последовательно.
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-тест для реального итогового адреса, включая случай отсутствия ресурса.