1. Зачем нужна папка routes/ и как она облегчает жизнь
В небольшом приложении все маршруты можно описать прямо в app.js или index.js. Но как только проект вырастает (а он обязательно вырастет, если вы не бросите программировать ради фермерства), файл с сервером превращается в «лапшу из маршрутов». Найти нужный обработчик становится всё сложнее, а при попытке добавить новый — можно случайно сломать что-то старое (или потерять рассудок).
Вот тут и приходит на помощь папка routes/!
Это ваш «органайзер» для всех маршрутов приложения. Каждый набор связанных маршрутов выносится в отдельный файл. В результате код становится модульным, читаемым и удобным для командной работы.
Пример структуры Express-приложения
my-express-app/
├── app.js
├── package.json
├── routes/
│ ├── users.js
│ ├── tasks.js
│ └── index.js
└── controllers/
└── ...
Факт для любознательных:
В больших проектах принято выделять не только папку routes/, но и controllers/, services/, models/ — чтобы каждая часть отвечала только за своё.
2. Основы: как устроен файл с маршрутами
В Express для группировки маршрутов используют объект Router. Это мини-приложение, которое обрабатывает только свою часть адресов.
Простой пример: файл routes/users.js
const express = require('express');
const router = express.Router();
// GET /users
router.get('/', (req, res) => {
res.json([{ id: 1, name: 'Alice' }]);
});
// GET /users/:id
router.get('/:id', (req, res) => {
// Здесь можно получить id через req.params.id
res.json({ id: req.params.id, name: 'Пользователь' });
});
module.exports = router;
Аналогия:
Представьте, что Router — это как отдельная ветка метро: у неё есть свои станции (маршруты), но все они подключаются к общей схеме (вашему приложению).
3. Подключение роутеров в основном файле приложения (app.js)
Теперь, когда у вас есть отдельный файл с маршрутами, его нужно «включить» в приложение. Для этого используем app.use():
const express = require('express');
const app = express();
const usersRouter = require('./routes/users');
const tasksRouter = require('./routes/tasks');
// Все маршруты /users/... будут обрабатываться usersRouter
app.use('/users', usersRouter);
// Все маршруты /tasks/... будут обрабатываться tasksRouter
app.use('/tasks', tasksRouter);
app.listen(3000, () => {
console.log('Сервер запущен на http://localhost:3000');
});
Теперь, если пользователь заходит на /users/1, запрос попадёт именно в обработчик из routes/users.js.
Важно помнить
- Путь, указанный в app.use('/users', usersRouter), становится префиксом для всех маршрутов внутри usersRouter.
- Внутри файла маршрута (users.js) вы пишете путь относительно этого префикса: router.get('/') — это /users/, а router.get('/:id') — это /users/1.
4. Разделение логики: routes/ vs controllers/
В рамках этой лекции мы фокусируемся на папке routes/, но нельзя не упомянуть о хорошей практике: разделять маршруты и бизнес-логику.
- Маршруты (routes) — отвечают только за то, чтобы «поймать» нужный URL и передать управление дальше.
- Контроллеры (controllers) — содержат основную логику обработки (например, работу с базой, валидацию данных).
Пример разделения
// routes/users.js
const express = require('express');
const router = express.Router();
const usersController = require('../controllers/users');
// GET /users
router.get('/', usersController.getAllUsers);
// GET /users/:id
router.get('/:id', usersController.getUserById);
module.exports = router;
// controllers/users.js
exports.getAllUsers = (req, res) => {
res.json([{ id: 1, name: 'Alice' }]);
};
exports.getUserById = (req, res) => {
res.json({ id: req.params.id, name: 'Пользователь' });
};
В чём профит?
Легко тестировать и переиспользовать контроллеры.
Маршруты становятся максимально простыми и читаемыми.
5. Best practices: советы по организации маршрутов
- Один файл — одна сущность
Если у вас есть сущности «пользователь», «задача», «продукт» — для каждой делайте отдельный файл маршрутов: users.js, tasks.js, products.js. Не смешивайте всё в одном файле. - Используйте Router, а не app
Внутри файлов маршрутов используйте express.Router(). Не создавайте новое приложение через express(), иначе у вас получится мини-зоопарк серверов. - Всегда экспортируйте router
В конце файла маршрута пишите module.exports = router;. Не экспортируйте функции или объекты напрямую — иначе Express не поймёт, что с этим делать. - Не забывайте про порядок
В app.js порядок подключения app.use() может быть важен. Например, если у вас есть middleware для авторизации, его нужно подключать ДО маршрутов, которые должны быть защищены. - Группируйте REST-методы
В одном файле маршрутов удобно собирать все методы для одной сущности:// routes/tasks.js router.get('/', ...); // Получить список задач router.post('/', ...); // Создать задачу router.get('/:id', ...); // Получить задачу по id router.put('/:id', ...); // Обновить задачу router.delete('/:id', ...); // Удалить задачу - Если маршрутов много — делайте index.js
В больших проектах внутри папки routes/ можно сделать свой index.js, который агрегирует все остальные маршруты:
А в app.js:// routes/index.js const express = require('express'); const router = express.Router(); router.use('/users', require('./users')); router.use('/tasks', require('./tasks')); module.exports = router;const routes = require('./routes'); app.use('/', routes);
6. Практика: продолжаем наше приложение
Давайте доработаем наше учебное приложение — теперь маршруты будут лежать в отдельных файлах.
Шаг 1. Создаём структуру проекта
my-todo-app/
├── app.js
├── routes/
│ └── tasks.js
└── controllers/
└── tasks.js
Шаг 2. routes/tasks.js
const express = require('express');
const router = express.Router();
const tasksController = require('../controllers/tasks');
// Получить все задачи
router.get('/', tasksController.getAllTasks);
// Получить задачу по id
router.get('/:id', tasksController.getTaskById);
// Создать новую задачу
router.post('/', tasksController.createTask);
// Обновить задачу
router.put('/:id', tasksController.updateTask);
// Удалить задачу
router.delete('/:id', tasksController.deleteTask);
module.exports = router;
Шаг 3. controllers/tasks.js
// В реальном приложении задачи были бы в базе данных.
// Для простоты пока используем массив в памяти:
let tasks = [
{ id: 1, title: 'Купить хлеб', completed: false },
{ id: 2, title: 'Выучить Express', completed: true }
];
exports.getAllTasks = (req, res) => {
res.json(tasks);
};
exports.getTaskById = (req, res) => {
const id = Number(req.params.id);
const task = tasks.find(t => t.id === id);
if (!task) {
return res.status(404).json({ error: 'Задача не найдена' });
}
res.json(task);
};
exports.createTask = (req, res) => {
const { title } = req.body;
if (!title) {
return res.status(400).json({ error: 'Нет названия задачи' });
}
const newTask = {
id: tasks.length ? Math.max(...tasks.map(t => t.id)) + 1 : 1,
title,
completed: false
};
tasks.push(newTask);
res.status(201).json(newTask);
};
exports.updateTask = (req, res) => {
const id = Number(req.params.id);
const task = tasks.find(t => t.id === id);
if (!task) {
return res.status(404).json({ error: 'Задача не найдена' });
}
const { title, completed } = req.body;
if (title !== undefined) task.title = title;
if (completed !== undefined) task.completed = completed;
res.json(task);
};
exports.deleteTask = (req, res) => {
const id = Number(req.params.id);
const index = tasks.findIndex(t => t.id === id);
if (index === -1) {
return res.status(404).json({ error: 'Задача не найдена' });
}
tasks.splice(index, 1);
res.status(204).send();
};
Шаг 4. app.js
const express = require('express');
const app = express();
app.use(express.json()); // Для разбора JSON в теле запроса
const tasksRouter = require('./routes/tasks');
app.use('/tasks', tasksRouter);
app.listen(3000, () => {
console.log('Сервер работает на http://localhost:3000');
});
7. Типичные ошибки при организации маршрутов
Ошибка №1: забыли экспортировать router.
Если в конце файла не написать module.exports = router;, Express не сможет подключить ваши маршруты. В результате — «404 Not Found» на все запросы, а вы проводите вечер в поисках опечатки.
Ошибка №2: перепутали app и router.
В файле маршрута случайно создали const app = express(); вместо const router = express.Router();. Итог: ваш мини-сервер живёт своей жизнью, а основной сервер о нём ничего не знает.
Ошибка №3: не используете префиксы.
Если не указать префикс в app.use('/users', usersRouter), все маршруты из users попадут в корень (/), и начнётся неразбериха.
Ошибка №4: смешивание логики.
Если в файле маршрута вы пишете всю бизнес-логику прямо в обработчиках, файл быстро разрастается и становится нечитаемым. Лучше выносить логику в контроллеры.
Ошибка №5: дублирование маршрутов.
Если несколько файлов маршрутов объявляют одинаковые пути (например, оба имеют router.get('/:id')), а вы подключаете их на один и тот же префикс, Express выберет первый совпавший обработчик. В итоге — загадочные баги и много удивления.
Ошибка №6: забыли про порядок подключения middleware.
Если вы ставите middleware (например, для авторизации) после маршрутов, он не будет работать для этих маршрутов. Всегда подключайте middleware до маршрутов, которые хотите защитить.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ