Skip to main content
Routing is how a Talos app decides which controller handles which request. You never wire up a route table by hand. You decorate a controller class with a route decorator, and the framework registers the path, method, and validation rules for you. When a request arrives, the router matches it against the registered routes and runs the matched controller’s index(context) method. A route and its controller are two halves of one unit: the decorator describes the contract (path, method, what input is valid, who is allowed in), and the controller supplies the behavior. Because every route is declared on the class that serves it, the definition lives next to the code that runs, and the same configuration object drives parameter parsing, validation, access control, and named URL generation.

What the route decorator buys you

The route lives on the controller it serves, so there’s no central route file drifting out of sync with its handlers. Path params, query strings, and request bodies are validated with @talosjs/validation schemas before your controller runs, so index receives typed, trusted data. Roles, permission classes, and env/ip/host restrictions are part of the same definition, which keeps “who can reach this” visible right where the route is declared. Routes are referenced by name rather than by a hardcoded path: every route has one, and you generate URLs from it with router.generate(...) instead of scattering stringly-typed paths across the codebase. The same decorator family declares HTTP and WebSocket endpoints, so socket routes follow the same naming and validation conventions as HTTP ones.

How it works

The router holds a map of paths to route configurations. Each Route.* decorator builds a route config from the path and your options, marks the HTTP method (or isSocket: true for sockets), attaches the controller class, and registers it with the container as a singleton. At request time the framework matches the path and method, validates the input against the route’s schemas, enforces any access rules, and invokes the matched controller’s index(context). The decorator takes the path as its first argument and the config object as its second:

Defining a route

Import Route and decorate a controller class. The method decorator name maps to the HTTP method: Route.get, Route.post, Route.put, Route.delete, Route.patch, Route.options, and Route.head. The controller implements IController and exposes an index(context) method that returns a response.

Configuration options

Every option except path and method (which come from the decorator) is passed in the config object.
The decorator supplies path, method, isSocket, and controller automatically — you never set those in the config object.

Route naming

Every route has a unique name following the namespace.resource.action convention, for example api.users.list or admin.products.create. Names must be unique across the whole app; registering two routes with the same name throws a RouterException. The name is what you pass to router.generate(...) and what the router uses for lookups. Valid namespaces: api, client, admin, public, auth, webhook, internal, external, system, metrics, docs The middle resource segment names the thing the route acts on (users, products, users.orders), and the final action segment names the operation (list, show, create, update, delete, search, and many more).

Path parameters

Dynamic path segments are written with a leading colon, like :id. Each declared parameter should have a schema in params; after validation the values are available on context.params.
A path may declare several parameters, like /api/users/:userId/orders/:orderId, and each is matched into context.params by name.

Query validation

Provide a queries schema to validate and coerce the query string. The schema handles defaults and optional keys, so the controller reads ready-to-use values from context.queries.

Payload validation

For routes that accept a request body, declare a payload schema. The body is validated before index runs, and the parsed value is on context.payload.
See validation for the full schema syntax, and response for the response builder used in these handlers.

Response validation

Declare a response schema to describe the shape index returns. Unlike params, queries, and payload, the response schema is not a runtime gate on the request. It documents the route’s output contract and drives type derivation and codegen, so generated clients and API docs stay in sync with what the controller actually sends back.
Because the schema is the same @talosjs/validation construct used for input, the response shape lives in the route contract alongside its inputs. Describe it once on the decorator and the generated types and documentation follow.

WebSocket routes

Route.socket(path, config) declares a WebSocket endpoint. It takes the same config as the HTTP decorators (the method is ignored and isSocket is set to true), but the controller’s context comes from @talosjs/socket and exposes a channel for subscribing and publishing.

Generating URLs

Look routes up by name and build their URL with router.generate(name, params). Path parameters are interpolated from the params object; missing required parameters throw a RouterException.
Generating from names keeps URLs in one place. Rename a path in its decorator and every call site that uses the route name keeps working.

Access control

Routes can declare who is allowed to reach them. These checks run before the controller, so an unauthorized request never reaches index. For role-based access, list the roles permitted to call the route:
When the rule is more than a fixed role list, point permission at a permission class that decides per request:
To limit a route to certain environments or network origins, use the env, ip, and host lists:
Access checks compose with the request middleware pipeline. Middleware runs first and may short-circuit, then the route’s own access rules apply, then the controller runs.

Conventions worth following

Name every route as namespace.resource.action so names stay predictable and router.generate calls read clearly. Declare params, queries, and payload schemas at the boundary, so index works with trusted, typed data instead of re-checking input by hand, and use router.generate(name, params) wherever you need a route URL rather than hardcoding one. Keep one route per controller: a controller’s index serves a single route, so split distinct operations into distinct controllers. Put roles, permission, and env/ip/host rules on the route itself so the contract states who may reach it. Set version deliberately so clients and generated docs can track route revisions.

Scaffolding

Generate a controller and its route together with the CLI rather than wiring one by hand:
See controller:create for the full command reference, and the controller page for how the generated index(context) method consumes the validated request.