Skip to main content

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.

Socket.IO Controllers


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);
}
}

Message Queue Controllers


What OPRA does for every operation

Regardless of the transport, for every incoming request OPRA:

  1. Parses path params, query params, headers, cookies, and body against their declared types
  2. Validates — rejects invalid values with a structured error before the handler runs
  3. Coerces — converts string query params to integer, date, etc. as declared
  4. Scopes — strips fields the caller is not authorized to read or write
  5. Encodes the return value against the declared response type
  6. Serializes and sends the response

Your handler receives clean, typed data and returns a plain value — the framework takes care of the rest.