Domain Routing
Domain routing lets you match routes based on the request’s hostname rather than just the URL path. You can extract parameters from subdomain patterns, making it straightforward to build multi-tenant applications, regional endpoints, and custom domain logic.
Controller-level domain
Section titled “Controller-level domain”Apply a domain constraint directly on a controller using the domain option in the @Controller() decorator. All routes within the controller will only match when the request hostname fits the pattern.
import { Controller, Route, type RouterContext } from 'stratal/router'
@Controller('/dashboard', { domain: '{tenant}.myapp.com' })class DashboardController { @Route() index(ctx: RouterContext) { const tenant = ctx.domain('tenant') return ctx.json({ tenant, message: `Welcome to ${tenant}'s dashboard` }) }}Parameters wrapped in curly braces (e.g., {tenant}) are extracted from the hostname and made available through ctx.domain().
Router-level domain
Section titled “Router-level domain”For broader control, set the domain constraint at the module level by implementing the RouteConfigurable interface. Every controller registered in the module will inherit the domain rule.
import { Module, type RouteConfigurable, type Router } from 'stratal/module'
@Module({ controllers: [DashboardController],})class TenantModule implements RouteConfigurable { configureRoutes(router: Router) { router.domain('{tenant}.myapp.com') }}Accessing domain parameters
Section titled “Accessing domain parameters”Use ctx.domain() inside any route handler to retrieve a captured hostname segment by name.
const tenant = ctx.domain('tenant')Multiple parameters
Section titled “Multiple parameters”Domain patterns can contain more than one parameter. Each segment between dots is matched independently.
// Pattern: '{region}.{tenant}.example.com'// Request: us-east.acme.example.com
const region = ctx.domain('region') // 'us-east'const tenant = ctx.domain('tenant') // 'acme'Grouping routes with domain and prefix
Section titled “Grouping routes with domain and prefix”Use router.group() inside configureRoutes to scope a set of controllers under a shared domain, prefix, and middleware.
import { Module, type RouteConfigurable, type Router } from 'stratal/module'
@Module({ controllers: [DashboardController, SettingsController],})class TenantModule implements RouteConfigurable { configureRoutes(router: Router) { router .domain('{tenant}.myapp.com') .group([DashboardController, SettingsController], (r) => { r.prefix('/api').middleware(TenantMiddleware) }) }}In this example, both DashboardController and SettingsController are reachable only when the request matches {tenant}.myapp.com, all paths are prefixed with /api, and TenantMiddleware runs before every handler.