ExisJS

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

PropertyTypeDescription
corsboolean | CorsConfigCORS configuration applied to all routes in this controller file.
middleware / middlewaresHandler | Handler[]Middleware functions executed prior to controller route handlers.
guardsGuard[]Authorization guards executed before controller endpoints.
interceptorsInterceptor[]Response interceptors applied to this controller.
filtersFilter[]Exception filters applied across this controller.
onError(err: Error, req: Request, res: Response) => anyController-scoped error hook.
onResponse(data: any, req: Request, res: Response) => anyController-scoped response hook.
[key: string]RouteDefinitionRoute 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

PropertyTypeDescription
middleware / middlewaresHandler | Handler[]Middleware cascaded to all routes in this directory and subdirectories.
corsboolean | CorsConfigDirectory-scoped CORS policy.
headersRecord<string, string>Default response headers applied to matching routes.
guardsGuard[]Authorization guards applied to matching routes.
interceptorsInterceptor[]Response interceptors applied to matching routes.
filtersFilter[]Exception filters applied to matching routes.
exclude(string | BoundaryExcludeRule)[]Route patterns or method rules excluded from this boundary.
timeoutnumber | ((req: Request) => number)Timeout threshold in milliseconds for routes in this directory.
blockSuspiciousProbesboolean | BlockProbesOptionsSuppresses and blackholes malicious scanner probes (.env, wp-admin, .git).
metadataRecord<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): ClassDecorator

Example

src/http/users/route.ts
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?)
DEFAULT

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:

DecoratorInjected DataExample
@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

DecoratorTargetDescription
@Use(...middlewares)Class / MethodApplies middleware functions to the controller or route.
@UseGuards(...guards)Class / MethodAttaches authorization guards.
@UseFilters(...filters)Class / MethodAttaches exception filters.
@UseInterceptors(...interceptors)Class / MethodWraps the handler: each interceptor receives next.handle() and its return value becomes the response.
@HttpCode(status)MethodSets the default HTTP status code for successful responses.
@Header(name, value)MethodSets static HTTP response headers.
@Redirect(url, status?)MethodRedirects responses to the target URL.
@SetMetadata(key, value)Class / MethodAttaches arbitrary metadata readable via Reflector.