For the complete documentation index, see llms.txt. This page is also available as Markdown.

Routing

SoapJS offers two ways to define HTTP endpoints. They share the same execution semantics, so pick whichever style you prefer — don't mix three:

  1. Decorators — routes declared on controller methods (familiar, co-located).

  2. Fluent Router — routes defined centrally in one place.

Both support pure use-case handlers and RouteIO transformation, and both map a Result to HTTP the same way.

Style 1 — Decorators

import { Controller, Get, Post, AdminOnly, ResultMapper } from '@soapjs/soap-express';

@Controller('/comics')
export class ComicsController {
  constructor(@Inject(ListComicsUseCase.Token) private readonly listComics: ListComicsUseCase) {}

  @Get('/')
  async list(req: Request, res: Response): Promise<void> {
    const result = await this.listComics.execute({ publisher: req.query.publisher as string });
    ResultMapper.toResponse(result, res);
  }

  @Post('/')
  @AdminOnly()
  async create(req: Request, res: Response): Promise<void> { /* ... */ }
}

await bootstrap({ controllers: [ComicsController], container });

Route decorators: @Get @Post @Put @Delete @Patch @Head @Options. Auth: @Auth(strategy) @AdminOnly() @RolesOnly(roles) @SelfOnly() @Public().

Style 2 — Fluent Router

Define routes centrally by extending ExpressRouter, then register it on the app:

Chainable per route: .useCase(UseCaseClass), .routeIO(IO), .cors(), .auth(), .rateLimit(), .validate(schema).

Pure handlers with use cases

In both styles a route can delegate straight to a use case — the handler receives a plain input and returns a Result, never touching req/res:

The framework runs: routeIO.from(req)useCase.execute(input) → response.

RouteIO — request/response transformation

A RouteIO keeps HTTP shaping out of your handlers:

  • from(req) — request → use-case input

  • to(result, res)Result → HTTP response

Built-ins: PaginationIO (page/limit/sort/filters → paginated envelope), SimpleIO (body in, content out), FileUploadIO. Or pass inline { from, to } mappings to @RouteIO.

Response handling (consistent across both styles)

Since @soapjs/soap-express 0.4.0, both routing styles dispatch results the same way:

  • a failed Result → always ResultMapper (consistent status mapping, e.g. ValidationError → 400), even when a RouteIO is set

  • a successful Result with a RouteIO → routeIO.to (custom shape)

  • a successful Result without a RouteIO → ResultMapper

  • a raw value → res.json; a handler that wrote the response itself → no-op

So you can always return Result.withSuccess(...) (let the framework respond) or call ResultMapper.toResponse(result, res) yourself — both are supported.

Last updated