Docs/Middlewarev1.0.0-rc.1

Middleware Overview

Package: @tagna/udiot v1.0.0-rc.1 Import path: @tagna/udiot


What Is Middleware?

A middleware is a single async function that receives a context object and a next callback:

type MiddlewareFunction<TContext extends BaseContext = BaseContext> = (
  context: TContext,
  next: () => Promise<void>
) => Promise<void>;

Every middleware follows this exact shape regardless of whether it is processing an HTTP request, a state transition, a DOM event, or a background job. The uniformity is deliberate: the same composition patterns, the same error handling, and the same performance tracking work across all workloads.


The Pipeline Metaphor

A pipeline is an ordered chain of middleware functions. A context object enters the left end of the chain and travels right, passing through each function in turn.

           context enters
                │
                ▼
┌───────────────────────────────────────────────────┐
│  cors → auth → rateLimit → logging → handler     │
│   │        │        │          │          │       │
│   └────────┴────────┴──────────┴──────────┘       │
│                  context flows right              │
└───────────────────────────────────────────────────┘
                │
                ▼
           result exits

Each middleware:

  1. Receives the shared context object (read from it, write to it).
  2. Calls await next() to hand off control to the next function in the chain.
  3. Optionally executes code after next() returns (post-processing, cleanup, timing).

This produces an onion-like execution model: middleware that wraps await next() in a try/catch can intercept errors from all downstream functions.


Context Types

UDIOT ships four concrete context types. All extend BaseContext.

Type Use case Key fields
HttpContext HTTP request/response middleware request, response, user, params, query, body, headers
StateContext<TState> State management action middleware store, action, prevState
EventContext DOM or custom event middleware event, source
GenericContext Background jobs, arbitrary workloads data, type

See contexts.md for full field definitions and factory functions.


Why a Universal Pipeline?

Traditional frameworks provide middleware only for HTTP. UDIOT generalizes the pattern so you can use the same composable, observable, error-safe pipeline for:

  • HTTP requests — authentication, rate limiting, CORS, validation, response caching.
  • State transitions — logging state changes, enforcing invariants, async side effects.
  • Browser events — analytics capture, permission checks, input sanitization.
  • Background jobs — retry logic, timeout guards, telemetry.

All four scenarios use MiddlewarePipeline, the same composition utilities, and the same PipelineResult return value.


Execution Order

Middleware runs in the order it is registered (subject to priority overrides). The full execution of a three-middleware pipeline looks like this:

pipeline.execute(ctx)
│
├─► mw1 handler starts
│     ├─► await next()
│     │     ├─► mw2 handler starts
│     │     │     ├─► await next()
│     │     │     │     ├─► mw3 handler starts
│     │     │     │     │     └─► (no next call, or end of chain)
│     │     │     │     └─► mw3 handler ends
│     │     │     └─► mw2 post-next code runs
│     │     └─► mw2 handler ends
│     └─► mw1 post-next code runs
└─► PipelineResult returned

Code before await next() runs on the way in. Code after await next() runs on the way out.


Short-Circuit Pattern

A middleware that does not call next() terminates the chain at that point. All downstream middleware are skipped and the pipeline returns with the context's current result.

const guardMiddleware: MiddlewareFunction<HttpContext> = async (ctx, next) => {
  if (!ctx.user) {
    ctx.response.status(401);
    ctx.result = { error: 'Unauthorized' };
    return; // next() is never called — chain stops here
  }
  await next(); // authenticated, continue
};

This is how authMiddleware, rateLimitingMiddleware, validationMiddleware, and cachingMiddleware all work when they need to reject a request early.


Error Propagation

Errors thrown by any middleware propagate up the call stack through all the await next() calls above them. The pipeline catches the error at the top level, records it in PipelineResult.error, and sets PipelineResult.success = false.

Middleware wrapping await next() in a try/catch can handle errors from all downstream middleware. The built-in errorHandlerMiddleware uses exactly this pattern.

The pipeline itself has four configurable error-recovery strategies (halt, continue, retry, fallback) that govern what happens at the pipeline level when a middleware throws. See pipeline.md for details.


  • pipeline.md — MiddlewarePipeline class reference, PipelineOptions, PipelineResult
  • contexts.md — All context types, factory functions, custom contexts
  • composition.md — compose, when, parallel, tryCatch, timeout, memoize, skip, measure
  • built-in.md — Authentication, logging, validation, rate limiting, CORS, compression, caching, error handling
From the canonical UDIOT public documentation.Try it in the playground ↗