Defining APIs
An OPRA API is a set of controllers — TypeScript classes whose methods become operations. You attach a decorator to the class to declare the transport and path, and a decorator to each method to declare the operation. OPRA handles routing, validation, encoding, and error responses automatically.
The same pattern works across all three supported transports:
HTTP request ──→ @HttpController ──→ @HttpOperation ──→ handler ──→ response
Socket.IO event ─→ @WSController ──→ @WSOperation ──→ handler ──→ acknowledgement
MQ message ──→ @MQController ──→ @MQOperation ──→ handler ──→ (optional reply)
HTTP
@HttpController groups HTTP operations under a base path. @HttpOperation binds each method to an HTTP verb and optional sub-path.
import { HttpController, HttpOperation } from '@opra/common';
import { HttpContext } from '@opra/http';
@HttpController({ path: 'customers' })
export class CustomersController {
@(HttpOperation.GET()
.Response(200, { type: Customer }))
async list(ctx: HttpContext) {
return this.service.findAll();
}
@(HttpOperation.POST()
.RequestContent(CustomerCreate)
.Response(201, { type: Customer }))
async create(ctx: HttpContext) {
return this.service.create(await ctx.getBody());
}
@(HttpOperation.DELETE({ path: ':id' })
.PathParam('id', { type: 'integer' })
.Response(200))
async remove(ctx: HttpContext) {
return this.service.delete(ctx.pathParams.id);
}
}
For standard CRUD resources, Entity method templates pre-wire the HTTP method, query parameters, request body, and response codes in a single decorator:
@HttpController({ path: 'customers' })
export class CustomersController {
@HttpOperation.Entity.Create(Customer)
async create(ctx: HttpContext) { ... }
@(HttpOperation.Entity.FindMany(Customer)
.Filter('givenName', ['=', 'like'])
.SortFields('givenName', 'familyName'))
async findMany(ctx: HttpContext) { ... }
@HttpOperation.Entity.Update(Customer)
async update(ctx: HttpContext) { ... }
@HttpOperation.Entity.Delete(Customer)
async delete(ctx: HttpContext) { ... }
}
→ HTTP Controllers · Methods · Entity Method Templates
Socket.IO
@WSController groups Socket.IO event handlers under a namespace. The event name defaults to the method name.
import { WSController, WSOperation } from '@opra/common';
import { WsContext } from '@opra/socket.io';
@WSController('customers')
export class CustomersWsController {
@WSOperation({ response: Customer })
async get(ctx: WsContext) {
return this.service.findById(ctx.params.id);
}
@WSOperation({ event: 'customer:create', response: Customer })
async create(ctx: WsContext) {
return this.service.create(ctx.input);
}
}
OPRA validates incoming event payloads and encodes outgoing acknowledgements against the declared types — the same codec pipeline as HTTP.
Message Queue
@MQController groups message handlers for Kafka and RabbitMQ topics. The channel name defaults to the method name.
import { MQController, MQOperation } from '@opra/common';
import { MQContext } from '@opra/kafka';
@MQController()
export class OrdersController {
@MQOperation(OrderCreate)
async created(ctx: MQContext) {
await this.service.process(ctx.input);
}
@(MQOperation(OrderPatch).Response(Order))
async updated(ctx: MQContext) {
return this.service.update(ctx.input);
}
}
What OPRA does for every operation
Regardless of the transport, for every incoming request OPRA:
- Parses path params, query params, headers, cookies, and body against their declared types
- Validates — rejects invalid values with a structured error before the handler runs
- Coerces — converts string query params to
integer,date, etc. as declared - Scopes — strips fields the caller is not authorized to read or write
- Encodes the return value against the declared response type
- Serializes and sends the response
Your handler receives clean, typed data and returns a plain value — the framework takes care of the rest.