express.Router и структура проекта
express.Router выносит группу маршрутов в отдельный файл — структура вместо свалки.
«Когда маршрутов становится много, Router превращает один гигантский файл в аккуратные модули.»
Пока маршрутов мало, они умещаются в index.js. Но настоящие приложения имеют десятки эндпоинтов, и держать всё в одном файле невозможно. Express даёт Router — мини-приложение, которое можно собрать отдельно и подключить к основному. В этом уроке научимся разбивать API на модули.
Создаём роутер
Router объявляется как express.Router() и наполняется маршрутами так же, как приложение. Затем его подключают по префиксу:
// routes/tasks.js
const express = require('express');
const router = express.Router();
router.get('/', (req, res) => res.json([])); // GET /tasks
router.get('/:id', (req, res) => res.json({})); // GET /tasks/:id
router.post('/', (req, res) => res.status(201).json({}));// POST /tasks
module.exports = router;// index.js
const express = require('express');
const app = express();
const tasksRouter = require('./routes/tasks');
app.use('/tasks', tasksRouter); // все маршруты под /tasks
app.listen(3000);Внутри роутера пути указываются относительно префикса: router.get('/') отвечает на /tasks, а router.get('/:id') — на /tasks/:id.
Структура проекта
Типичная организация растущего API: маршруты, контроллеры (логика обработки), сервисы (бизнес-логика):
src/
index.js -- запуск приложения
routes/
tasks.js -- только маршруты -> зовут контроллеры
users.js
controllers/
tasks.js -- читает req, зовёт сервис, шлёт res
services/
tasks.js -- бизнес-логика, работа с данными
middleware/
auth.jsПринцип: маршрут только связывает путь с контроллером, контроллер работает с HTTP, сервис — с данными. Слои не лезут в чужую зону ответственности.
Мини-роутер с префиксом в браузере
Смоделируем, как Router склеивает префикс с относительным путём — это и есть магия app.use('/tasks', router):
function mountRouter(prefix, routes) {
const full = {};
for (const path in routes) {
const fullPath = prefix + (path === '/' ? '' : path);
full[fullPath || '/'] = routes[path];
}
return full;
}
const tasksRoutes = {
'/': 'список задач',
'/:id': 'одна задача'
};
const mounted = mountRouter('/tasks', tasksRoutes);
console.log(mounted);
// { '/tasks': 'список задач', '/tasks/:id': 'одна задача' }Как работает под капотом
Router — это самостоятельный стек middleware и маршрутов, по сути мини-приложение. Когда ты делаешь app.use('/tasks', router), Express добавляет роутер как один слой с префиксом. При запросе он сначала проверяет префикс, отрезает его от url и передаёт остаток внутреннему стеку роутера. Поэтому внутри роутера пути относительные.
Частые ошибки
- Дублировать префикс. Если подключил по
/tasks, не пиши внутриrouter.get('/tasks')— получится/tasks/tasks. - Логика в маршрутах. Файл routes должен лишь связывать путь с контроллером, а не содержать запросы к базе.
- Забыть module.exports. Без экспорта роутер не подключится.
Best practices
- Один роутер на один ресурс: tasks, users, orders.
- Разделяй слои: routes, controllers, services — это упрощает тесты и поддержку.
- Группируй файлы по домену в больших проектах, а не только по типу.
Итоги
Router превращает монолитный файл в аккуратные модули, по одному на ресурс, а слои routes/controllers/services наводят порядок в логике. Это завершает раздел про REST API. Дальше подключим настоящую базу данных и аутентификацию.
Router-уровневые middleware
Router — это не только группировка путей, но и удобная точка для middleware, общих всему ресурсу. Можно написать router.use(requireAuth) в начале файла роутера, и защита применится ко всем его маршрутам разом, без повторения в каждом. Так же удобно вешать на роутер middleware, загружающий ресурс по :id один раз для всех вложенных маршрутов через router.param('id', ...). Эти приёмы убирают дублирование и держат сквозную логику в одном месте. Чем крупнее проект, тем заметнее выигрыш: модуль ресурса становится самодостаточным и переносимым.