ExisJS

Decorators

ExisJS provides ECMAScript and TypeScript decorators for building backend applications in the Object-Oriented Programming (OOP) paradigm.

Decorators attach metadata to classes and methods using JavaScript Symbol keys. They do not depend on external reflection packages such as reflect-metadata.

Import decorators directly from exisjs/decorators:

import {  Controller,  Get,  Post,  Put,  Patch,  Delete,  Body,  Param,  Query,  Headers,  Injectable,  Use,  UseGuards,  UsePipes,  ParseIntPipe,  Cron,  Interval,  Cacheable,  Transactional} from 'exisjs/decorators'

1. Application & Boundary Decorators

@Server(options)

Configures root providers, plugins, cron schedules, and lifecycle hooks on an application entry class:

src/http/server.ts
import { Server } from 'exisjs/decorators'import type { App } from 'exisjs'
@Server({  providers: [['LOGGER_TOKEN', { useValue: console }]],  plugins: []})export default class RootServer {  async onStart(app: App) {    console.log('Server started')  }
  async onClose(app: App) {    console.log('Server stopping')  }}

@Boundary(options)

Configures directory-scoped middleware chains, CORS policies, response headers, and route exclusions inside boundary.ts:

src/http/admin/boundary.ts
import { Boundary } from 'exisjs/decorators'import type { Request, Response, Next, BoundaryContext } from 'exisjs/router'import { UnauthorizedError } from 'exisjs/error'
@Boundary({  cors: { origin: 'https://admin.example.com', credentials: true },  headers: { 'X-Admin-Api-Version': '2.0' },  exclude: ['/admin/health']})export default class AdminBoundary {  // Middleware handler identified by (req, res, next) signature  requireApiKey(req: Request, res: Response, next: Next) {    if (!req.headers['x-api-key']) {      throw new UnauthorizedError('Missing admin API key')    }    next()  }
  // Request wrapper identified by handle(ctx, next) signature  async handle(ctx: BoundaryContext, next: Next) {    const start = Date.now()    const result = await next()    ctx.res.setHeader('X-Response-Time', `${Date.now() - start}ms`)    return result  }}

2. Controller & Routing Decorators

@Controller(prefixOrOptions)

Registers a class as a route controller. Accepts a string path prefix or a configuration object:

src/http/users/route.ts
import { Controller, Get, Post, Put, Patch, Delete } from 'exisjs/decorators'import { UserParamsSchema, CreateUserSchema } from './schema'
@Controller('/users')export default class UsersController {  @Get('/')  async listUsers() {    return { users: [] }  }
  @Get('/:id', UserParamsSchema)  async getUser() {    return { id: 1, name: 'Alice' }  }
  @Post('/', CreateUserSchema)  async createUser() {    return { success: true }  }
  @Delete('/:id')  async deleteUser() {    return { deleted: true }  }}

Routing Methods Table

DecoratorHTTP MethodArgumentsDescription
@Get(path?, schema?)GETpath?: string, schema?: RouteSchemaMaps handler to GET requests.
@Post(path?, schema?)POSTpath?: string, schema?: RouteSchemaMaps handler to POST requests.
@Put(path?, schema?)PUTpath?: string, schema?: RouteSchemaMaps handler to PUT requests.
@Patch(path?, schema?)PATCHpath?: string, schema?: RouteSchemaMaps handler to PATCH requests.
@Delete(path?, schema?)DELETEpath?: string, schema?: RouteSchemaMaps handler to DELETE requests.
@Options(path?, schema?)OPTIONSpath?: string, schema?: RouteSchemaMaps handler to OPTIONS requests.
@Head(path?, schema?)HEADpath?: string, schema?: RouteSchemaMaps handler to HEAD requests.
@All(path?, schema?)Any Verbpath?: string, schema?: RouteSchemaCatches any HTTP verb matching the path.
@Connect(path?, schema?)CONNECTpath?: string, schema?: RouteSchemaMaps handler to CONNECT requests.
@Trace(path?, schema?)TRACEpath?: string, schema?: RouteSchemaMaps handler to TRACE requests.
@Query(path?, schema?)QUERYpath?: string, schema?: RouteSchemaMaps handler to RFC QUERY requests.

3. Parameter Decorators

Parameter decorators extract values from the incoming request and pass them into method arguments:

src/http/users/route.ts
import {  Controller,  Post,  Body,  Param,  Query,  Headers,  Req,  Res,  Ip,  Session,  UploadedFile} from 'exisjs/decorators'import type { Request, Response, ExisFile } from 'exisjs'
@Controller('/users')export default class UsersController {  @Post('/:id/avatar')  async uploadAvatar(    @Param('id') id: string,    @Query('quality') quality: string,    @Body() metadata: any,    @Headers('authorization') token: string,    @UploadedFile() file: ExisFile,    @Ip() clientIp: string,    @Req() req: Request,    @Res({ passthrough: true }) res: Response  ) {    return { id, quality, metadata, clientIp, file: file?.filename }  }}

Parameter Decorators Reference

DecoratorInjected ValueExample Usage
@Body(fieldOrPipe?)Request body or specific property@Body() dto: CreateUserDto, @Body('email') email: string
@Param(nameOrPipe?)Path parameter string@Param('id', ParseIntPipe) id: number
@Query(nameOrPipe?) / @QueryParam(...)Parsed query parameter or query map@Query('search') search: string
@Headers(nameOrPipe?)Request header value (case-insensitive)@Headers('x-api-key') apiKey: string
@Cookies(nameOrPipe?) / @Cookie(...)Parsed cookie map or specific cookie string@Cookie('session_id') sessionId: string
@Req()The incoming ExisRequest instance@Req() req: Request
@Res(options?)The outgoing ExisResponse instance ({ passthrough?: boolean })@Res({ passthrough: true }) res: Response
@Ip()Client IP address string (reads trustProxy setting)@Ip() ip: string
@Session()Active session state object@Session() session: any
@Next()The next() pipeline callback@Next() next: Next
@State(keyOrPipe?)Request context state@State('user') user: AuthUser
@UploadedFile(nameOrPipe?)Single parsed file object@UploadedFile('avatar') file: ExisFile
@UploadedFiles(nameOrPipe?)Array of parsed file objects@UploadedFiles('photos') files: ExisFile[]
@Fields(nameOrPipe?)Non-file multipart form fields@Fields() fields: Record<string, any>
@HostParam(nameOrPipe?)Subdomain or host parameter@HostParam('subdomain') domain: string
@AppCtx()Root Application instance@AppCtx() app: App

Custom Parameter Decorators (createParamDecorator)

import { createParamDecorator } from 'exisjs/decorators'
export const CurrentUser = createParamDecorator((data: string | undefined, ctx) => {  return data ? ctx.req.user?.[data] : ctx.req.user})

4. Transformation & Validation Pipes

Pipes transform parameter inputs or validate input types:

src/http/users/route.ts
import {  Controller,  Get,  Param,  Query,  UsePipes,  ParseIntPipe,  ParseFloatPipe,  ParseBoolPipe,  ParseUUIDPipe,  ParseArrayPipe,  ValidationPipe} from 'exisjs/decorators'
@Controller('/users')@UsePipes(new ValidationPipe({ transform: true }))export default class UsersController {  @Get('/:id')  async getUser(@Param('id', ParseIntPipe) id: number) {    return { id, isNumber: typeof id === 'number' }  }
  @Get('/filter')  async filter(    @Query('page', ParseIntPipe) page: number,    @Query('active', ParseBoolPipe) active: boolean,    @Query('tags', new ParseArrayPipe({ separator: ',' })) tags: string[]  ) {    return { page, active, tags }  }
  @Get('/uuid/:uuid')  async getByUuid(@Param('uuid', new ParseUUIDPipe({ version: '4' })) uuid: string) {    return { uuid }  }}

Pipes Reference

Pipe ClassDescriptionOptions
ParseIntPipeConverts input string or number to integer using Math.trunc{ errorHttpStatusCode?: number, exceptionFactory?: (msg) => any }
ParseFloatPipeConverts input string or number to floating point{ errorHttpStatusCode?: number, exceptionFactory?: (msg) => any }
ParseBoolPipeConverts 'true', '1', true to true, 'false', '0', false to false{ errorHttpStatusCode?: number, exceptionFactory?: (msg) => any }
ParseUUIDPipeValidates UUID strings{ version?: '3' | '4' | '5' | 'all', errorHttpStatusCode?: number }
ParseArrayPipeParses comma-separated strings or arrays with sub-item transformation{ items?: any, separator?: string, optional?: boolean }
ValidationPipeValidates DTO classes or schemas implementing validate() or Standard Schema{ transform?: boolean, whitelist?: boolean, errorHttpStatusCode?: number }
@UsePipes(...pipes)Attaches one or more pipes to a controller class or method handlerArray of Pipe instances or Pipe classes

5. Middleware, Guards & Lifecycle Pipeline Decorators

Attach guards, interceptors, exception filters, timeouts, and traffic policies to classes or route methods:

src/http/checkout/route.ts
import {  Controller,  Post,  UseGuards,  UseInterceptors,  UseFilters,  RateLimit,  Timeout,  Idempotent,  Cors,  Compress,  Dedupe,  IpFilter,  BlockProbes} from 'exisjs/decorators'import { AuthGuard } from '../../common/guards/auth.guard'import { LoggingInterceptor } from '../../common/interceptors/logging'import { HttpExceptionFilter } from '../../common/filters/http.filter'
@Controller('/checkout')@UseGuards(AuthGuard)@UseInterceptors(LoggingInterceptor)@UseFilters(HttpExceptionFilter)@Cors({ origin: 'https://pay.example.com', credentials: true })@RateLimit({ max: 20, windowMs: 60000 })@IpFilter({ allow: ['10.0.0.0/8', '127.0.0.1'] })@BlockProbes()export default class CheckoutController {  @Post('/')  @Timeout(15000)  @Idempotent({ ttlMs: 86400000 })  @Dedupe({ keyGenerator: (req) => req.user?.id ?? req.ip })  @Compress()  async processPayment() {    return { status: 'processed' }  }}

Traffic & Middleware Decorators

DecoratorScopeDescription
@Use(...items)Class / MethodAttaches middlewares, guards (canActivate), interceptors (intercept), or filters (catch).
@UseGuards(...guards)Class / MethodAttaches route guards implementing the CanActivate interface.
@UseInterceptors(...interceptors)Class / MethodAttaches request/response interceptors implementing Interceptor.
@UseFilters(...filters)Class / MethodAttaches exception filters implementing ExceptionFilter.
@Catch(...exceptions)ClassDeclares the exception types handled by an ExceptionFilter class.
@Timeout(msOrOptions)Class / MethodSets request execution timeout in milliseconds.
@Idempotent(options?)Method / ClassCaches responses matching the Idempotency-Key header in an LRU memory store.
@RateLimit(options?)Class / MethodEnforces rate limits using a token bucket algorithm.
@Cors(options?)Class / MethodApplies CORS headers to a specific controller or method.
@IpFilter(options)Class / MethodRestricts access using CIDR IP allowlists and denylists.
@Compress()Class / MethodEnables Gzip and Brotli response compression.
@Dedupe(options)Method / ClassDeduplicates parallel in-flight requests matching the same key.
@BlockProbes(options?) / @BlockSuspiciousProbes()Class / MethodDrops vulnerability scanner probes (.env, wp-admin, /.git).
@Upload(fieldNameOrOptions) / @UseUpload(options)Method / ParameterConfigures multipart file upload middleware.

6. Security & RBAC Decorators

Declare role requirements, permission checks, and authentication bypasses:

src/http/admin/roles/route.ts
import {  Controller,  Get,  Post,  Body,  UseGuards,  Roles,  Permissions,  Public} from 'exisjs/decorators'import { AuthGuard } from '../../common/guards/auth.guard'
@Controller('/roles')@UseGuards(AuthGuard)export default class RolesController {  @Get('/public')  @Public() // Instructs AuthGuard to skip credential verification  async getPublicInfo() {    return { status: 'open' }  }
  @Get('/')  @Roles('admin', 'security_officer')  async listRoles() {    return { roles: ['admin', 'manager', 'viewer'] }  }
  @Post('/')  @Permissions('roles:create', 'roles:write')  async createRole(@Body() body: any) {    return { created: true }  }}

Security Decorators Reference

DecoratorTargetDescription
@Roles(...roles: string[])Class / MethodRequires req.user to have one of the roles (user.roles or user.role). No user: 401; wrong role: 403.
@Permissions(...perms: string[])Class / MethodRequires all listed permissions in req.user.permissions. A role is never counted as a permission. No user: 401; missing permission: 403.
@Public() / @IsPublic() / @AllowAnonymous()Class / MethodMarks route as unauthenticated. Wins over @Roles / @Permissions on the same route (a warning is logged at startup).

7. Cron & Scheduled Background Tasks

Schedule recurring tasks directly on @Injectable() service classes:

src/http/cron/cleanup.service.ts
import { Injectable, Cron, Interval, TimeoutTask } from 'exisjs/decorators'
@Injectable()export class CleanupService {  // Executes daily at midnight UTC with overlap protection  @Cron('0 0 * * *', { name: 'nightly-purge', timezone: 'UTC', preventOverlap: true })  async purgeOldSessions() {    console.log('Purging expired sessions')  }
  // Executes every 60 seconds  @Interval(60_000, { name: 'health-heartbeat' })  async pingHeartbeat() {    console.log('Heartbeat ping')  }
  // Executes once 5 seconds after server startup  @TimeoutTask(5_000, { name: 'cache-warmup' })  async warmupCaches() {    console.log('Caches warmed up')  }}

Cron Decorators Table

DecoratorSchedule ParameterDescription
@Cron(schedule, options?) / @Scheduled(...)5-field cron expression stringSchedules a recurring cron task.
@Interval(ms, options?)Milliseconds intervalRuns repeatedly at the specified interval.
@TimeoutTask(ms, options?) / @ScheduledTimeout(...)Milliseconds delayRuns once after a delay following server start.

8. Service Enhancements: Caching, Transactions & Profiling

src/http/orders/service.ts
import {  Injectable,  Cacheable,  CacheEvict,  Transactional,  Profile,  type OnInit,  type OnDestroy} from 'exisjs/decorators'
@Injectable()export class OrderService implements OnInit, OnDestroy {  async onInit() {    console.log('OrderService initialized')  }
  async onDestroy() {    console.log('OrderService destroyed')  }
  // Caches result in memory for 300 seconds  @Cacheable({ ttl: 300, key: (id: string) => `order:${id}` })  @Profile({ thresholdMs: 50, warnSlow: true })  async getOrder(id: string) {    return this.db.query('SELECT * FROM orders WHERE id = $1', [id])  }
  // Executes inside a database transaction and evicts the cached key  @Transactional()  @CacheEvict({ key: (id: string) => `order:${id}` })  async updateOrder(id: string, data: any) {    return this.db.update('orders', id, data)  }}

Service & Lifecycle Reference

Decorator / InterfaceTargetDescription
@Cacheable(options?)MethodCaches return value. Options: { ttl?: number, key?: string | ((...args) => string) }.
@CacheEvict(options)MethodEvicts cache keys on method completion. Options: { key: string | ((...args) => string) }.
@Transactional(options?)MethodWraps execution in a database transaction (Prisma, Drizzle, Knex, SQL). Inside the call, the service's client property (db, database, prisma, dataSource or connection) is the transaction, including in methods it calls; concurrent requests using the same service keep the normal client.
@Profile(options?)MethodMeasures execution time using performance.now().
OnInit (onInit())Class InterfaceInvoked once after service instantiation.
OnDestroy (onDestroy())Class InterfaceInvoked once during graceful shutdown.
OnModuleInit / OnModuleDestroyClass InterfaceInvoked during parent module initialization and destruction.
OnApplicationBootstrapClass InterfaceInvoked when all modules are initialized and server starts listening.
OnApplicationShutdownClass InterfaceonApplicationShutdown(signal?) runs during graceful shutdown; signal is 'SIGTERM' or 'SIGINT' when a signal triggered it.

9. Response, Header & OpenAPI Decorators

Response & Host Modifiers

src/http/items/route.ts
import {  Controller,  Post,  Get,  HttpCode,  Header,  Redirect,  Hosts,  Returns} from 'exisjs/decorators'import { ItemResponseSchema } from './schema'
@Controller('/items')@Hosts('api.example.com', 'internal.example.com')export default class ItemsController {  @Post('/')  @HttpCode(201)  @Header('X-Resource-Version', '1.0')  @Returns(ItemResponseSchema)  async create() {    return { created: true }  }
  @Get('/legacy')  @Redirect('/items', 301)  async legacyRedirect() {}}

OpenAPI & Swagger Decorators

All OpenAPI decorators are available from exisjs/decorators:

src/http/users/route.ts
import {  Controller,  Get,  ApiTags,  ApiOperation,  ApiResponse,  ApiBearerAuth,  ApiSecurity,  ApiExclude,  ApiProperty} from 'exisjs/decorators'
export class UserDto {  @ApiProperty({ description: 'User ID', example: 'usr_123', required: true })  id: string
  @ApiProperty({ description: 'Full name', example: 'Alice Smith' })  name: string}
@ApiTags('Users')@ApiBearerAuth()@Controller('/users')export default class UsersController {  @Get('/:id')  @ApiOperation({ summary: 'Get User By ID', description: 'Returns a user record' })  @ApiResponse({ status: 200, description: 'User retrieved successfully' })  @ApiResponse({ status: 404, description: 'User not found' })  async getUser() {    return { id: 'usr_123', name: 'Alice Smith' }  }
  @Get('/internal/health')  @ApiExclude()  async health() {    return { ok: true }  }}
DecoratorScopeDescription
@ApiTags(...tags) / @Tags(...)Class / MethodAssigns OpenAPI documentation tags.
@ApiOperation(options) / @Operation(...)MethodConfigures summary, description, operationId, deprecated, externalDocs.
@ApiResponse(options)MethodDefines status code documentation (status, description, schema).
@ApiBearerAuth(name?) / @BearerAuth(...)Class / MethodMarks route as requiring Bearer JWT authentication in OpenAPI output.
@ApiSecurity(name, scopes?)Class / MethodDeclares custom security scheme requirement.
@ApiExclude()Class / MethodHides route or controller from OpenAPI / Swagger output.
@ApiProperty(options)PropertyAnnotates class DTO properties with description, examples, types, and enums.

10. Dependency Injection & Modules

@Injectable(options?)

src/http/users/service.ts
import { Injectable } from 'exisjs/decorators'
@Injectable({ scope: 'singleton' }) // 'singleton' | 'transient' | 'request'export class UserService {  async findOne(id: string) {    return { id, name: 'Alice' }  }}

@Inject(token) & @Optional()

src/http/users/route.ts
import { Controller, Get, Param } from 'exisjs/decorators'import { Inject, Optional } from 'exisjs/di'import { UserService } from './service'
@Controller('/users')export default class UsersController {  constructor(    @Inject(UserService) private userService: UserService,    @Inject('DATABASE_URL') private dbUrl: string,    @Optional() @Inject('ANALYTICS_SERVICE') private analytics?: any  ) {}
  @Get('/:id')  async getUser(@Param('id') id: string) {    return this.userService.findOne(id)  }}

@Module(options) & @Global()

src/http/users/users.module.ts
import { Module, Global } from 'exisjs/decorators'import UsersController from './route'import { UserService } from './service'
@Global()@Module({  controllers: [UsersController],  providers: [UserService],  exports: [UserService]})export class UsersModule {}

11. JavaScript Runtime Utilities (Decorate & BindParam)

For JavaScript projects without a build step or TypeScript compiler, use Decorate and BindParam to apply decorator metadata programmatically:

src/http/users/route.js
import { Decorate, BindParam, Controller, Get, Post, Body, Param } from 'exisjs/decorators'
class UsersController {  list() {    return []  }
  create(body) {    return { created: body.name }  }}
export default Decorate(  UsersController,  [Controller('/users')],  {    list: [Get('/')],    create: [      Post('/'),      BindParam(0, Body()) // Injects @Body() into the first argument (index 0)    ]  })