Skip to main content
The @ooneex/middleware component is a pipeline framework for intercepting requests. Each middleware implements the IMiddleware interface (or ISocketMiddleware for WebSockets), receives the request context, and returns it — modified or unchanged — to the next stage. Register a class with @decorator.middleware() and the container resolves it; the app runs registered middleware in order before the controller handles the request.

Why this component

  • One interface. Every HTTP middleware implements IMiddleware with a single handler(context) method — predictable to write, read, and test.
  • Context in, context out. The same context object flows through every stage, carrying the request, response, and headers; you read and mutate it in place.
  • Short-circuiting. Write a response (context.response.json(...), context.response.exception(...)) to halt the pipeline before the controller — for auth, rate limits.
  • WebSocket support. ISocketMiddleware mirrors the HTTP shape against the socket context for connection-time interception.
  • Container-managed. Decorate a class and resolve it through dependency injection — constructor-inject env, loggers, or services.

How it works

The app holds an ordered list of middleware. For each incoming request it builds a context and passes it through every registered middleware in sequence, then to the matched controller. Each handler returns the context to continue the chain.
StageWhat happens
Before the controllerEach middleware’s handler runs in registration order, reading and mutating the shared context.
Short-circuitIf a middleware writes a response (e.g. context.response.exception(...) or context.response.json(...)), the pipeline can resolve early without reaching the controller.
At the controllerThe fully processed context (with any state a middleware attached, like a resolved user) reaches the route handler.
A middleware is “before” by doing its work at the top of handler and then returning context. Because the same context object carries the response, middleware can also shape the outgoing response (set headers, status) before returning. The contract is simple: always return the context so the chain continues.
public async handler(context: ContextType): Promise<ContextType> {
  // Pre-processing: inspect/modify request, attach state, set response headers
  return context; // hand off to the next middleware or the controller
}

Decorator and usage

@decorator.middleware()

Registers a class as middleware with the dependency injection container. It accepts an optional scope (defaults to singleton). The class must implement IMiddleware (HTTP) or ISocketMiddleware (WebSocket).
interface IMiddleware<T extends ContextConfigType = ContextConfigType> {
  handler: (context: ContextType<T>) => Promise<ContextType<T>> | ContextType<T>;
}
A real HTTP middleware reads the context, optionally writes to the response to short-circuit, and returns the context:
import type { ContextType } from "@ooneex/controller";
import { decorator, type IMiddleware } from "@ooneex/middleware";

@decorator.middleware()
export class AuthMiddleware implements IMiddleware {
  public async handler(context: ContextType): Promise<ContextType> {
    const authHeader = context.header.get("Authorization");

    if (!authHeader?.startsWith("Bearer ")) {
      // Short-circuit: write a response and the controller is skipped
      context.response.exception("Missing authorization header", { status: 401 });
      return context;
    }

    // Continue to the next middleware / controller
    return context;
  }
}
Inject dependencies through the constructor — the container provides them when it resolves the middleware:
import { AppEnv } from "@ooneex/app-env";
import { inject } from "@ooneex/container";
import type { ContextType } from "@ooneex/controller";
import { decorator, type IMiddleware } from "@ooneex/middleware";

@decorator.middleware()
export class VersionMiddleware implements IMiddleware {
  constructor(@inject(AppEnv) private readonly env: AppEnv) {}

  public async handler(context: ContextType): Promise<ContextType> {
    context.response.header.set("X-App-Version", this.env.APP_VERSION ?? "unknown");
    return context;
  }
}
WebSocket middleware uses the same decorator and shape against the socket context via ISocketMiddleware:
import type { ContextType } from "@ooneex/socket";
import { decorator, type ISocketMiddleware } from "@ooneex/middleware";

@decorator.middleware()
export class SocketAuthMiddleware implements ISocketMiddleware {
  public async handler(context: ContextType): Promise<ContextType> {
    return context;
  }
}

Best practices

  • Always return the context. The chain continues only when handler returns the context object; never return a new object in its place.
  • Mutate in place. Read from and write to the shared context — attach resolved state (like a user) for downstream middleware and the controller to use.
  • Short-circuit deliberately. Write a response only when you mean to stop the pipeline (auth failures, rate limits); otherwise fall through.
  • Keep middleware focused. One concern per class — auth, logging, rate limiting — so the registration order reads as a clear pipeline.
  • Mind the order. Middleware runs in registration order; put cross-cutting concerns (logging, headers) before guards that may short-circuit.
  • Inject, don’t reach. Pull env, loggers, and services through the constructor with @inject rather than constructing them inside handler.

CLI command

Scaffold a middleware class and its test file with the generator. It writes the class under modules/<module>/src/middlewares/<Name>Middleware.ts, adds a matching test, and installs @ooneex/middleware if it is missing.
# Interactive: prompts for the name and whether it is a socket middleware
ooneex middleware:create

# Provide the name
ooneex middleware:create --name=Auth

# Socket middleware in a specific module, overwriting an existing file
ooneex middleware:create --name=Auth --module=auth --is-socket=true --override
OptionDescriptionDefault
--nameMiddleware class name. The Middleware suffix is appended automatically.Prompted if omitted
--moduleTarget module the class is generated into.shared
--is-socketGenerate a WebSocket middleware against the socket context.Prompted if omitted
--overrideOverwrite an existing class without prompting.false
The generated class is a ready-to-implement IMiddleware stub:
import type { ContextType } from "@ooneex/controller";
import { decorator, type IMiddleware } from "@ooneex/middleware";

@decorator.middleware()
export class AuthMiddleware implements IMiddleware {
  public async handler(context: ContextType): Promise<ContextType> {
    // Example: Add custom header
    // context.response.header("X-Custom-Header", "value");

    return context
  }
}
A socket middleware stub is identical except it imports ContextType from @ooneex/socket. After generating, register the class in the middlewares array of your module. See middleware:create for the full command reference.

Use with Claude and Codex

The generator ships a matching middleware:create skill. It runs the scaffold and then guides your AI agent through completing the middleware — implementing handler with real logic (auth checks, logging, header injection), wiring dependencies through the constructor, and registering the class in its module. Initialize the skills once for your agent:
ooneex claude:init
Then ask Claude in natural language — it maps the request to the generator, runs it, and fills in the implementation:
Prompt
Create a middleware that logs every incoming request.
For example, the prompt above maps to middleware:create --name=Logging, then implements handler to log the request method and path before returning the context.