zmdbzero-maintenance data layer
Docs Benchmarks Anti-patterns OpenAPI
Docs / Server framework

Controllers & RoutingSupported

Define HTTP controllers with Stage-3 decorators. @Controller sets a path prefix; @Get/@Post/@Put/@Patch/@Delete mark handler methods. All route data is stored in the standard Symbol.metadata record — no reflect-metadata, no runtime type reflection. The route table is resolved once via getRoutes.

Declaring a controller#

import { Controller, Get, Post, Patch, Delete } from '@zmdb/web';

@Controller('/users')
class UsersController {
  @Get('/:id')
  get() {
    /* ... */
  }

  @Post()
  create() {
    /* ... */
  }

  @Patch('/:id')
  update() {
    /* ... */
  }

  @Delete('/:id')
  remove() {
    /* ... */
  }
}
📝 Note

Stage 3 has no parameter decorators, so handlers don't take @Param/@Body arguments. Instead they'll receive a single strongly-typed request context — see the typed context work (path params are _derived_ from the route string). This page covers the route wiring itself.

Reading the route table#

getRoutes(ControllerClass) returns the resolved routes — the controller prefix composed with each method path, normalized, in declaration order:

import { getRoutes } from '@zmdb/web';

getRoutes(UsersController);
// [
//   { method: 'GET',    path: '/users/:id', handlerName: 'get' },
//   { method: 'POST',   path: '/users',     handlerName: 'create' },
//   { method: 'PATCH',  path: '/users/:id', handlerName: 'update' },
//   { method: 'DELETE', path: '/users/:id', handlerName: 'remove' },
// ]

The table is computed by reading context.metadata — cache it freely; it is stable after class initialization and never re-reflected per request.

Contract-owned public APIs#

For an API that also emits OpenAPI and a typed client, pair the controller with an explicit @zmdb/web/contract declaration. The contract owns the public operation ID, parameters, exact responses, security, and version; router.registerContract(...) binds that compiled operation to the controller instance and refuses disagreement with the decorated runtime route.

The complete declaration-to-runtime-to-OpenAPI-to-client flow is in Generated HTTP Client.

Path composition#

@Controller prefixmethod pathresolved
/users/:id/users/:id
users (no slash)_(none)_/users
_(none)_/health/health
users///users

Duplicate slashes collapse and a trailing slash is stripped (the root / stays /).

Design notes#