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:

OptionTypeDescription
originstring | string[] | ((origin: string) => boolean)Allowed origins
methodsstring[]Allowed HTTP methods
allowedHeadersstring[]Allowed request headers
exposedHeadersstring[]Headers exposed to the browser
credentialsbooleanAllow credentials
maxAgenumberPreflight 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)