Middleware
ExisJS comes with a collection of built-in middleware out of the box.
ExisJS provides first-party implementations for features like CORS, Request Logging, Backpressure, In-Memory Rate Limiting, Idempotency, and Deduplication.
All middleware can be imported directly from the exisjs/middleware package.
Where to Apply Middleware
ExisJS gives you granular control over where your middleware executes:
- Globally (
server.ts): Applies to every request hitting your server viaapp.use(). - Folder-Scoped (
boundary.ts): Applies to routes inside that folder and all subfolders automatically. - Route-Scoped (
route.ts): Applies only to a specific endpoint.
import { defineBoundary } from 'exisjs/router'import type { Request, Response, Next } from 'exisjs/router'import { requestLogger } from 'exisjs/middleware'
export const config = defineBoundary({})
// Auto-detected chain step for /admin/*export const logger = requestLogger({ level: 'debug' })Middleware vs Inline Execution
Middleware executes before your route handler. If you are using custom typed contexts to destructure variables (e.g., async handle({ user })), you must use middleware to populate req.user.
Functional Paradigm
import { controller, route } from 'exisjs/router'import { validate } from 'exisjs/middleware'import { tex } from 'exisjs/validator'
const mySchema = { body: tex.object({ name: tex.string() })}
export default controller({ create: route.post('/', { middlewares: [validate(mySchema)], handle({ req }) { return { ok: true, name: req.body.name } } })})Class-Based (OOP) Paradigm
import { Controller, Post, Use } from 'exisjs/decorators'import { validate } from 'exisjs/middleware'import { tex } from 'exisjs/validator'
const mySchema = { body: tex.object({ name: tex.string() })}
@Controller()export default class MyController { @Use(validate(mySchema)) @Post('/') async create() { return { ok: true } }}1. CORS
The cors middleware handles Cross-Origin Resource Sharing.
The recommended practice in ExisJS is to configure CORS globally in your exis.config.ts file or per-folder in boundary.ts.
import { defineConfig } from 'exisjs/config'
export default async () => defineConfig({ cors: { origin: ['https://myapp.com', /localhost:\d+/], methods: ['GET', 'POST', 'OPTIONS'], credentials: true, maxAge: 86400, }})2. Request Logger
ExisJS uses Pino under the hood. The requestLogger injects a request-scoped logger instance (req.log) and times response cycles automatically.
import { requestLogger } from 'exisjs/middleware'
app.use(requestLogger({ level: 'debug', pretty: true}))3. Request ID
Injects a unique X-Request-Id into every incoming request and outgoing response header. Supports distributed tracing headers (traceparent, x-b3-traceid).
import { requestId } from 'exisjs/middleware'
app.use(requestId())4. Rate Limiting (In-Memory / Native Off-Heap)
ExisJS provides high-performance rate limiting powered by Rust native memory:
import { rateLimit } from 'exisjs/middleware'
export const limiter = rateLimit({ windowMs: 60000, max: 100,})[!NOTE]
Core rate limiting runs completely off-heap in-memory. For Redis-backed distributed rate limiting across clusters, use @exisjs/cache.
5. Idempotency (In-Memory / Native Off-Heap)
Idempotency caches responses based on the Idempotency-Key header, preventing duplicate mutations:
import { Controller, Post, Idempotent } from 'exisjs/decorators'
@Controller('/checkout')export default class CheckoutController { @Idempotent({ ttlMs: 86400000 }) @Post('/') async checkout() { return { success: true } }}6. Request Deduplication (dedupe)
If multiple clients request the exact same resource concurrently, dedupe collapses identical in-flight requests into a single operation:
import { controller, route } from 'exisjs/router'import { dedupe } from 'exisjs/middleware'
export default controller({ getStats: route.get('/', { middlewares: [dedupe({ ttlMs: 5000 })], async handle() { return { activeUsers: 1000 } } })})7. Static File Serving
A static file server that resolves MIME types and streams files:
import { serveStatic } from 'exisjs/middleware'import path from 'node:path'
app.use('/public', serveStatic(path.join(process.cwd(), 'public'), { maxAge: 31536000}))8. Compression (Gzip / Brotli / Deflate)
Stream-based compression middleware supporting Gzip, Deflate, and Brotli:
import type { ExisConfig } from 'exisjs/config'
const config: ExisConfig = { compression: true,}
export default config