Middleware для групп маршрутов

В Page.js middleware представляет собой функцию, которая выполняется перед обработчиком маршрута и получает объект контекста, а также функцию next для передачи управления следующему middleware или маршруту. Middleware позволяет централизованно обрабатывать повторяющуюся логику: проверку авторизации, загрузку данных, ведение логов, а также модификацию контекста перед передачей его конечному обработчику.

Группировка маршрутов

Page.js поддерживает концепцию групп маршрутов, когда несколько маршрутов объединяются под одним контекстом или префиксом, что позволяет применять один middleware ко всем маршрутам группы. Это особенно удобно при построении SPA (Single Page Application) с несколькими секциями, требующими одинаковой предобработки.

Создание базовой группы маршрутов

Простейший способ реализовать группу маршрутов — использовать функцию обёртку:

function group(basePath, middleware, routes) {
  routes.forEach(route => {
    page(basePath + route.path, middleware, route.handler);
  });
}

Пример использования:

group('/admin', requireAuth, [
  { path: '/dashboard', handler: adminDashboard },
  { path: '/users', handler: manageUsers },
  { path: '/settings', handler: adminSettings }
]);

Здесь requireAuth — middleware, проверяющий права доступа. Он выполняется перед каждым маршрутом /admin/*.

Middleware цепочки

Page.js позволяет объединять несколько middleware в цепочку. Каждый middleware получает объект ctx и функцию next. Цепочка выполняется последовательно: первый middleware вызывает next(), управление передается следующему, и так далее, пока не будет вызван конечный обработчик маршрута.

Пример цепочки:

function logMiddleware(ctx, next) {
  console.log(`Переход на маршрут: ${ctx.path}`);
  next();
}

function loadUserMiddleware(ctx, next) {
  ctx.user = { name: 'Alice', role: 'admin' };
  next();
}

page('/profile', logMiddleware, loadUserMiddleware, showProfile);

Преимущество группового подхода заключается в возможности применить цепочку middleware ко всей группе маршрутов:

group('/account', [logMiddleware, loadUserMiddleware], [
  { path: '/settings', handler: accountSettings },
  { path: '/history', handler: accountHistory }
]);

В этом случае для маршрутов /account/settings и /account/history будут последовательно вызваны оба middleware.

Префиксы и динамические сегменты

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

Пример:

function loadArticle(ctx, next) {
  const articleId = ctx.params.id;
  fetch(`/api/articles/${articleId}`)
    .then(res => res.json())
    .then(data => {
      ctx.article = data;
      next();
    });
}

group('/blog', loadArticle, [
  { path: '/:id', handler: showArticle },
  { path: '/:id/edit', handler: editArticle }
]);

В данном примере middleware loadArticle срабатывает для всех маршрутов /blog/:id*, загружая данные статьи и добавляя их в контекст.

Обработка ошибок в middleware

Ошибки в middleware можно обрабатывать централизованно. Для этого достаточно обернуть асинхронные операции в try/catch и при ошибке перенаправить на страницу ошибки или вызвать специальный middleware для обработки ошибок:

function safeMiddleware(ctx, next) {
  try {
    next();
  } catch (err) {
    console.error('Ошибка в middleware:', err);
    page('/error');
  }
}

Советы по организации middleware для групп

  • Порядок имеет значение. Middleware выполняются строго в том порядке, в котором они указаны. Сначала общий для группы, затем специфический для маршрута.
  • Минимизировать задержки. Асинхронные операции в middleware должны быть оптимизированы, чтобы не блокировать переходы между маршрутами.
  • Передача данных через ctx. Любая информация, необходимая обработчику маршрута, должна сохраняться в ctx, а не в глобальных переменных.
  • Переиспользуемость. Middleware стоит делать независимыми и универсальными, чтобы их можно было применять к разным группам маршрутов без дублирования кода.

Примеры комплексного применения

function authMiddleware(ctx, next) {
  if (!ctx.user) {
    page('/login');
  } else {
    next();
  }
}

function analyticsMiddleware(ctx, next) {
  sendAnalytics(ctx.path);
  next();
}

group('/dashboard', [authMiddleware, analyticsMiddleware], [
  { path: '/', handler: dashboardHome },
  { path: '/reports', handler: dashboardReports },
  { path: '/settings', handler: dashboardSettings }
]);

В этом примере для всех маршрутов /dashboard/* выполняется проверка авторизации и отправка аналитики, после чего вызывается соответствующий обработчик страницы.