exisjs/router
ExisJS provides two routing paradigms with full feature parity: Functional Routing (exisjs/router) and Class-Based OOP Routing (exisjs/decorators).
1. Functional Routing API
The functional router allows defining endpoints and boundaries using composable objects and functions.
import { route, controller, createRouter, defineMiddleware, defineBoundary, validate, getContext, setContext, getRequest, getResponse, after, fileUpload, SSEStream,} from 'exisjs/router'
import type { RouteConfig, RouteDefinition, ControllerConfig, SuperContext, BoundaryConfig, BoundaryContext, BoundaryExcludeRule, Request, Response, NextFunction,} from 'exisjs/router'controller(config)
Wraps route definitions, file-scoped middlewares, guards, and CORS policies into an exportable controller slice.
function controller<T extends Record<string, any>>( config: ControllerConfig<T>): ControllerDefinition<T>ControllerConfig Options
| Property | Type | Description |
|---|---|---|
cors | boolean | CorsConfig | CORS configuration applied to all routes in this controller file. |
middleware / middlewares | Handler | Handler[] | Middleware functions executed prior to controller route handlers. |
guards | Guard[] | Authorization guards executed before controller endpoints. |
interceptors | Interceptor[] | Response interceptors applied to this controller. |
filters | Filter[] | Exception filters applied across this controller. |
onError | (err: Error, req: Request, res: Response) => any | Controller-scoped error hook. |
onResponse | (data: any, req: Request, res: Response) => any | Controller-scoped response hook. |
[key: string] | RouteDefinition | Route builder definitions (e.g. list: route.get(...)). |
Composition Methods
controller.merge(...controllers): Combines multiple functional controller slices into a single unified controller.controller.extend(base, overrides): Inherits routes and configuration from a base controller, allowing overrides and additions.
import { controller, route } from 'exisjs/router'
const base = controller({ list: route.get('/', { async handle() { return { items: [] } }, }),})
export default controller.extend(base, { create: route.post('/', { async handle({ body }) { return { created: true } }, }),})route Builder
Constructs route definitions for standard HTTP methods and streaming protocols:
route.get(path, config)route.post(path, config)route.put(path, config)route.patch(path, config)route.delete(path, config)route.options(path, config)route.head(path, config)route.all(path, config)route.sse(path, config)route.ws(path, config)
RouteConfig Specification
export interface RouteConfig<B = any, Q = any, P = any, TContext = Record<string, any>, TReturn = any> { // Input Validation Schemas body?: Schema<B> query?: Schema<Q> params?: Schema<P> response?: Schema<TReturn> returns?: Schema<TReturn>
// Execution Handler handle: (ctx: SuperContext<B, Q, P, TContext>) => TReturn | Promise<TReturn>
// Pipeline & Security middleware?: Handler<any, any, any, any, TContext> | Handler<any, any, any, any, TContext>[] guards?: any | any[] interceptors?: any | any[] filters?: any | any[] roles?: string | string[] permissions?: string | string[] cors?: boolean | CorsConfig host?: string | string[] public?: boolean
// File Uploads & Tuning upload?: string | RouteUploadOptions timeout?: number | { ms: number; statusCode?: number; message?: string } httpCode?: number headers?: Record<string, string> redirect?: string | { url: string; statusCode?: number }
// OpenAPI Metadata summary?: string description?: string tags?: string[] operationId?: string deprecated?: boolean excludeFromDocs?: boolean}SuperContext
The strongly-typed execution context supplied to every functional handle() function:
export interface SuperContext<B = any, Q = any, P = any, TContext = Record<string, any>> { /** Pre-validated request body */ body: B /** Pre-validated query string parameters */ query: Q /** Pre-validated URL route parameters */ params: P /** Request HTTP headers */ headers: Record<string, string | string[] | undefined> /** ExisRequest instance */ req: Request<B, Q, P, TContext> /** ExisResponse instance */ res: Response /** Exis Application instance */ app: App /** Mutable dictionary shared across route middlewares */ state: Record<string, any> /** Request-scoped Dependency Injection resolver */ resolve: <T>(token: ProviderToken<T>) => T /** Single uploaded file */ file?: ExisFile /** Multiple uploaded files */ files?: ExisFile[] | Record<string, ExisFile[]> /** Multipart text fields */ fields?: Record<string, any>}defineBoundary(config)
Declares hierarchical folder-level middleware, security controls, and response defaults inside boundary.ts files.
function defineBoundary<TContext = Record<string, any>>( config: BoundaryConfig<TContext>): BoundaryConfig<TContext>BoundaryConfig Options
| Property | Type | Description |
|---|---|---|
middleware / middlewares | Handler | Handler[] | Middleware cascaded to all routes in this directory and subdirectories. |
cors | boolean | CorsConfig | Directory-scoped CORS policy. |
headers | Record<string, string> | Default response headers applied to matching routes. |
guards | Guard[] | Authorization guards applied to matching routes. |
interceptors | Interceptor[] | Response interceptors applied to matching routes. |
filters | Filter[] | Exception filters applied to matching routes. |
exclude | (string | BoundaryExcludeRule)[] | Route patterns or method rules excluded from this boundary. |
timeout | number | ((req: Request) => number) | Timeout threshold in milliseconds for routes in this directory. |
blockSuspiciousProbes | boolean | BlockProbesOptions | Suppresses and blackholes malicious scanner probes (.env, wp-admin, .git). |
metadata | Record<string, any> | Metadata (e.g. OpenAPI tags) cascaded to all child routes. |
2. Class-Based OOP Routing API (exisjs/decorators)
The Class-Based paradigm uses standard TypeScript decorators for controller definitions, route methods, parameter injection, and pipeline lifecycle hooks.
import { Controller, Get, Post, Put, Patch, Delete, Options, Head, All, Body, Param, Query, QueryParam, Headers, Req, Res, Next, Ip, Session, UploadedFile, UploadedFiles, createParamDecorator, Use, Guards, Filters, Interceptors, HttpCode, Header, Redirect, SetMetadata, Reflector,} from 'exisjs/decorators'@Controller(prefix?, options?)
Decorates a class as an HTTP controller slice mounted at the specified route prefix.
function Controller( prefixOrOptions?: string | ControllerOptions, options?: ControllerOptions): ClassDecoratorExample
import { Controller, Get, Post, Body, Param } from 'exisjs/decorators'import { tex } from 'exisjs/validator'
@Controller('/users')export default class UserController { @Get('/:id') async getUser(@Param('id') id: string) { return { id, name: 'Alice' } }
@Post('/', { body: tex.object({ name: tex.string(), email: tex.email(), }), }) async createUser(@Body() body: { name: string; email: string }) { return { success: true, user: body } }}HTTP Method Decorators
Method decorators attach HTTP handler logic and validation schemas to controller methods:
@Get(path?, schema?)@Post(path?, schema?)@Put(path?, schema?)@Patch(path?, schema?)@Delete(path?, schema?)@Options(path?, schema?)@Head(path?, schema?)@All(path?, schema?)
Server-Sent Events use a normal @Get route that calls res.sse(); see Responses. WebSocket routes are not part of the class decorators.
Parameter Injector Decorators
Parameter decorators extract and inject typed data directly into handler method parameters:
| Decorator | Injected Data | Example |
|---|---|---|
@Body(property?) | Request body payload or sub-property | @Body() data: CreateUserDto |
@Param(key?) | URL path parameters dictionary or specific param | @Param('id') userId: string |
@Query(key?) / @QueryParam(key?) | Query string parameters dictionary or specific key | @Query('limit') limit: number |
@Headers(name?) | Request headers dictionary or specific header | @Headers('authorization') auth: string |
@Req() | Underlying ExisRequest instance | @Req() req: Request |
@Res() | Underlying ExisResponse instance | @Res() res: Response |
@Next() | Pipeline NextFunction callback | @Next() next: NextFunction |
@Ip() | Client remote IP address | @Ip() clientIp: string |
@UploadedFile(field?) | Single uploaded multipart file object | @UploadedFile('avatar') file: ExisFile |
@UploadedFiles(field?) | Multiple uploaded multipart file objects | @UploadedFiles('docs') files: ExisFile[] |
createParamDecorator(factory)
Constructs custom parameter decorators to extract specialized values from the request context:
import { createParamDecorator } from 'exisjs/decorators'
export const CurrentUser = createParamDecorator((data: string | undefined, ctx) => { const req = ctx.req return data ? req.user?.[data] : req.user})
// Usage in controller:@Get('/profile')getProfile(@CurrentUser() user: any, @CurrentUser('id') userId: string) { return { userId, user }}Pipeline & Response Decorators
| Decorator | Target | Description |
|---|---|---|
@Use(...middlewares) | Class / Method | Applies middleware functions to the controller or route. |
@UseGuards(...guards) | Class / Method | Attaches authorization guards. |
@UseFilters(...filters) | Class / Method | Attaches exception filters. |
@UseInterceptors(...interceptors) | Class / Method | Wraps the handler: each interceptor receives next.handle() and its return value becomes the response. |
@HttpCode(status) | Method | Sets the default HTTP status code for successful responses. |
@Header(name, value) | Method | Sets static HTTP response headers. |
@Redirect(url, status?) | Method | Redirects responses to the target URL. |
@SetMetadata(key, value) | Class / Method | Attaches arbitrary metadata readable via Reflector. |