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'

// Отключаем авто-HEAD, чтобы не было дублей именованных маршрутов
const app = fastify({ exposeHeadRoutes: false })

await app.register(fastifyReverseRoutes)

Fastify по умолчанию создает HEAD-маршрут для каждого GET. При использовании fastify-reverse-routes это может приводить к дублированию именованных маршрутов, поэтому мы отключаем автогенерацию HEAD.

Если приложение запускается через fastify cli, ту же опцию нужно экспортировать из app.js:

export const options = { exposeHeadRoutes: false }

Чтобы fastify cli применил options из app.js, в командах запуска нужно передавать флаг --options (-o):

"scripts": {
  "test": "node --test test/**/*.test.js",
  "start": "fastify start -o -l info app.js",
  "dev": "fastify start -w -o -l info -P app.js"
}

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

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 = 'Создать')

Рекомендуемые программы

Дальше

Завершено

0 / 23

+7 800 100 22 47

бесплатно по РФ

+7 495 085 21 62

бесплатно по Москве

Республика Казахстан, г. Алматы,
ул. Ауэзова, д. 14А
БИН 230340043714