Типизация middleware

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


Основы middleware

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

  • Проверка авторизации;
  • Логирование запросов;
  • Обработка ошибок;
  • Модификация запроса или контекста перед передачей дальше.

Типичная сигнатура middleware:

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

export function authMiddleware(
  req: Request,
  ctx: MiddlewareHandlerContext,
  next: () => Promise<Response>,
): Promise<Response> {
  // Логика авторизации
  return next();
}

Ключевой момент: ctx является объектом, содержащим информацию о текущем запросе и состояние приложения. Его типизация критически важна для безопасной работы с пользовательскими данными и расширениями.


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

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

Пример расширения:

interface CustomContext extends MiddlewareHandlerContext {
  user?: { id: string; role: string };
}

export async function authMiddleware(
  req: Request,
  ctx: CustomContext,
  next: () => Promise<Response>,
) {
  const token = req.headers.get("Authorization");
  if (token) {
    ctx.user = { id: "123", role: "admin" }; // пример получения пользователя
  }
  return next();
}

Типизация контекста позволяет избежать ошибок доступа к полям user на этапе компиляции и обеспечивает поддержку автодополнения в редакторах.


Middleware цепочки и композиция

Middleware во Fresh можно комбинировать в цепочки, где каждый слой обрабатывает запрос и передает управление следующему через next(). Типизация функции next критически важна, так как она возвращает Promise<Response>. Ошибки в типах могут привести к некорректной работе цепочки или неверной обработке ответов.

Пример составной middleware:

export async function loggingMiddleware(
  req: Request,
  ctx: MiddlewareHandlerContext,
  next: () => Promise<Response>,
) {
  console.log(`${req.method} ${req.url}`);
  const response = await next();
  console.log(`Response status: ${response.status}`);
  return response;
}

export async function combinedMiddleware(
  req: Request,
  ctx: CustomContext,
  next: () => Promise<Response>,
) {
  await loggingMiddleware(req, ctx, async () => {
    return authMiddleware(req, ctx, next);
  });
}

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


Типизация запросов и ответов

Для сложных приложений важно явно указывать типы данных, получаемых в запросе и отправляемых в ответе. Fresh не накладывает жестких ограничений на это, но правильная типизация позволяет избежать runtime ошибок:

interface LoginRequestBody {
  username: string;
  password: string;
}

interface LoginResponseBody {
  token: string;
  expiresAt: string;
}

export async function loginMiddleware(
  req: Request,
  ctx: MiddlewareHandlerContext,
  next: () => Promise<Response>,
) {
  const body: LoginRequestBody = await req.json();
  if (body.username === "admin" && body.password === "123") {
    const responseBody: LoginResponseBody = { token: "abc", expiresAt: new Date().toISOString() };
    return new Response(JSON.stringify(responseBody), { status: 200 });
  }
  return next();
}

Использование интерфейсов для типов данных запроса и ответа повышает читаемость и надежность кода.


Практические рекомендации

  • Всегда типизировать ctx, особенно при расширении пользовательскими данными.
  • Явно указывать возвращаемый тип Promise<Response> для middleware.
  • При комбинировании middleware использовать строгую типизацию функции next.
  • Для данных запросов и ответов использовать интерфейсы или типы TypeScript для предотвращения несоответствий.
  • Следить за совместимостью типов между разными слоями middleware, чтобы избежать runtime-ошибок.

Итоговые паттерны

  1. Типизированный контекст:
interface CustomCtx extends MiddlewareHandlerContext {
  session?: string;
}
  1. Типизированный middleware:
export async function exampleMiddleware(
  req: Request,
  ctx: CustomCtx,
  next: () => Promise<Response>,
): Promise<Response> {
  ctx.session = "active";
  return next();
}
  1. Композиция middleware с сохранением типов:
await firstMiddleware(req, ctx, async () => {
  return secondMiddleware(req, ctx, next);
});

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