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

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

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

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

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


Подключение middleware к маршрутам

Middleware можно подключать глобально ко всем маршрутам или к отдельным маршрутам.

Глобальная middleware:

page(logger); // применится ко всем маршрутам

page('/home', homePage);
page('/about', aboutPage);

page.start();

В этом примере функция logger будет вызываться при каждом переходе по любому маршруту.

Middleware для конкретного маршрута:

page('/dashboard', authCheck, dashboardPage);

Здесь функция authCheck будет выполнена только при переходе на /dashboard.


Создание цепочек middleware

Page.js поддерживает использование нескольких middleware на одном маршруте. Они выполняются последовательно в порядке объявления.

page('/profile', authCheck, loadUserData, renderProfile);

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

function loadUserData(ctx, next) {
    fetch(`/api/user/${ctx.user.id}`)
        .then(res => res.json())
        .then(data => {
            ctx.userData = data;
            next();
        });
}

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


Управление асинхронностью

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

function fetchData(ctx, next) {
    fetch('/api/items')
        .then(response => response.json())
        .then(data => {
            ctx.items = data;
            next();
        })
        .catch(err => console.error(err));
}

Если требуется прервать дальнейшее выполнение, можно просто не вызывать next() или использовать page.redirect() для перенаправления.


Глобальные vs локальные middleware

Глобальные middleware применяются ко всем маршрутам. Их удобно использовать для:

  • Логирования всех переходов
  • Общих проверок авторизации
  • Управления глобальными состояниями

Локальные middleware привязаны к конкретным маршрутам. Их используют для:

  • Загрузки данных, специфичных для страницы
  • Валидации параметров маршрута
  • Настройки локального состояния страницы

Примеры реальных сценариев

  1. Аутентификация и защита маршрутов:
function requireAuth(ctx, next) {
    if (!ctx.user) {
        page.redirect('/login');
    } else {
        next();
    }
}

page('/settings', requireAuth, renderSettings);
  1. Загрузка данных перед рендером страницы:
page('/products/:id', loadProduct, renderProduct);

function loadProduct(ctx, next) {
    const id = ctx.params.id;
    fetch(`/api/products/${id}`)
        .then(res => res.json())
        .then(product => {
            ctx.product = product;
            next();
        });
}
  1. Логирование и аналитика:
page((ctx, next) => {
    console.log(`Переход на ${ctx.path} в ${new Date().toISOString()}`);
    next();
});

Порядок выполнения middleware

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

  1. Сначала выполняются глобальные middleware, подключенные через page(fn).
  2. Затем выполняются локальные middleware для конкретного маршрута.
  3. Наконец, вызывается основной обработчик маршрута.

Если middleware не вызывает next(), цепочка останавливается, и основной обработчик не выполняется.


Доступ к параметрам маршрута и контексту

В объекте ctx доступны следующие ключевые данные:

  • ctx.path — текущий путь маршрута
  • ctx.params — объект с параметрами маршрута
  • ctx.querystring — строка запроса после ?
  • ctx.state — можно использовать для передачи данных между middleware
function addTimestamp(ctx, next) {
    ctx.state.timestamp = Date.now();
    next();
}

Использование ctx.state позволяет middleware обмениваться данными без глобальных переменных.


Прерывание маршрута и редирект

Middleware может полностью остановить выполнение маршрута:

function blockAccess(ctx, next) {
    if (ctx.user.role !== 'admin') {
        page.redirect('/not-authorized');
    } else {
        next();
    }
}

В этом примере, если условие не выполнено, последующие middleware и обработчик маршрута не вызовутся, а пользователь будет перенаправлен на страницу /not-authorized.


Советы по организации middleware

  • Разделять middleware по назначению: авторизация, загрузка данных, логирование.
  • Сохранять порядок вызова, чтобы асинхронные операции выполнялись корректно.
  • Использовать ctx.state для передачи данных между middleware, вместо глобальных переменных.
  • Проверять все возможные сценарии остановки маршрута (например, редиректы).

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