Skip to main content

NestJS

NestJS is one of the most widely adopted Node.js frameworks for building server-side applications. Its module system, dependency injection container, decorator-based architecture, and first-class TypeScript support make it an excellent foundation for building scalable, maintainable APIs. The ecosystem around NestJS — guards, interceptors, pipes, lifecycle hooks — provides everything teams need to structure complex applications without reinventing the wheel.

However, NestJS intentionally stops short of providing a fully integrated validation and transformation layer. For input validation it directs you to class-validator, for transformation to class-transformer, and for API documentation to @nestjs/swagger — three separate libraries that each require their own configuration, decorators, and wiring. Keeping them in sync is ongoing manual work: a field added to a DTO must be decorated in all three places to be validated, transformed, and documented correctly. When they drift apart, the API contract your documentation describes no longer matches what your code actually enforces.

OPRA fills this gap with an end-to-end, fully integrated contract layer:

  • Every incoming request — path parameters, query parameters, headers, and body — is automatically parsed, validated, and type-coerced against the declared schema before your handler is ever called. For heavy payloads such as file uploads and multipart forms, OPRA streams parts sequentially, stores files in temporary storage rather than memory, and cleans them up automatically after the request completes — with no extra configuration required.
  • Every outgoing response is encoded against the declared response type, guaranteeing that the data your client receives matches exactly what the contract promises.
  • Response types and error codes are declared alongside the operation and documented automatically. OPRA guarantees that the correct HTTP status codes and error shapes are returned — what the documentation describes is what the client gets.
  • Multi-protocol support — the same schema and controller model works across HTTP, message queues (Kafka, RabbitMQ), and WebSockets (Socket.IO). You define your types and operations once; OPRA handles the transport-specific encoding, decoding, and routing for each protocol.
  • Integrated data layer — for data-driven applications, OPRA manages the full pipeline from endpoint to data service to response. Database adapters for MongoDB, SQL (via SQB), and Elasticsearch provide execution-context-aware services that plug directly into the request lifecycle — query parameters like filtering, sorting, and projection are parsed from the request and passed to the data service automatically, with no manual wiring between the HTTP layer and the database layer.
  • API documentation is generated directly from the same schema — no separate Swagger decorators, no risk of drift.

One schema definition drives validation, transformation, error handling, and documentation simultaneously.

OPRA is designed to work seamlessly alongside NestJS. Rather than replacing NestJS's application infrastructure, OPRA plugs into it: your controllers remain NestJS providers, your services are injected as usual, and OPRA takes care of the contract layer on top. The two frameworks complement each other — NestJS brings the application structure, OPRA brings the end-to-end type-safe API contract.

We recommend NestJS as the preferred way to build OPRA applications.


Packages

OPRA provides a dedicated NestJS module for each transport:

PackageTransportDescription
@opra/nestjs-httpHTTPIntegrates ExpressAdapter into a NestJS HTTP application.
@opra/nestjs-kafkaKafkaIntegrates KafkaAdapter as a NestJS microservice.
@opra/nestjs-rabbitmqRabbitMQIntegrates RabbitmqAdapter as a NestJS microservice.
@opra/nestjs-socketioSocket.IOIntegrates SocketioAdapter into a NestJS WebSocket gateway.
note

@opra/nestjs is an internal shared library used by the transport modules above. You do not import it directly — install and import the transport-specific package for your use case.


Installation

Install the base module and whichever transport module you need:

# HTTP
npm install @opra/nestjs-http @opra/http @opra/common

# Kafka
npm install @opra/nestjs-kafka @opra/kafka @opra/common

# RabbitMQ
npm install @opra/nestjs-rabbitmq @opra/rabbitmq @opra/common

# Socket.IO
npm install @opra/nestjs-socketio @opra/socketio @opra/common

Setup

Import the transport module in your root AppModule and call forRoot() with your document definition. Controllers and types are passed directly — no api wrapper needed. The example below uses @opra/nestjs-http — the pattern is identical for the other transport modules.

import { Module } from '@nestjs/common';
import { OpraHttpModule } from '@opra/nestjs-http';
import { CustomersController } from './customers/customers.controller.js';
import { CustomersService } from './customers/customers.service.js';
import * as models from './models/models.js';

@Module({
imports: [
OpraHttpModule.forRoot({
imports: [SomeDependencyModule],
providers: [CustomersService],
exports: [CustomersService],
name: 'CustomerApi',
info: { title: 'Customer API', version: '1.0' },
references: {
shared: () => SharedModels.initApiDocument(),
},
types: [...Object.values(models)],
controllers: [CustomersController],
schemaIsPublic: true,
}),
],
})
export class AppModule {}
OptionDescription
nameAPI name.
infoDocument metadata — title, version, description.
typesData types to register (decorated classes, EnumType results).
controllersOPRA controller classes to register.
referencesNamespaced references to other ApiDocument instances or async thunks.
schemaIsPublicExpose the schema endpoint (GET /$schema) publicly.
importsNestJS modules to import into the OPRA module context.
providersNestJS providers available for injection inside controllers.
exportsProviders to export from the OPRA module to the rest of the application.

Async configuration

Use forRootAsync() when the configuration depends on injected services or environment variables:

import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { OpraHttpModule } from '@opra/nestjs-http';

@Module({
imports: [
ConfigModule.forRoot(),
OpraHttpModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
controllers: [CustomersController],
useFactory: (config: ConfigService) => ({
name: 'CustomerApi',
info: { title: 'Customer API', version: config.get('API_VERSION') },
types: [...Object.values(models)],
}),
}),
],
})
export class AppModule {}

Injecting the document

Once registered, inject ApiDocument anywhere in your application:

import { Injectable } from '@nestjs/common';
import { ApiDocument } from '@opra/common';

@Injectable()
export class ApiInfoService {
constructor(private readonly document: ApiDocument) {}

getTitle() {
return this.document.info.title;
}
}

For transport-specific setup, see the module pages: