Middleware
Transform requests and responses using the Koa-style onion model.
MiiaJS uses a Koa-style onion model for middleware. A single compose() function replaces the need for separate interceptors, pipes, and filters.
Middleware signature
type Middleware = (
ctx: RequestContext,
next: () => Promise<void>,
) => Promise<void>
Every middleware must call await next() to pass control to the next middleware, or skip it to short-circuit the pipeline.
Onion model
Middleware wraps around the handler, executing code before and after:
Request -> [MW1 before] -> [MW2 before] -> [Handler] -> [MW2 after] -> [MW1 after] -> Response
const timing: Middleware = async (ctx, next) => {
const start = Date.now()
await next()
const duration = Date.now() - start
ctx.res.header('X-Response-Time', `${duration}ms`)
}
Global middleware
Apply middleware to all routes with app.use():
import { Miia, cors } from '@miiajs/core'
const app = new Miia()
.use(cors())
.use(timing)
.register(AppModule)
Global middleware executes in the order it's registered.
Controller middleware
Apply middleware to all routes in a controller with the @Use() decorator:
import { Controller, Use } from '@miiajs/core'
@Controller('/api')
@Use(loggingMiddleware, authMiddleware)
class ApiController {
// All methods get loggingMiddleware and authMiddleware
}
Route middleware
Apply middleware to a specific route:
@Controller('/items')
class ItemController {
@Post('/')
@Use(validateMiddleware)
create(ctx: RequestContext) {
return ctx.body
}
}
Execution order
Global middleware (app.use())
-> Global guards (app.useGuard())
-> Controller middleware (@Use on class)
-> Route guards (@UseGuard on method)
-> Route middleware (@Use on method)
-> Validation (@ValidateBody, etc.)
-> Route handler
Built-in middleware
CORS
import { cors } from '@miiajs/core'
app.use(cors({
origin: ['https://example.com', 'https://app.example.com'],
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true,
maxAge: 86400,
}))
Options:
| Option | Type | Description |
|---|---|---|
origin | string | string[] | ((origin: string) => boolean) | Allowed origins |
methods | string[] | Allowed HTTP methods |
allowedHeaders | string[] | Allowed request headers |
exposedHeaders | string[] | Headers exposed to the browser |
credentials | boolean | Allow credentials |
maxAge | number | Preflight cache duration (seconds) |
Writing custom middleware
import type { Middleware, RequestContext } from '@miiajs/core'
const auth: Middleware = async (ctx, next) => {
const token = ctx.req.headers.get('authorization')
if (!token) {
throw new UnauthorizedException()
}
ctx.user = await verifyToken(token)
await next()
}
const errorHandler: Middleware = async (ctx, next) => {
try {
await next()
} catch (error) {
// Custom error handling
ctx.res.status(500).json({ error: 'Something went wrong' })
}
}
compose()
The compose() function combines multiple middleware into one:
import { compose } from '@miiajs/core'
const combined = compose([auth, logging, timing])
app.use(combined)