Introduction
Declarative controllers, DTO validation, dependency injection, OpenAPI generation and Express bootstrap for TypeScript APIs.
exjs-controllers
exjs-controllers is a thin, decorator-based framework on top of Express 5 and Zod 4. It lets you describe HTTP APIs as classes with explicit metadata — controllers, parameters, DTOs, authorization, and use cases — and bootstraps the whole application (including OpenAPI 3.1 and Scalar) with a single configureApplication call.
Declarative Controllers
Class-based controllers with `@Controller`, `@JsonController` and HTTP method decorators on top of Express 5.
DTOs With Zod
`BaseSchema` + `@Field` collect Zod definitions, validate request bodies and feed the OpenAPI schema.
Authentication & Authorization
Pluggable principal and authorization checkers with `@Authorized`, `@CurrentUser` and `@CurrentApiKey`.
OpenAPI + Scalar Out of the Box
Auto-generated OpenAPI 3.1 document and Scalar reference UI served by `configureApplication`.
Install
npm install exjs-controllers express zodPeer dependencies: express ^5, zod ^4. TypeScript projects also need @types/express as a dev dependency. Node.js >=22.
Why exjs-controllers
- Express, not against it. Routes, middlewares and error handlers are plain Express — controllers register through the same
app.get/post/...surface. - Schema-first DTOs.
BaseSchema+@Field(z....)describe both the runtime validation and the OpenAPI shape. - Real DI. Singleton-by-default container with constructor
@Inject(token)support, no reflect-metadata required. - Auth without lock-in. You provide
currentUserCheckerandauthorizationChecker; the framework only orchestrates them and emits the security blocks in OpenAPI.
Status
Public API current as of version 0.4.0. The library targets the modern (TC39) decorator proposal and continues to accept legacy experimental decorators for older builds.
Get Started
Install
Add `exjs-controllers` to your project alongside its peers `express ^5`, `zod ^4` and Node.js `>=20`.
Declare
Write `@Controller` classes, declare DTOs that extend `BaseSchema` and annotate fields with `@Field(z....)`.
Bootstrap
Call `configureApplication(app, options)` to register controllers, expose OpenAPI and (optionally) mount Scalar.
Public Entry Points
Application
exjs-controllers/http/application`createApplication()` factory returning the typed `Application` wrapper around Express.
Configuration
exjs-controllers/config/configureApplication`configureApplication(app, options)` wires controllers, middlewares, auth, logging, OpenAPI and Scalar.
Decorators
exjs-controllers/decorators/*Controllers, routes, params, DI, authorization, use cases and tracing decorators.
Schemas
exjs-controllers/schemas/BaseSchema`BaseSchema` base class plus `@Field` for declarative Zod-backed DTOs.
Authentication
exjs-controllers/core/authenticationAuth types, principal kinds, `UnauthorizedError` / `ForbiddenError` and middleware factory.
OpenAPI
exjs-controllers/openapi/generateOpenApiDocumentProgrammatic OpenAPI 3.1 document generation from controller metadata.
Observability
exjs-controllers/observability/tracing`runWithSpan` / `runWithControllerSpan` helpers backed by `@opentelemetry/api`.
Logging
exjs-controllers/logging/*Pino-based HTTP logger middleware with correlation IDs and async-local request context.
Minimal Server
import { z } from 'zod'
import { createApplication } from 'exjs-controllers/http/application'
import { configureApplication } from 'exjs-controllers/config/configureApplication'
import { Controller } from 'exjs-controllers/decorators/Controller'
import { Get } from 'exjs-controllers/decorators/HttpMethod'
import { Field } from 'exjs-controllers/decorators/Field'
import { BaseSchema } from 'exjs-controllers/schemas/BaseSchema'
class HealthOutput extends BaseSchema {
@Field(z.string())
status!: string
}
@Controller('/health')
class HealthController {
@Get('/', { outputClass: HealthOutput, summary: 'Health check', tags: ['health'] })
getHealth(): HealthOutput {
return HealthOutput.create({ status: 'ok' })
}
}
const app = createApplication()
await configureApplication(app, {
controllers: [HealthController],
openapi: {
documentation: { info: { title: 'Example API', version: '1.0.0' } }
},
enableScalar: true
})
app.listen(3000)What You Get
Dependency Injection
Singleton-by-default container with `@Injectable` and constructor `@Inject(token)` parameters.
Use Cases
`@DefineUseCase` traces and validates `execute(input)` so controllers can dispatch business logic with confidence.
OpenAPI From Metadata
`inputClass` and `outputClass` on routes produce request/response schemas in the generated spec.
Tracing With OpenTelemetry
Controller handlers and `@DefineUseCase` calls run inside spans; `@TraceSpan` wraps any method.
Next Reading
Getting Started
TypeScript configuration, a runnable bootstrap and the project layout exjs-controllers expects.
Controllers
@Controller, @JsonController and the HTTP method decorators with their RouteOptions.
DTOs
Declare request and response shapes with BaseSchema + @Field, with full inheritance support.
OpenAPI
How route metadata is turned into a 3.1 document and how to enrich it with security schemes.