Зарегистрируйтесь, чтобы продолжить обучение

Именованные маршруты JS: Веб-разработка

Шаблоны сайта содержат множество внутренних ссылок, начиная от меню, заканчивая кнопками и формами. До сих пор мы формировали эти ссылки прямо в тех местах, где они нам нужны:

if courses.length === 0
  p Пока не добавлено ни одного курса
else
  each course in courses
    div
      h2
        a(href=`/courses/${course.id}`) #{course.title}
      p #{course.description}

Так же ссылки строятся и в обработчиках, например, при редиректах:

const id = /* Как-то формируется */;
res.redirect(`/courses/${id}`);

В примерах выше, формирование ссылки зашито прямо в то место, где она используется. Такой способ формирования ссылок потенциально опасен. Что если маршрут изменится с /courses/:id на /c/:id? Придётся пройтись по всем шаблонам и изменить все ссылки /courses/:id на /c/:id. А если этот маршрут удалить? Сайт продолжит работать, но ссылки начнут вести на несуществующие страницы. Лучше если страницы с такими ссылками начнут выдавать ошибки. Тогда выявить подобные ссылки станет крайне просто.

Именованные маршруты с помощью маппинга

Для решения этой задачи придумали именованные маршруты. Каждому маршруту фреймворка присваивается имя, которое затем можно использовать при построении конкретной ссылки. В Fastify такой механизм не встроен, но его легко можно имитировать объектом с описанием маршрутов.

const routes = {
  usersPath: () => '/users',
  newUserPath: () => '/users/new',
  coursesPath: () => '/courses',
  coursePath: id => `/courses/${id}`,
}

После того как методы добавлены, их можно внедрить в описание маршрутов Fastify и передавать в шаблоны:

app.get(routes.newUserPath(), (req, res) => {
  // ...
  const templateData = {
    // ...
    routes, // Передаем в шаблон объект для получения маршрутов
  }

  res.render('users/new', templateData)
})

app.get(routes.coursesPath(), (req, res) => {
  const templateData = {
    courses: state.courses,
    routes,
  }

  res.render('courses/index', templateData)
})

app.get(routes.coursePath(':id'), (req, res) => {
  // ...
  const templateData = {
    // ...
    routes,
  }

  res.render('course/index', templateData)
})

app.post(routes.usersPath(), (req, res) => {
  // ...
  const templateData = {
    // ...
    routes,
  }

  res.render('users/index', templateData)
})

Пример шаблона для вывода списка курсов:

if courses.length === 0
  p Пока не добавлено ни одного курса
else
  each course in courses
    div
      h2
        a(href=routes.coursePath(course.id)) #{course.title}
      p #{course.description}

Немного падает читаемость, но ее можно повысить добавив комментарии над определениями маршрутов.

const routes = {
  // Пользователи
  usersPath: () => '/users',
  // Форма создания пользователя
  newUserPath: () => '/users/new',
  // Курсы
  coursesPath: () => '/courses',
  // Конкретный курс
  coursePath: id => `/courses/${id}`,
}

Именованные маршруты с помощью библиотек

Есть еще один популярный подход в работе с маршрутами. Он реализуется с помощью специальных библиотек и позволяет создавать имена для маршрутов. Разберем на примере библиотеки fastify-reverse-routes:

npm i fastify-reverse-routes

Подключение библиотеки к приложению осуществляется стандартным способом:

import { plugin as fastifyReverseRoutes } from 'fastify-reverse-routes'

// При создании приложения нужно указать эту опцию
const app = fastify({ exposeHeadRoutes: false })

await app.register(fastifyReverseRoutes)

Когда библиотека подключена, мы можем получать маршрут по его имени, а имя маршрута задается через опции. Разберем подробнее на примере маршрута:

app.get('/users', { name: 'users' }, (req, reply) => {
  // ...
})

Мы создали обработчик и задали ему имя 'users'. Теперь везде, где нам нужно получить адрес этого маршрута, мы можем это сделать по заданному имени:

app.reverse('users')

Этот способ позволяет обращаться и к динамическим маршрутам:

app.get('/users/:id/edit', { name: 'editUser' }, (req, reply) => {
  // ...
})
app.reverse('editUser', { id: user.id })

Для удобства, мы можем определить вспомогательную функцию для шаблонов, чтобы в них получать маршруты. Это делается с помощью свойства defaultContext:

const route = (name, placeholdersValues) => app.reverse(name, placeholdersValues)

await app.register(view, {
  engine: { pug },
  defaultContext: {
    route,
  },
})

Вызов в шаблоне:

form(role = 'form' method = 'POST' action = route('users'))
  input(type = 'text', name = 'term', value = body)
  input(type = 'submit' value = 'Создать')

Самостоятельная работа

  1. Повторите у себя на компьютере все шаги из урока. Добавьте в приложение класс с именами маршрутов. Измените код обработчиков и шаблонов так, чтобы они использовали именованные маршруты, вместо ручного формирования ссылки
  2. Прочувствуйте преимущества именованных маршрутов. Измените в классе с именованными маршрутами адреса ссылок. Например, вместо /users сделайте /u. Запустите приложение и убедитесь, что оно продолжает работать с новыми адресами. Нам даже не пришлось менять код во всех обработчиках и шаблонах
  3. Залейте изменения на GitHub

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

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

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

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

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

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