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', ...). Эти приёмы убирают дублирование и держат сквозную логику в одном месте. Чем крупнее проект, тем заметнее выигрыш: модуль ресурса становится самодостаточным и переносимым.

Проверьте себя
1. Как пути указываются внутри роутера, подключённого по app.use('/tasks', router)?
AПолностью, с /tasks
BОтносительно префикса: '/' это /tasks
CТолько через регулярные выражения
DС обязательным /api впереди
2. За что отвечает слой контроллеров?
AЗа запросы к базе данных
BЗа работу с HTTP: читает req, зовёт сервис, формирует res
CЗа запуск сервера
DЗа хранение конфигурации