Зарегистрируйтесь для доступа к 15+ бесплатным курсам по программированию с тренажером

Маршруты JS: Express

Любое приложение на Express состоит минимум из трёх элементов:

  1. Создание объекта приложения;
  2. Определение обработчиков маршрутов;
  3. Запуск приложения на определённом порту.
import Express from 'express';
const app = new Express();

app.get('/', (req, res) => {
  res.send('Hello World!');
});

app.listen(3000, () => {
  console.log('Example app listening on port 3000!');
});

С первым и последним пунктом всё более-менее понятно, а вот обработчики — это уже интересно. Напомню, что микрофреймворки строятся по схеме: http verb + path + callback. В случае Express глагол определяется функцией, которая вызывается на объекте app. Например если мы хотим определить маршрут GET /, то для этого необходимо вызывать функцию get и первым параметром передать в неё строку /. Таким же образом нужно поступать для любого другого маршрута. Вот список глаголов, которые нас будут интересовать в процессе этого курса:

// И - Идемпотентный
app.head // И
app.get // И
app.post
app.delete // И

// Полное обновление
app.put // И

// Частичное обновление
app.patch

Семантика http подразумевает, что глаголы get, delete, head и put, являются идемпотентными. Это должно обеспечиваться программистом, который реализует обработчики.

Динамические маршруты

Самая главная и мощная функциональность системы роутинга — это работа с динамическими маршрутами.

Route path: /users/:userId/books/:id
Request URL: http://localhost:3000/users/34/books/8989
req.params: { "userId": "34", "id": "8989" }
app.get('/users/:userId/books/:id', (req, res) => {
  const { userId, id } = req.params;
});

Для динамических частей используется заполнитель :name, который состоит из двоеточия и произвольного имени. Express производит сопоставление актуального запроса со всеми шаблонами в порядке определения и выполняет соответствующие обработчики. Все динамические части маршрута попадают в объект req.params.

Кроме этого, Express позволяет использовать регулярные выражения прямо в шаблоне:

// abcd, abxcd, abRANDOMcd, ab123cd
app.get('/ab*cd', (req, res) => {
  res.send('ab*cd');
});

Честно говоря, за свой многолетний опыт я не припомню ситуацию, когда мне это могло понадобиться. Всегда, если у вас есть возможность управлять урлами, можно сделать хороший вариант на одних плейсхолдерах (заполнителях), без прямого использования регулярных выражений.

Именованные маршруты

В приложении есть различные роуты. Например, у авторизации может стоять роут /signup. Когда мы отдаем с сервера HTML, то этот адрес мы прописываем дважды:

  • В роуте на сервере app.post('/signup', signupHandler)
  • В HTML <a href="/signup">Зарегистрироваться</a>

Про такое решение обычно говорят «хардкодить ссылки» или «магические строки». Если по какой-то причине мы решим переименовать этот роут, например, в /register, то нам надо поменять его и в роутах, и в HTML. В реальных приложениях роутов много, они состоят из нескольких частей, и переименование — не такая уж редкость.

Чтобы облегчить работу с именованием роутов, можно выделить их в отдельную константу и переиспользовать ее:

// Можно выделить отдельный модуль и импортировать из него объект с роутами:
const routes = { auth: '/signin', register: '/signup' }

// Переиспользуем роут
app.post(routes.register, (req, res) => {
  // ...
});

// Где-то в шаблоне
// <a href=`"${routes.register}"`>Зарегистрироваться</a>`.

Так мы сделаем именованные роуты. Теперь все адреса записаны в одном месте и их легко поменять. Весь остальной код будет использовать эти имена.

Есть другой подход в создании именованных роутеров — с помощью специальных библиотек. Например, named-routes:

import Express from 'express';
// Импортируем библиотеку
import Router from 'named-routes';

const app = new Express();
// Создаем объект роутера
const router = new Router();
// Подключаем в приложение
router.extendExpress(app);
router.registerAppHelpers(app);

app.get('/admin/users/:id', 'admin.user', (req, res, next) => {
  // ...
  // Создаем маршрут
  const path = app.namedRoutes.build('admin.user', { id: 2 }); // /admin/users/2
  // Имя текущего маршрута будет находиться в req.route.name
});

В примере выше был создан именованный маршрут admin.user. Теперь, где-то в другом обработчике можно строить ссылки используя имя маршрута:

app.get('/users', 'users', (req, res, next) => {
  const path = app.namedRoutes.build('admin.user', { id: 2 }); // /admin/users/2
  // path содержит путь с которым можно сделать что-то полезное
});

Преимущества:

  • Если удалится маршрут, то при генерации ссылки мы получим исключение, и тесты его поймают
  • Если изменится маршрут, то везде будут автоматически подставлены новые адреса

Чтобы сделать именованный роутинг действительно полезным и удобным, в других языках фреймворки предоставляют готовые решения, но в Express из коробки такой возможности нет. Поэтому в упражнениях мы продолжим хардкодить ссылки, но попробовать именованный роутинг можно будет в проекте.

Request/Response

Каждый определяемый обработчик принимает на вход два параметра: req и res. В этом смысле, всё очень похоже на ситуацию с http модулем с той лишь разницей, что там подобный обработчик один, а здесь на каждый маршрут свой. Request и Response предоставляют упрощенный интерфейс. Больше нет необходимости, по крайней мере в простых случаях, пользоваться ими как eventEmitter. На каждую задачу эти объекты предоставляют метод или свойство.

// GET /users?page=3
app.get('/users', (req, res) => {
  console.log(req.query); // { page: 3 }
  res.status(200);
  res.send(users);
  // res.json(users);
});

Пройдёмся по базовым возможностям:

  • req.query готовый к обработке query string, другими словами вам не придётся парсить эти параметры, они уже поступают в виде объекта;
  • Установка статуса работает через метод res.status(code);
  • Отправка данных выполняется функцией res.send(data). Все необходимые заголовки выставляются автоматически;
  • Если необходимо отдавать данные в виде json, то Express позволяет это делать без использования дополнительных механизмов. Всё что нужно сделать это вызвать метод res.json(data), и данные будут автоматически сериализованы и отправлены клиенту.

Дополнительные материалы

  1. Named Routes (Документация)

Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff

Используйте Хекслет по-максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»