Skip to content

Decorators or plain routes ​

Prefer route functions? Use them. Prefer controllers? Use standard TypeScript 5 decorators. Both run through the same router.

Route functions ​

ts
export function plainApp(service: GreetingService) {
  const app = createApp();
  app.router.get("/readers/:id", (req, res) => {
    res.json({ message: service.message(toHttpId(req.param("id"))) });
  });
  app.router.head("/readers/:id", (req, res) => {
    toHttpId(req.param("id"));
    res.header("content-type", "application/json; charset=utf-8").send("");
  });
  return app;
}

Controller methods ​

ts
@Controller("/readers")
export class ReadersController {
  constructor(private readonly service: GreetingService) {}

  @Get("/:id")
  show(req: Request, res: Response): void {
    res.json({ message: this.service.message(toHttpId(req.param("id"))) });
  }

  @Head("/:id")
  head(req: Request, res: Response): void {
    toHttpId(req.param("id"));
    res.header("content-type", "application/json; charset=utf-8").send("");
  }
}

Construct the controller yourself and pass it to registerControllers(router, instance). The complete example uses the same greeting service and ID validation in both versions.

Import decorators from @portablesheep/brevity/decorators, not the default core entry point. Leave legacy experimentalDecorators and emitDecoratorMetadata disabled. Use public instance methods and keep request-specific state in locals: one controller instance is reused across requests.

Both implementations explicitly register HEAD and map expected domain errors. Run npm run docs:check to verify equivalent GET/HEAD/422 responses. The API reference covers inheritance, path joining, and override semantics.

Small core. Build your way.