Шаблоны сайта содержат множество внутренних ссылок, начиная от меню, заканчивая кнопками и формами. До сих пор мы формировали эти ссылки прямо в тех местах, где они нам нужны:
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 = 'Создать')
Самостоятельная работа
- Повторите у себя на компьютере все шаги из урока. Добавьте в приложение класс с именами маршрутов. Измените код обработчиков и шаблонов так, чтобы они использовали именованные маршруты, вместо ручного формирования ссылки
- Прочувствуйте преимущества именованных маршрутов. Измените в классе с именованными маршрутами адреса ссылок. Например, вместо /users сделайте /u. Запустите приложение и убедитесь, что оно продолжает работать с новыми адресами. Нам даже не пришлось менять код во всех обработчиках и шаблонах
- Залейте изменения на GitHub
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.