Middleware на уровне маршрута

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


Структура маршрута с middleware

Маршрут в Universal Router представляет собой объект с ключевыми полями:

{
  path: '/example',
  action: async (context, params) => { ... },
  children: [...],
  middleware: [...]
}
  • path — путь маршрута, поддерживает динамические сегменты (/user/:id).
  • action — функция, выполняющаяся при совпадении маршрута.
  • children — массив вложенных маршрутов.
  • middleware — массив функций промежуточной обработки, вызываемых перед action.

Каждый middleware получает объект context и объект params, аналогично action, и может возвращать результат либо передавать управление следующему middleware через next().


Определение middleware

Middleware — это функция, которая принимает три аргумента:

async function middleware(context, params, next) {
  // Логика middleware
  return next(); // Передача управления следующему middleware или action
}
  • context — объект с информацией о маршрутизации, включая URL, базовый путь, роутер.
  • params — объект с параметрами маршрута, извлечёнными из URL.
  • next — функция, запускающая следующий middleware или конечный action. Если next не вызывается, цепочка прерывается.

Пример использования middleware на маршруте

const routes = [
  {
    path: '/admin',
    middleware: [
      async (context, params, next) => {
        if (!context.user || !context.user.isAdmin) {
          return '/login';
        }
        return next();
      }
    ],
    action: async (context) => {
      return 'Admin dashboard';
    }
  }
];

В этом примере middleware проверяет, является ли пользователь администратором. Если проверка не проходит, происходит редирект на страницу /login. Если проверка успешна, управление передаётся action маршрута.


Последовательность выполнения middleware

  1. Роутер находит маршрут, совпадающий с URL.
  2. Выполняются middleware, определённые для маршрута, в том порядке, в котором они перечислены.
  3. Если все middleware вызывают next(), выполняется action маршрута.
  4. Если middleware возвращает результат без next(), цепочка прерывается, и этот результат возвращается роутеру.

Middleware для вложенных маршрутов

Middleware можно определять на любом уровне вложенности маршрутов. При этом порядок выполнения следующий:

  • Сначала выполняются middleware родительского маршрута.
  • Затем middleware вложенных маршрутов.
  • В конце вызывается action соответствующего маршрута.

Пример:

const routes = [
  {
    path: '/user',
    middleware: [
      async (ctx, params, next) => {
        console.log('User middleware');
        return next();
      }
    ],
    children: [
      {
        path: '/:id',
        middleware: [
          async (ctx, params, next) => {
            console.log('User ID middleware', params.id);
            return next();
          }
        ],
        action: async (ctx, params) => `User profile ${params.id}`
      }
    ]
  }
];

Запрос к /user/42 вызовет:

  1. User middleware
  2. User ID middleware 42
  3. User profile 42

Асинхронные middleware

Universal Router полностью поддерживает асинхронные функции:

async function authMiddleware(context, params, next) {
  const authorized = await checkUserAuth(context.user);
  if (!authorized) return '/login';
  return next();
}

Асинхронные операции, такие как запросы к API или базе данных, могут безопасно выполняться до вызова action маршрута.


Передача данных через context

Middleware может модифицировать объект context, добавляя новые свойства, которые будут доступны следующим middleware и action:

async function addTimestamp(context, params, next) {
  context.timestamp = Date.now();
  return next();
}

async function showTimestamp(context) {
  return `Timestamp: ${context.timestamp}`;
}

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

Если middleware выбрасывает исключение, роутер передаст его наружу. Для обработки ошибок можно использовать глобальные middleware или оборачивать middleware в try/catch:

async function safeMiddleware(context, params, next) {
  try {
    return await next();
  } catch (err) {
    console.error('Middleware error', err);
    return '/error';
  }
}

Использование middleware для редиректов

Middleware может возвращать строку URL, объект с redirect, либо промис:

async function redirectIfNotLogged(context, params, next) {
  if (!context.user) return '/login';
  return next();
}

При возврате URL роутер автоматически перенаправляет пользователя.


Практические рекомендации

  • Middleware лучше использовать для повторяющихся задач: проверка авторизации, логирование, подготовка данных.
  • Минимизировать тяжёлые операции в каждом middleware, чтобы не снижать производительность роутинга.
  • Всегда вызывать next() в middleware, если требуется продолжение цепочки.
  • Использовать вложенные middleware для разделения логики родительских и дочерних маршрутов.

Middleware на уровне маршрута в Universal Router предоставляет мощный инструмент контроля над маршрутизацией, позволяя внедрять проверку, модификацию данных и асинхронную обработку до вызова основной логики маршрута. Такой подход обеспечивает гибкость и модульность при построении сложных приложений.