JavaRush /Курсы /Модуль 4: Node.js, Next.js и Angular /routes/: определение маршрутов, best practices

routes/: определение маршрутов, best practices

Модуль 4: Node.js, Next.js и Angular
6 уровень , 6 лекция
Открыта

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;
routes/users.js — только маршруты, логика вынесена в контроллер

// controllers/users.js
exports.getAllUsers = (req, res) => {
  res.json([{ id: 1, name: 'Alice' }]);
};

exports.getUserById = (req, res) => {
  res.json({ id: req.params.id, name: 'Пользователь' });
};
controllers/users.js — бизнес-логика отдельно

В чём профит?
Легко тестировать и переиспользовать контроллеры.
Маршруты становятся максимально простыми и читаемыми.

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, который агрегирует все остальные маршруты:
    
    // routes/index.js
    const express = require('express');
    const router = express.Router();
    
    router.use('/users', require('./users'));
    router.use('/tasks', require('./tasks'));
    
    module.exports = router;
    
    А в app.js:
    
    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 до маршрутов, которые хотите защитить.

1
Задача
Модуль 4: Node.js, Next.js и Angular, 6 уровень, 6 лекция
Недоступна
Создание простого файла маршрута с использованием Router
Создание простого файла маршрута с использованием Router
1
Задача
Модуль 4: Node.js, Next.js и Angular, 6 уровень, 6 лекция
Недоступна
Организация маршрутов через routes/index.js
Организация маршрутов через routes/index.js
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ