Entity Method Templates
HttpOperation.Entity.* decorators are pre-configured operation templates for the eight standard CRUD patterns. Instead of manually declaring HTTP methods, query parameters, request bodies, and response codes, a single decorator wires everything up — ready to chain additional options on top.
Available templates
| Decorator | HTTP | Composition key | Purpose |
|---|---|---|---|
Entity.Get | GET | Entity.Get | Fetch a single resource by key |
Entity.FindMany | GET | Entity.FindMany | Fetch a pageable, filterable, sortable collection |
Entity.Create | POST | Entity.Create | Create a new resource |
Entity.Update | PATCH | Entity.Update | Partially update a single resource |
Entity.UpdateMany | PATCH | Entity.UpdateMany | Partially update multiple resources by filter |
Entity.Delete | DELETE | Entity.Delete | Delete a single resource by key |
Entity.DeleteMany | DELETE | Entity.DeleteMany | Delete multiple resources by filter |
Entity.Replace | POST | Entity.Replace | Fully replace a single resource by key |
Call signatures
All Entity templates accept the same two call forms:
// 1. Type first (most common)
@HttpOperation.Entity.Get(Customer)
@HttpOperation.Entity.Get(Customer, { description: 'Fetch a customer' })
// 2. Args object
@HttpOperation.Entity.Get({ type: Customer, description: 'Fetch a customer' })
Every template accepts the standard HttpOperation.Options fields (path, description, immediateFetch, etc.) except method and requestBody — those are controlled by the template itself.
Entity.Get
Fetches a single resource. Pre-wires:
projectionquery parameter — controls which fields are returned in the response object. Accepts a comma-separated list of field names, each optionally prefixed with a sign:- No prefix → inclusion list: only the listed fields are returned (
?projection=givenName,familyName) -prefix → exclusion: all default fields are returned except the listed ones (?projection=-password,-internalCode)+prefix → add an exclusive field: fields marked asexclusiveon the model are never returned by default;+explicitly opts in to them (?projection=+sensitiveData)
- No prefix → inclusion list: only the listed fields are returned (
200 OK— resource returned204 No Content— key matched no resource422 Unprocessable Entity— validation error
Extra chain method:
| Method | Description |
|---|---|
.KeyParam(name, typeOrOptions?) | Adds a path parameter marked as the entity key. Appends @:name to the controller path automatically. |
@HttpController({ path: 'customers' })
@(HttpController.KeyParam('customerId', 'integer'))
export class CustomerController {
@HttpOperation.Entity.Get(Customer)
async get(ctx: HttpContext): Promise<PartialDTO<Customer> | undefined> {
const { key, options } = await MongoAdapter.parseRequest(ctx);
return this.service.for(ctx).findById(key, options);
}
}
When the controller already sets the key via @HttpController.KeyParam, you don't need .KeyParam() on the operation. Use .KeyParam() on the operation when the path parameter is operation-specific.
Entity.FindMany
Fetches a pageable, filterable, sortable collection. Pre-wires:
limit— number of results to return (integer,minValue: 1, optionalmaxValuefrom args)skip— number of results to skip before returning (integer,minValue: 1)count— boolean; whentrue, the response includes the total number of matching resources intotalMatchesprojection— controls which fields each returned object includes or excludes. Comma-separated field names with optional sign prefix:- No prefix → return only these fields (
?projection=_id,givenName,familyName) -prefix → return all default fields except these (?projection=-password)+prefix → also include fields markedexclusiveon the model, which are otherwise never returned (?projection=+encryptedNote)- Nested fields use dot notation (
?projection=address.cityor?projection=-address.internalCode)
- No prefix → return only these fields (
200 OK— array of resources (withpartial: 'deep')422 Unprocessable Entity
Extra chain methods:
| Method | Description |
|---|---|
.Filter(field, operators?) | Enables filtering on field. operators is a comma-separated string or an array of OPRA comparison operator strings. Default operators: =, !=. |
.SortFields(...fields) | Declares which fields are sortable. Accepts field names (optionally prefixed with +/- to restrict direction) or a Record<name, mappedName> map. |
.DefaultSort(...fields) | Sets the default sort order used when the client sends no sort parameter. |
Args-specific options:
| Option | Type | Description |
|---|---|---|
defaultLimit | number | Default page size when client omits limit. |
maxLimit | number | Hard cap on limit. |
defaultProjection | string[] | Field list applied when the client sends no projection parameter. Follows the same sign-prefix syntax (-field to exclude, +field to include exclusive fields). |
@(HttpOperation.Entity.FindMany(Customer)
.Filter('_id', '=, !=, <, >, >=, <=, in, !in')
.Filter('givenName', ['=', '!=', 'like', '!like', 'ilike', '!ilike'])
.Filter('familyName', ['=', '!=', 'like', '!like'])
.Filter('gender')
.Filter('active')
.Filter('birthDate')
.SortFields('_id', 'givenName', 'familyName', 'gender')
.DefaultSort('givenName'))
async findMany(ctx: HttpContext) {
const { options } = await MongoAdapter.parseRequest(ctx);
return this.service.for(ctx).findMany(options);
}
Entity.Create
Creates a new resource. Pre-wires:
projectionquery parameter — controls which fields the created resource includes in the response (supports-/+prefixes and dot notation)- Request body: required,
immediateFetch: true 201 Created— created resource returned422 Unprocessable Entity
Args-specific option — requestBody:
| Field | Type | Description |
|---|---|---|
requestBody.type | Type | string | Use a different type for the request body than the response type (e.g. strip _id or internal fields). |
requestBody.description | string | Description for the body in the schema. |
requestBody.maxContentSize | number | Maximum allowed body size in bytes. |
requestBody.immediateFetch | boolean | Override the default true. |
@HttpOperation.Entity.Create(Customer, {
requestBody: {
type: OmitType(Customer, ['_id']), // clients don't submit the server-assigned id
},
})
async create(ctx: HttpContext): Promise<PartialDTO<Customer>> {
const { data, options } = await MongoAdapter.parseRequest(ctx);
return this.service.for(ctx).create(data, options);
}
Entity.Update
Partially updates a single resource (PATCH). Pre-wires:
projectionquery parameter — controls which fields the updated resource includes in the response (supports-/+prefixes and dot notation)- Request body: required,
partial: 'deep',allowPatchOperators: true,allowNullOptionals: true,keepKeyFields: true,immediateFetch: true 200 OK— updated resource returned204 No Content— key matched no resource422 Unprocessable Entity
Extra chain methods:
| Method | Description |
|---|---|
.KeyParam(name, typeOrOptions?) | Same as Entity.Get — adds a key path parameter. |
.Filter(field, operators?) | Enables filtering for scoped updates (update by filter condition in addition to key). |
Args-specific option — requestBody:
| Field | Type | Description |
|---|---|---|
requestBody.type | Type | string | Override the body type. |
requestBody.allowPatchOperators | boolean | Default true. Allow _$push/_$pull in the body. |
requestBody.allowNullOptionals | boolean | Default true. Allow null for optional fields. |
@HttpOperation.Entity.Update(Customer)
async update(ctx: HttpContext) {
const { key, data, options } = await MongoAdapter.parseRequest(ctx);
return this.service.for(ctx).update(key, data, options);
}
Entity.UpdateMany
Partially updates multiple resources (PATCH). Pre-wires:
- Request body: same defaults as
Entity.Update(partial deep, patch operators, null optionals) 200 OK—affectedcount returned422 Unprocessable Entity
Extra chain method:
| Method | Description |
|---|---|
.Filter(field, operators?) | Enables filtering to scope which resources are updated. |
@(HttpOperation.Entity.UpdateMany(Customer)
.Filter('_id', '=, !=, <, >, >=, <=, in, !in')
.Filter('active'))
async updateMany(ctx: HttpContext) {
const { data, options } = await MongoAdapter.parseRequest(ctx);
return this.service.for(ctx).updateMany(data, options);
}
Entity.Delete
Deletes a single resource by key. Pre-wires:
200 OK—affectedcount returned422 Unprocessable Entity
Extra chain method:
| Method | Description |
|---|---|
.KeyParam(name, typeOrOptions?) | Adds a key path parameter. |
@HttpOperation.Entity.Delete(Customer)
async delete(ctx: HttpContext) {
const { key, options } = await MongoAdapter.parseRequest(ctx);
return this.service.for(ctx).delete(key, options);
}
Entity.DeleteMany
Deletes multiple resources that match a filter. Pre-wires:
200 OK—affectedcount returned422 Unprocessable Entity
Extra chain method:
| Method | Description |
|---|---|
.Filter(field, operators?) | Enables filtering to scope which resources are deleted. |
@(HttpOperation.Entity.DeleteMany(Customer)
.Filter('_id', '=, !=, <, >, >=, <=, in, !in')
.Filter('active'))
async deleteMany(ctx: HttpContext) {
const { options } = await MongoAdapter.parseRequest(ctx);
return this.service.for(ctx).deleteMany(options);
}
Entity.Replace
Fully replaces a single resource (POST semantics in OPRA). Pre-wires:
projectionquery parameter — controls which fields the returned resource includes in the response (supports-/+prefixes and dot notation)200 OK— replaced resource returned204 No Content— key matched no resource422 Unprocessable Entity
Extra chain method:
| Method | Description |
|---|---|
.KeyParam(name, typeOrOptions?) | Adds a key path parameter. |
@HttpOperation.Entity.Replace(Customer)
async replace(ctx: HttpContext) {
const { key, data, options } = await MongoAdapter.parseRequest(ctx);
return this.service.for(ctx).replace(key, data, options);
}
.Filter() operators
The .Filter() chain method accepts a field name and an optional set of comparison operators.
Operators can be supplied as a comma-separated string or as an array:
.Filter('age', '=, !=, <, >, >=, <=') // string form
.Filter('age', ['=', '!=', '<', '>', '>=', '<=']) // array form
.Filter('name', ['=', '!=', 'like', '!like'])
.Filter('active') // default operators: = and !=
Available operators:
| Operator | Meaning |
|---|---|
= | Equal |
!= | Not equal |
< | Less than |
> | Greater than |
<= | Less than or equal |
>= | Greater than or equal |
in | Value is in array |
!in | Value is not in array |
like | String pattern match (case-sensitive) |
!like | String pattern does not match |
ilike | Case-insensitive like |
!ilike | Case-insensitive !like |
Complete CRUD example
A typical controller pair — a collection controller and a single-resource controller:
import {
HttpController,
HttpOperation,
OmitType,
OperationResult,
} from '@opra/common';
import { HttpContext } from '@opra/http';
import { MongoAdapter } from '@opra/mongodb';
@HttpController({ path: 'customers' })
export class CustomersController {
@HttpOperation.Entity.Create(Customer, {
requestBody: { type: OmitType(Customer, ['_id']) },
})
async create(ctx: HttpContext) {
const { data, options } = await MongoAdapter.parseRequest(ctx);
return this.service.for(ctx).create(data, options);
}
@(HttpOperation.Entity.FindMany(Customer)
.Filter('_id', '=, !=, in, !in')
.Filter('givenName', ['=', '!=', 'like', '!like'])
.Filter('active')
.SortFields('_id', 'givenName', 'familyName')
.DefaultSort('givenName'))
async findMany(ctx: HttpContext) {
const { options } = await MongoAdapter.parseRequest(ctx);
return this.service.for(ctx).findMany(options);
}
@(HttpOperation.Entity.DeleteMany(Customer)
.Filter('_id', '=, !=, in, !in'))
async deleteMany(ctx: HttpContext) {
const { options } = await MongoAdapter.parseRequest(ctx);
return this.service.for(ctx).deleteMany(options);
}
@(HttpOperation.Entity.UpdateMany(Customer)
.Filter('_id', '=, !=, in, !in'))
async updateMany(ctx: HttpContext) {
const { data, options } = await MongoAdapter.parseRequest(ctx);
return this.service.for(ctx).updateMany(data, options);
}
}
@(HttpController({ path: 'customers' })
.KeyParam('customerId', 'integer'))
export class CustomerController {
@HttpOperation.Entity.Get(Customer)
async get(ctx: HttpContext) {
const { key, options } = await MongoAdapter.parseRequest(ctx);
return this.service.for(ctx).findById(key, options);
}
@HttpOperation.Entity.Update(Customer)
async update(ctx: HttpContext) {
const { key, data, options } = await MongoAdapter.parseRequest(ctx);
return this.service.for(ctx).update(key, data, options);
}
@HttpOperation.Entity.Delete(Customer)
async delete(ctx: HttpContext) {
const { key, options } = await MongoAdapter.parseRequest(ctx);
return this.service.for(ctx).delete(key, options);
}
}