Создание middleware функций

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

Middleware в Universal Router реализуются через функцию resolveRoute или цепочку вызовов внутри resolve. Основная идея заключается в том, что каждый middleware получает текущий контекст маршрута и функцию next, которая передаёт управление следующему обработчику.

const router = new UniversalRouter(routes, {
  resolveRoute: async (context, next) => {
    console.log('Middleware before route');
    const result = await next();
    console.log('Middleware after route');
    return result;
  }
});

Здесь context содержит всю информацию о текущем запросе, включая путь, параметры, query и другие данные, передаваемые в маршруты. Вызов next() позволяет передать управление следующему middleware или самому маршруту.


Создание нескольких middleware

Можно определить несколько middleware, которые будут выполняться последовательно. Порядок их определения критически важен, так как Universal Router обрабатывает их сверху вниз.

const logger = async (context, next) => {
  console.log('Путь запроса:', context.pathname);
  return next();
};

const auth = async (context, next) => {
  if (!context.user) {
    throw new Error('Неавторизованный доступ');
  }
  return next();
};

const router = new UniversalRouter(routes, {
  resolveRoute: async (context, next) => {
    return logger(context, () => auth(context, () => next()));
  }
});

В этом примере сначала выполняется логгер, затем проверка авторизации, и только после этого — сам маршрут. Такая композиция позволяет строить гибкие цепочки обработки запросов.


Контекст маршрута и его расширение

context является ключевым объектом для middleware. Его можно расширять, добавляя свойства, которые понадобятся в других middleware или маршрутах.

const addRequestTime = async (context, next) => {
  context.requestTime = Date.now();
  return next();
};

const router = new UniversalRouter(routes, {
  resolveRoute: async (context, next) => {
    return addRequestTime(context, () => next());
  }
});

После выполнения middleware context.requestTime будет доступен в любом маршруте, что позволяет, например, логировать время запроса или управлять кэшированием.


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

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

const errorHandler = async (context, next) => {
  try {
    return await next();
  } catch (err) {
    console.error('Ошибка маршрута:', err.message);
    return { message: 'Произошла ошибка на сервере' };
  }
};

const router = new UniversalRouter(routes, {
  resolveRoute: async (context, next) => {
    return errorHandler(context, () => next());
  }
});

Таким образом можно обрабатывать ошибки в одном месте, не дублируя обработку в каждом маршруте.


Асинхронные операции в middleware

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

const fetchUserData = async (context, next) => {
  context.user = await getUserFromApi(context.params.userId);
  return next();
};

Асинхронные middleware должны всегда возвращать результат вызова next(), чтобы цепочка продолжалась корректно. Если next() не вызывается, маршрут или последующие middleware не будут выполнены.


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

  1. Авторизация и проверка прав — проверка токенов, ролей пользователя, уровня доступа.
  2. Логирование и мониторинг — запись запросов, времени выполнения, статистики.
  3. Расширение контекста — добавление данных, необходимых в маршрутах.
  4. Обработка ошибок — централизованная обработка исключений.
  5. Асинхронная подготовка данных — предварительная загрузка данных перед рендерингом маршрута.

Комбинирование middleware с маршрутизируемыми компонентами

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

const routes = [
  {
    path: '/profile/:id',
    async action(context) {
      await fetchUserData(context, () => {});
      return `
Привет, ${context.user.name}
`; } } ];

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