Условное применение middleware

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

Базовая сигнатура middleware:

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

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

Middleware получает доступ к объекту Request, контексту запроса и управляет тем, будет ли вызван следующий обработчик. Это делает middleware центральным механизмом для аутентификации, логирования, модификации заголовков, управления доступом и других поперечных задач.

Что означает условное применение middleware

Условное применение middleware — это избирательное выполнение логики middleware в зависимости от контекста запроса: пути, метода, заголовков, состояния пользователя, окружения или любых других факторов.

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

Ключевая идея: middleware вызывается всегда, но его поведение может быть условным.

Проверка пути запроса

Самый распространённый сценарий — выполнение middleware только для определённых URL.

export async function handler(
  req: Request,
  ctx: MiddlewareHandlerContext,
) {
  const url = new URL(req.url);

  if (!url.pathname.startsWith("/admin")) {
    return await ctx.next();
  }

  // логика только для /admin
}

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

Условие по HTTP-методу

Fresh middleware часто используется для ограничения действий по методу запроса.

if (req.method !== "POST") {
  return new Response("Method Not Allowed", { status: 405 });
}

Комбинация метода и пути позволяет точно контролировать поведение API, не смешивая валидацию с бизнес-логикой маршрута.

Аутентификация и авторизация

Условная проверка прав — один из самых показательных примеров.

const token = req.headers.get("authorization");

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

При этом возможно более тонкое управление:

  • пропуск middleware для публичных маршрутов;
  • разная логика для API и страниц;
  • проверка ролей только при доступе к определённым сегментам.
if (url.pathname.startsWith("/api/private")) {
  // строгая проверка
}

Использование состояния контекста

Fresh позволяет передавать данные между middleware и обработчиком маршрута через ctx.state.

ctx.state.user = user;

Условность может быть построена на основе уже установленного состояния:

if (!ctx.state.user?.isAdmin) {
  return new Response("Forbidden", { status: 403 });
}

Это позволяет строить цепочки middleware, где каждый последующий зависит от результатов предыдущего.

Условное изменение ответа

Middleware может не только решать, пропускать ли запрос дальше, но и модифицировать ответ — также по условию.

const res = await ctx.next();

if (url.pathname.startsWith("/api")) {
  res.headers.set("Cache-Control", "no-store");
}

return res;

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

Работа с окружением и режимами

Часто middleware должно работать только в режиме разработки или только в продакшене.

if (Deno.env.get("ENV") !== "production") {
  console.log(req.method, req.url);
}

Это особенно полезно для логирования, отладки, метрик и экспериментальных функций.

Комбинирование условий

На практике условия редко бывают одиночными. Обычно используется комбинация факторов:

if (
  req.method === "POST" &&
  url.pathname.startsWith("/api") &&
  ctx.state.user
) {
  // специфичная логика
}

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

Разделение middleware по назначению

Хотя Fresh допускает размещение всей логики в одном middleware, условное применение часто сочетается с архитектурным разделением:

  • один middleware отвечает за аутентификацию;
  • другой — за локализацию;
  • третий — за заголовки безопасности.

Каждый из них содержит собственные условия, что упрощает сопровождение и тестирование.

Частичное прерывание цепочки

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

if (blocked) {
  return new Response("Blocked", { status: 403 });
}

При условном применении это особенно важно: отсутствие явного return ctx.next() должно быть результатом логического решения, а не побочным эффектом.

Отличие от маршрутизации

Условное middleware не заменяет маршруты. Маршруты отвечают за структуру приложения, middleware — за поперечные аспекты. Условность позволяет сохранить middleware универсальным, не привязывая его жестко к файловой системе маршрутов Fresh.

Типичные ошибки

  • выполнение тяжёлой логики до проверки условия;
  • забытый return ctx.next() в ветке условия;
  • дублирование одинаковых условий в разных middleware;
  • смешивание бизнес-логики маршрута с логикой доступа.

Правильно реализованное условное middleware остаётся тонким слоем контроля, а не центром приложения.

Архитектурная роль условного middleware

В Fresh условное применение middleware — это не workaround, а основной паттерн. Он позволяет:

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

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