TypeDrop

2026-08-04 Challenge

2026-08-04 Medium

Typed Middleware Pipeline with Context Narrowing

You're building the request-processing core for an HTTP API gateway. Each incoming request passes through a chain of middleware functions that progressively enrich a shared context object — attaching a parsed auth token, validated body, rate-limit metadata, and more — before reaching the final route handler. The hardest part is making TypeScript track exactly which properties have been added to the context at each stage, so the final handler only compiles when all required enrichments are present.

Goals

  • Define and use the `Middleware<TIn, TOut>` generic type so each stage's output type flows into the next stage's input type.
  • Implement `createPipeline` with overloads for 1–4 middleware that thread the accumulated context type through the chain and short-circuit on the first failure.
  • Implement the four middleware factories (`makeAuthMiddleware`, `makeBodyParserMiddleware`, `makeBodyValidatorMiddleware`, `makeRateLimitMiddleware`) using bounded generics and intersection types to widen the context.
  • Implement `makeHandler` to wrap a pipeline and a typed handler function, returning a unified `HandlerResponse<TResponse>` discriminated union.
challenge.ts
// Core types — understand these before implementing

interface BaseContext {
  requestId: string;
  method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
  path: string;
  headers: Record<string, string>;
  startedAt: number;
}

type MiddlewareResult<TCtx> =
  | { ok: true; ctx: TCtx }
  | { ok: false; status: number; message: string };

// A middleware widens the context: output must extend input
type Middleware<TIn, TOut extends TIn> =
  (ctx: TIn) => Promise<MiddlewareResult<TOut>>;

// Pipeline threads types through each stage
declare function createPipeline<T0 extends BaseContext, T1 extends T0, T2 extends T1>(
  m0: Middleware<T0, T1>,
  m1: Middleware<T1, T2>,
): { run: (ctx: T0) => Promise<MiddlewareResult<T2>> };
Hints (click to reveal)

Hints

  • Each middleware factory should return `{ ...ctx, <newField>: value }` — TypeScript will infer the intersection type `TIn & AuthContext` from the spread if your return type annotation is correct.
  • For `createPipeline`'s implementation body, cast the rest parameter to `Middleware<BaseContext, BaseContext>[]` — the overloads above it provide precise types to callers; the implementation just needs to compile.
  • Use `result.ok` as a discriminant to narrow `MiddlewareResult<T>` — after `if (!result.ok) return result`, TypeScript knows the happy path has `result.ctx`.

Or clone locally

git clone -b challenge/2026-08-04 https://github.com/niltonheck/typedrop.git