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

Асинхронные middleware в Fresh строятся вокруг минималистичной, но выразительной модели обработки запросов, унаследованной от экосистемы Deno и философии Web Standards. Middleware в Fresh — это функции, которые встраиваются в жизненный цикл HTTP-запроса и позволяют выполнять асинхронную логику до передачи управления обработчику маршрута или после получения ответа.

Middleware представляет собой асинхронную функцию, принимающую контекст запроса и функцию next, которая передаёт управление следующему middleware или конечному обработчику.

import { MiddlewareHandlerContext } from "$fresh/server.ts";

export async function handler(
  req: Request,
  ctx: MiddlewareHandlerContext,
) {
  return await ctx.next();
}

Ключевая особенность — строгая ориентация на стандартный объект Request и возвращаемый Response. Middleware не оперирует абстракциями уровня фреймворка HTTP-сервера, а работает напрямую с web-API.

Асинхронная природа и управление потоком

Каждый middleware является асинхронным по определению. Это позволяет:

  • выполнять неблокирующие операции ввода-вывода;
  • обращаться к базам данных;
  • запрашивать внешние API;
  • читать cookies и заголовки;
  • модифицировать ответ после выполнения обработчика.

Управление потоком основано на явном вызове ctx.next(). Пока next не вызван, дальнейшая цепочка не выполняется.

const response = await ctx.next();
return response;

Такой подход даёт полный контроль над тем, когда и будет ли выполнен следующий шаг.

Middleware до и после обработчика

Асинхронность позволяет использовать middleware как «обёртку» вокруг основной логики.

Выполнение до обработчика

console.log("before handler");
const response = await ctx.next();
return response;

Типичные задачи:

  • аутентификация;
  • проверка прав доступа;
  • логирование запроса;
  • модификация Request через ctx.state.

Выполнение после обработчика

const response = await ctx.next();
response.headers.set("X-Custom", "value");
return response;

Используется для:

  • добавления заголовков;
  • кеширования;
  • логирования ответа;
  • обработки ошибок.

Контекст и ctx.state

ctx.state — общий объект состояния, передаваемый между middleware и конечным обработчиком. Он идеально подходит для хранения данных, полученных асинхронно.

ctx.state.user = await getUserFromSession(req);

В обработчике маршрута:

const user = ctx.state.user;

Состояние существует только в рамках одного запроса и не требует синхронизации.

Композиция и порядок выполнения

Middleware в Fresh применяются иерархически, на основе файловой структуры:

  • routes/_middleware.ts — глобальный middleware;
  • routes/admin/_middleware.ts — применяется только к /admin/*;
  • порядок выполнения — от корня к вложенным маршрутам.

Асинхронные middleware образуют стек вызовов:

  1. глобальный middleware (до next)
  2. вложенные middleware (до next)
  3. обработчик маршрута
  4. вложенные middleware (после next)
  5. глобальный middleware (после next)

Это поведение аналогично стеку вызовов и позволяет реализовывать сложную логику без дополнительных инструментов.

Обработка ошибок в асинхронных middleware

Ошибки перехватываются стандартным механизмом try/catch.

try {
  return await ctx.next();
} catch (err) {
  return new Response("Internal Error", { status: 500 });
}

Такой middleware может выступать в роли глобального error boundary, перехватывая исключения из любых нижележащих обработчиков.

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

Прерывание цепочки middleware

Middleware не обязан вызывать ctx.next(). Это позволяет полностью остановить обработку запроса.

if (!authorized) {
  return new Response("Unauthorized", { status: 401 });
}

Подобное поведение используется для:

  • защиты маршрутов;
  • раннего возврата ответа;
  • оптимизации, когда дальнейшая обработка не требуется.

Асинхронные побочные эффекты

Асинхронность middleware позволяет запускать фоновые задачи, не блокируя ответ:

ctx.next().then(() => {
  logRequestAsync(req);
});

При этом важно учитывать, что жизненный цикл запроса в Deno может завершиться до окончания фоновой задачи, если она не удерживает event loop.

Типизация и безопасность

Fresh предоставляет строгую типизацию через MiddlewareHandlerContext, что особенно важно при асинхронной передаче данных через ctx.state. Это снижает вероятность ошибок при доступе к данным, установленным в предыдущих middleware.

interface State {
  user?: User;
}

export async function handler(
  req: Request,
  ctx: MiddlewareHandlerContext<State>,
) {
  ctx.state.user = await fetchUser();
  return await ctx.next();
}

Архитектурные преимущества

Асинхронные middleware в Fresh:

  • не требуют сторонних библиотек;
  • полностью совместимы с web-standards;
  • легко тестируются;
  • масштабируются за счёт файловой структуры;
  • не навязывают паттерны, но поддерживают композицию.

Минимализм API в сочетании с асинхронной моделью делает middleware в Fresh мощным инструментом для построения серверной логики без избыточной абстракции.