name: noony-middleware-development
description: Use when creating custom middleware, adding cross-cutting concerns, intercepting requests or responses, implementing before/after/onError lifecycle hooks, passing data between middlewares via businessData, or accessing DI services inside middleware. PREREQUISITE — read noony-middleware-ordering first for where to place your middleware, then come here for how to build it.
skill:noony-middleware-development
Does exactly this
Provides 5 patterns for building type-safe middleware: class-based, factory functions, DI-aware, conditional logic, and inter-middleware communication via context.businessData. All with proper <TBody, TUser> generics. This skill is the BRIDGE between the pipeline order (noony-middleware-ordering) and your custom logic.
When to use
- "Create custom middleware"
- "Add cross-cutting concerns" (logging, timing, caching, auditing)
- "Intercept requests/responses"
- "Implement before/after/onError logic"
- "Pass data between middlewares"
- "Access DI services in middleware"
Do not use this skill when
- You need middleware ordering guidance ->
noony-middleware-orderingis the authority on positioning - You need body validation schemas ->
noony-validation-schemasfor Zod integration - You need error class selection ->
noony-error-handlingfor error types and cause chaining - You need type inference guidance ->
noony-type-inferencefor generics flow - For built-in middleware configuration -> see individual middleware skills:
noony-validation-schemas,noony-error-handling,noony-dependency-injection,noony-guard-system
Steps
- Read
noony-middleware-orderingfirst to determine where your middleware belongs in the canonical order — ordering determines when yourbefore/after/onErrorhooks run relative to other middleware - Define middleware class implementing
BaseMiddleware<TBody, TUser>— both generics required to preserve the type chain (seenoony-type-inferencefor why this matters) -> Seereferences/middleware-patterns.md#pattern-1-class-based-middlewarefor structure - Implement lifecycle hooks as needed:
before(context)— preprocessing, runs top-to-bottom in registration orderafter(context)— postprocessing, runs bottom-to-top (reverse order)onError(context, error)— error handling, runs bottom-to-top (reverse order)- All hooks are optional — implement only what you need
- Use
context.businessDataMap for inter-middleware communication — never extend Context interface -> Seereferences/middleware-patterns.md#pattern-5-inter-middleware-communication-via-businessdata - Access injected services via
getService(context, ServiceClass)helper — middleware does not need to know how the service was created -> Seereferences/middleware-patterns.md#pattern-3-middleware-with-dependency-injection - Use factory functions for simpler, stateless middleware that needs configuration parameters
-> See
references/middleware-patterns.md#pattern-2-factory-function-middleware - Register your middleware in the canonical order from
noony-middleware-ordering— place it at the correct position relative to ErrorHandler (position 1), validation (positions 6-7), auth (positions 9-12), and ResponseWrapper (last)
Rules
- MANDATORY:
implements BaseMiddleware<TBody, TUser>with both generics — omitting them silently breaks type inference forvalidatedBodyanduserdownstream - Default generic values:
<TBody = unknown, TUser = unknown>— allows optional type specification at usage site - Inter-middleware data ONLY via
context.businessData.set(key, value)— never modify Context properties directly - Use descriptive, namespaced businessData keys to avoid collisions —
'otel_span'is reserved byOpenTelemetryMiddleware - All lifecycle methods are optional — implement only what you need
- Middleware must not have side effects on framework state — context properties like
userandreqare read-only - Consult
noony-middleware-orderingfor where to register your middleware — position determines execution timing
Anti-patterns
- Writing middleware without reading
noony-middleware-orderingfirst — ordering determines when your hooks run; wrong position = wrong behavior BaseMiddlewarewithout generics — breaks type chain silently;context.req.validatedBodyandcontext.userbecomeunknown- Extending Context interface (
interface CustomContext extends Context) — not portable, breaks framework compatibility - Returning data from
before()— return value is ignored by the framework, usebusinessDatainstead - Mutating
context.userorcontext.req.bodydirectly — these are read-only; usebusinessDatafor custom data - Duplicate
businessDatakeys across middlewares — second write silently overwrites first, causing lost data - Heavy logic in
onErrorwithout guard clauses —onErrorfires for every error type, check error class before acting
Done when
- You have read
noony-middleware-orderingand know where your middleware sits in the canonical order - You can write class-based middleware with proper
<TBody, TUser>generics - You understand lifecycle hook execution order (before: top-down, after/onError: bottom-up)
- You can pass data between middlewares via
businessData - You know how to access DI services in middleware via
getService() - You can test middleware in isolation using
createContext()
If you need more detail
-> references/middleware-patterns.md — 5 complete patterns (class-based, factory, DI-aware, conditional, inter-middleware), anti-patterns with code examples, testing examples with assertions