Обработка HTTP запросов

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

Каждый маршрут в Universal Router описывается объектом вида:

const route = {
  path: '/users/:id',
  action: ({ params, query }) => {
    return `Пользователь с ID: ${params.id}`;
  }
};

Здесь ключевые элементы:

  • path — путь маршрута. Может содержать динамические сегменты (:id), регулярные выражения или wildcard (*).
  • action — функция, выполняемая при совпадении маршрута. Получает объект контекста, содержащий параметры пути, query-параметры и дополнительные данные.

Объект контекста

При обработке HTTP-запроса Universal Router формирует объект context, который передаётся во все функции action. Стандартная структура:

{
  pathname: '/users/123',
  query: { search: 'test' },
  params: { id: '123' },
  route: { path: '/users/:id', action: [Function] },
  baseUrl: ''
}
  • pathname — путь запроса без query-параметров.
  • query — объект query-параметров, если они были переданы.
  • params — объект параметров маршрута, полученных из динамических сегментов.
  • route — объект маршрута, который был сопоставлен.
  • baseUrl — базовый URL, полезный при вложенной маршрутизации.

Контекст может быть расширен дополнительными данными, например, для передачи состояния аутентификации или сервисов.

Сопоставление маршрутов

Universal Router использует последовательное сопоставление маршрутов. Каждый маршрут проверяется в порядке объявления до первого успешного совпадения. Для сложных случаев применяется вложенная маршрутизация:

const router = new UniversalRouter([
  {
    path: '/users',
    children: [
      {
        path: ':id',
        action: ({ params }) => `Пользователь ${params.id}`
      }
    ]
  }
]);

Вложенные маршруты позволяют:

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

Динамические параметры и регулярные выражения

Маршруты могут содержать параметры, которые автоматически извлекаются в context.params. Форматы:

  • Простые параметры: /users/:idparams.id
  • Параметры с регулярными выражениями: /files/:name([a-z]+) → сопоставляет только буквы
  • Wildcard: /files/* → сопоставляет любое количество сегментов, результат доступен как params['*']

Пример с регулярным выражением:

{
  path: '/posts/:year(\\d{4})/:month(\\d{2})',
  action: ({ params }) => `Пост за ${params.month}/${params.year}`
}

Асинхронные действия

action может возвращать промис, что позволяет выполнять асинхронные операции, например, запросы к базе данных или API:

const route = {
  path: '/data/:id',
  action: async ({ params }) => {
    const response = await fetch(`/api/data/${params.id}`);
    return await response.json();
  }
};

Universal Router автоматически обрабатывает промисы, возвращая результат при завершении.

Навигация и обработка ошибок

Для обработки ошибок и отсутствующих маршрутов используется свойство action в корневом маршруте или глобальные обработчики:

const router = new UniversalRouter([
  {
    path: '/users/:id',
    action: ({ params }) => `User ${params.id}`
  },
  {
    path: '(.*)', // wildcard для всех остальных путей
    action: () => 'Страница не найдена'
  }
]);

Такой подход обеспечивает:

  • Гибкое управление 404-страницами.
  • Лёгкую обработку исключений при асинхронных действиях.
  • Возможность переадресации при необходимости.

Встраивание в HTTP-сервер

Universal Router легко интегрируется с серверными фреймворками, например, Express:

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

const app = express();

const routes = [
  {
    path: '/users/:id',
    action: ({ params }) => `Пользователь ${params.id}`
  }
];

const router = new UniversalRouter(routes);

app.use(async (req, res) => {
  try {
    const result = await router.resolve({ pathname: req.path, query: req.query });
    res.send(result);
  } catch (err) {
    res.status(404).send('Not Found');
  }
});

app.listen(3000);

Здесь resolve принимает объект контекста с pathname и query и возвращает результат выполнения маршрута. Ошибки маршрутизации обрабатываются через try/catch.

Middleware и расширение контекста

Universal Router не имеет встроенной концепции middleware, но её можно реализовать через обёртки action или вложенные маршруты:

const authMiddleware = async (context, next) => {
  if (!context.user) throw new Error('Не авторизован');
  return next();
};

const routes = [
  {
    path: '/dashboard',
    action: async (context) => authMiddleware(context, async () => 'Dashboard')
  }
];

Такой подход позволяет:

  • Проверять права доступа до выполнения основной логики.
  • Создавать цепочки действий с модификацией контекста.
  • Сохранять чистоту маршрутов без дублирования кода.

Работа с query-параметрами

Query-параметры автоматически передаются в объект context.query. Их можно использовать для фильтрации или сортировки данных:

{
  path: '/search',
  action: ({ query }) => `Поиск по: ${query.q || 'ничего'}`,
}

Если нужно, query-параметры можно преобразовать или валидировать перед основной обработкой маршрута, расширяя контекст.

Поддержка Base URL

Universal Router поддерживает базовые URL, что полезно при вложенных маршрутах или при деплое приложения в подкаталогах:

const router = new UniversalRouter(routes, { baseUrl: '/app' });

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


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