Middleware на сервере

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


Концепция middleware

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

  • Проверять авторизацию пользователя.
  • Выполнять предварительную подготовку данных.
  • Логировать запросы.
  • Управлять перехватом ошибок и перенаправлениями.

Функция middleware получает на вход контекст запроса и функцию next, вызывая которую можно передать управление следующему middleware или маршруту.

Простейший пример структуры middleware:

async function logger(context, next) {
  console.log(`Запрос к пути: ${context.path}`);
  return next();
}

Здесь context.path содержит путь запроса, а вызов next() передаёт управление следующему middleware или маршруту.


Регистрация middleware в маршрутах

Universal Router позволяет прикреплять middleware на уровне отдельных маршрутов или для всей маршрутизируемой структуры.

На уровне маршрута:

import UniversalRouter from 'universal-router';

const routes = [
  {
    path: '/admin',
    async action(context) {
      return 'Страница администратора';
    },
    middleware: [
      async function checkAdmin(context, next) {
        if (!context.user?.isAdmin) {
          throw new Error('Доступ запрещен');
        }
        return next();
      }
    ]
  }
];

const router = new UniversalRouter(routes);

В этом примере middleware checkAdmin выполняется перед action. Если условие не выполняется, next() не вызывается, и маршрут не будет обработан.

Глобальное middleware можно реализовать через вложенные маршруты или через обертку над Universal Router:

const router = new UniversalRouter(routes, {
  context: {},
  resolveRoute(context, route) {
    if (route.middleware) {
      const chain = route.middleware.reduceRight(
        (next, fn) => () => fn(context, next),
        () => route.action ? route.action(context) : null
      );
      return chain();
    }
    return route.action ? route.action(context) : null;
  }
});

Передача данных через контекст

Контекст (context) — это объект, который передается через цепочку middleware и до конечного маршрута. Он может содержать:

  • path — путь запроса.
  • query — объект с параметрами запроса.
  • user — данные о текущем пользователе.
  • Любые дополнительные свойства, необходимые для middleware.

Middleware может добавлять новые свойства или модифицировать существующие:

async function attachRequestId(context, next) {
  context.requestId = Date.now().toString();
  return next();
}

После выполнения этого middleware все последующие функции и маршруты будут иметь доступ к context.requestId.


Асинхронные middleware и обработка ошибок

Middleware в Universal Router поддерживают асинхронные операции. Это позволяет обращаться к базе данных, выполнять запросы к API или проверять авторизацию:

async function fetchUser(context, next) {
  context.user = await getUserFromToken(context.token);
  return next();
}

Ошибки можно обрабатывать на уровне middleware или на глобальном уровне через try/catch:

async function errorHandler(context, next) {
  try {
    return await next();
  } catch (err) {
    console.error(err);
    return { status: 500, message: err.message };
  }
}

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

Порядок middleware имеет критическое значение:

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

Применение на сервере

На серверной стороне Universal Router часто используют в связке с Node.js и фреймворками вроде Express или Koa. В этом случае middleware Universal Router интегрируется с серверным циклом обработки запроса:

import express from 'express';
import UniversalRouter from 'universal-router';

const app = express();

const router = new UniversalRouter(routes);

app.use(async (req, res) => {
  const context = { path: req.path, query: req.query };
  try {
    const result = await router.resolve(context);
    res.send(result);
  } catch (err) {
    res.status(500).send(err.message);
  }
});

app.listen(3000);

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


Практические советы

  • Middleware должны быть максимально легковесными и выполнять только одну задачу.
  • Асинхронные операции всегда должны оборачиваться в try/catch, чтобы предотвратить необработанные ошибки.
  • Использование контекста для передачи данных между middleware повышает читаемость и масштабируемость маршрутов.
  • Для сложных приложений рекомендуется создавать пакет middleware, который можно подключать к разным маршрутам без дублирования кода.

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