> For the complete documentation index, see [llms.txt](https://docs.soapjs.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.soapjs.com/components/routing.md).

# 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

```typescript
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:

```typescript
import { ExpressRouter } from '@soapjs/soap-express';

class ComicsRouter extends ExpressRouter {
  setupRoutes() {
    this.get('/comics', (req, res) => this.listComics.execute({ publisher: req.query.publisher }));
    this.post('/comics', (req, res) => this.createComic.execute(req.body));
  }
}

const router = new ComicsRouter().initialize();
app.registerRouter(router);
```

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`:

```typescript
// Decorators:
@Get('/')
@CallUseCase(ListComicsUseCase)
@RouteIO(new PaginationIO())
list() {}                       // body intentionally empty

// Fluent:
this.get('/comics', () => {}).useCase(ListComicsUseCase).routeIO(new PaginationIO());
```

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`.

```typescript
export class PaginationIO implements ExpressIO {
  from(req) { return { page: +req.query.page || 1, limit: +req.query.limit || 10 }; }
  to(result, res) { /* success → envelope; failure → ResultMapper */ }
}
```

### 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.
