Controllers

Handle incoming HTTP requests and define your API routes.

Controllers are responsible for handling incoming HTTP requests and returning responses. A controller groups related routes under a common path prefix.

Defining a controller

Use the @Controller() decorator to define a controller and its path prefix:

import type { RequestContext } from '@miiajs/core'
import { Controller, Get, Post } from '@miiajs/core'

@Controller('/users')
class UserController {
  @Get('/')
  list() {
    return [{ id: 1, name: 'Alice' }]
  }

  @Get('/:id')
  findOne(ctx: RequestContext) {
    return { id: ctx.params.id }
  }

  @Post('/')
  create(ctx: RequestContext) {
    return ctx.body
  }
}

Controllers must be registered in a Module to be discovered by the framework.

Route decorators

MiiaJS provides decorators for all standard HTTP methods:

DecoratorHTTP Method
@Get(path?)GET
@Post(path?)POST
@Put(path?)PUT
@Patch(path?)PATCH
@Delete(path?)DELETE
@Head(path?)HEAD
@Options(path?)OPTIONS

The path argument is optional and defaults to '' (the controller prefix itself).

RequestContext

Every route handler receives a RequestContext object as its first argument:

interface RequestContext {
  req: Request               // Native Fetch API Request
  res: ResponseBuilder       // Fluent response builder
  params: Record<string, string>  // URL path parameters
  query: Record<string, string>   // Parsed query string
  rawQuery: URLSearchParams       // Raw query for multi-value params
  body: any                  // Parsed request body
  state: Map<string, any>   // Shared state between middleware
  route?: RouteInfo          // Matched route metadata
  user?: any                 // User object (set by auth)
}

query, rawQuery, and state are lazy-loaded on first access for better performance.

Route parameters

Dynamic segments in the path are extracted as ctx.params:

@Get('/:userId/posts/:postId')
getPost(ctx: RequestContext) {
  const { userId, postId } = ctx.params
  return { userId, postId }
}

Response handling

Route handlers can return values in several ways:

Auto JSON

Return any object or array and it will be serialized as JSON with status 200:

@Get('/')
list() {
  return [{ id: 1 }, { id: 2 }]
}

Custom status

Use the @Status() decorator to set a different status code:

import { Status } from '@miiajs/core'

@Post('/')
@Status(201)
create(ctx: RequestContext) {
  return { id: 3, created: true }
}

Native Response

Return a Response object for full control:

@Get('/download')
download() {
  return new Response('raw body', {
    status: 200,
    headers: { 'Content-Type': 'text/plain' },
  })
}

Response builder

Use ctx.res for a fluent API:

@Get('/html')
page(ctx: RequestContext) {
  ctx.res
    .status(200)
    .header('X-Request-Id', 'abc')
    .html('<h1>Hello</h1>')
}

See Response for more details.

Validation

MiiaJS provides built-in validation decorators that work with any ZodLike schema (Zod, or any object with safeParse()):

import { ValidateBody, ValidateQuery, ValidateParams } from '@miiajs/core'
import { z } from 'zod'

const CreateUserSchema = z.object({
  name: z.string().min(1),
  email: z.string().email(),
})

const QuerySchema = z.object({
  page: z.string().transform(Number).default('1'),
  limit: z.string().transform(Number).default('10'),
})

@Controller('/users')
class UserController {
  @Post('/')
  @Status(201)
  @ValidateBody(CreateUserSchema)
  create(ctx: RequestContext) {
    // ctx.body is validated and typed
    return ctx.body
  }

  @Get('/')
  @ValidateQuery(QuerySchema)
  list(ctx: RequestContext) {
    return { page: ctx.query.page, limit: ctx.query.limit }
  }
}

Validation decorators throw UnprocessableException (422) with detailed error messages on failure.

DecoratorValidatesSource
@ValidateBody(schema)Request bodyctx.body (auto-parsed from JSON)
@ValidateQuery(schema)Query parametersctx.query
@ValidateParams(schema)Path parametersctx.params