Skip to main content

HTTP Methods

Every OPRA HTTP operation is associated with an HTTP method. The eight method decorators — GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS, and SEARCH — are called on HttpOperation to declare an operation on a controller method.


Method decorators

DecoratorHTTP MethodTypical use
HttpOperation.GET(options?)GETFetch a resource or collection
HttpOperation.POST(options?)POSTCreate a resource
HttpOperation.PUT(options?)PUTReplace a resource entirely
HttpOperation.PATCH(options?)PATCHApply a partial update
HttpOperation.DELETE(options?)DELETERemove a resource
HttpOperation.HEAD(options?)HEADFetch headers only
HttpOperation.OPTIONS(options?)OPTIONSDescribe allowed methods
HttpOperation.SEARCH(options?)SEARCHComplex query via request body

All eight accept the same optional options object and return an HttpOperationDecorator — a chainable builder that also acts as a TypeScript decorator.


Basic usage

Without chaining, apply the decorator directly to the method:

import { HttpController, HttpOperation } from '@opra/common';
import { HttpContext } from '@opra/http';

@HttpController({ path: 'customers' })
export class CustomersController {
@HttpOperation.GET({ path: ':id', description: 'Returns a customer by id' })
async getOne(ctx: HttpContext) {}

@HttpOperation.DELETE({ path: ':id' })
async delete(ctx: HttpContext) {}
}

Chaining

Every method decorator returns an HttpOperationDecorator with chainable methods for declaring parameters, request body, and responses. When chaining, wrap the entire expression in parentheses so TypeScript applies the result as a decorator:

@(HttpOperation.GET({ path: ':id' })
.PathParam('id', { type: 'integer' })
.Response(200, { type: Customer }))
async getOne(ctx: HttpContext) {
const { id } = ctx.pathParams;
}

@(HttpOperation.GET()
.QueryParam('status', { type: 'string' })
.QueryParam('limit', { type: 'integer', default: 20 })
.Response(200, { type: ArrayType(Customer) }))
async list(ctx: HttpContext) {
const { status, limit } = ctx.queryParams;
}

@(HttpOperation.POST()
.RequestContent(Customer)
.Response(201, { type: Customer }))
async create(ctx: HttpContext) {
const body = await ctx.getBody<Customer>();
}

Chainable methods

MethodDescription
.QueryParam(name, options?)Declare a query string parameter
.PathParam(name, options?)Declare a path segment parameter
.Header(name, options?)Declare a request header parameter
.Cookie(name, options?)Declare a cookie parameter
.RequestContent(type, options?)Declare a typed request body content
.MultipartContent(fields, options?)Declare a multipart request body
.Response(statusCode, options?)Declare a response shape
.UseType(type)Register an additional type in the operation's type map

Options

OptionTypeDescription
pathstringURL path relative to the controller. Use :name syntax for path parameters (e.g. ':id', 'archive/:year/:month').
mergePathbooleanWhen true, concatenate with the parent path without a separator (useful for query suffixes).
descriptionstringHuman-readable description of the operation, included in the generated schema.
immediateFetchbooleanRead and buffer the request body immediately before calling the handler. Use when the body is always needed.
allowPatchOperatorsbooleanAccept _$push / _$pull patch operator fields in the decoded body.
allowNullOptionalsbooleanAllow optional body fields to be null in addition to absent. Useful for PATCH endpoints where null means "clear this field".
requestBodyHttpRequestBody.OptionsPre-configure the request body (size limits, content, etc.) without using .RequestContent().

Declaring response type

Use .Response() to declare what the operation returns. This registers the response in the schema and tells OPRA clients what to expect:

@(HttpOperation.GET({ path: ':id' })
.Response(200, { type: Customer }))
async getOne(ctx: HttpContext) {}

@(HttpOperation.GET()
.Response(200, { type: ArrayType(Customer) }))
async list(ctx: HttpContext) {}

@(HttpOperation.POST()
.RequestContent(Customer)
.Response(201, { type: Customer })
.Response(409, { description: 'Conflict — duplicate key' }))
async create(ctx: HttpContext) {}
note

For operations that return a collection, wrap the type with ArrayType(). Without it, the schema describes a single object even if your handler returns an array.


PATCH — partial updates

PATCH endpoints typically accept partial bodies. Use allowNullOptionals when clients need to send null to explicitly clear a field:

@(HttpOperation.PATCH({ path: ':id' })
.PathParam('id', { type: 'integer' })
.RequestContent('application/json', { partial: true, allowNullOptionals: true })
.Response(200, { type: Customer }))
async update(ctx: HttpContext) {
const body = await ctx.getBody<Partial<Customer>>();
// body.email may be undefined (omitted), a string, or null (clear it)
}

SEARCH — query via request body

SEARCH is an HTTP method extension for complex queries that are too large for a query string. It is treated like GET semantically but accepts a request body:

@(HttpOperation.SEARCH()
.RequestContent(CustomerFilter)
.Response(200, { type: ArrayType(Customer) }))
async search(ctx: HttpContext) {
const filter = await ctx.getBody<CustomerFilter>();
}

Full example

import { ArrayType, EnumType, HttpController, HttpOperation } from '@opra/common';
import { HttpContext } from '@opra/http';

@HttpController({ path: 'customers' })
@HttpController.QueryParam('locale', { type: 'string', required: false })
export class CustomersController {
@(HttpOperation.GET()
.QueryParam('status', {
type: EnumType(['active', 'inactive']),
required: false,
})
.QueryParam('limit', { type: 'integer', default: 20 })
.QueryParam('skip', { type: 'integer', default: 0 })
.Response(200, { type: ArrayType(Customer) }))
async list(ctx: HttpContext) {
const { status, limit, skip, locale } = ctx.queryParams;
}

@(HttpOperation.GET({ path: ':id' })
.PathParam('id', { type: 'integer' })
.Response(200, { type: Customer }))
async getOne(ctx: HttpContext) {
const { id } = ctx.pathParams;
}

@(HttpOperation.POST()
.RequestContent(Customer)
.Response(201, { type: Customer }))
async create(ctx: HttpContext) {
const body = await ctx.getBody<Customer>();
}

@(HttpOperation.PATCH({ path: ':id' })
.PathParam('id', { type: 'integer' })
.RequestContent('application/json', { partial: true, allowNullOptionals: true })
.Response(200, { type: Customer }))
async update(ctx: HttpContext) {
const body = await ctx.getBody<Partial<Customer>>();
}

@(HttpOperation.DELETE({ path: ':id' })
.PathParam('id', { type: 'integer' })
.Response(204))
async delete(ctx: HttpContext) {
const { id } = ctx.pathParams;
}
}

Query Parameters · Path Parameters