Skip to main content

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

DecoratorHTTPComposition keyPurpose
Entity.GetGETEntity.GetFetch a single resource by key
Entity.FindManyGETEntity.FindManyFetch a pageable, filterable, sortable collection
Entity.CreatePOSTEntity.CreateCreate a new resource
Entity.UpdatePATCHEntity.UpdatePartially update a single resource
Entity.UpdateManyPATCHEntity.UpdateManyPartially update multiple resources by filter
Entity.DeleteDELETEEntity.DeleteDelete a single resource by key
Entity.DeleteManyDELETEEntity.DeleteManyDelete multiple resources by filter
Entity.ReplacePOSTEntity.ReplaceFully 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:

  • projection query 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 as exclusive on the model are never returned by default; + explicitly opts in to them (?projection=+sensitiveData)
  • 200 OK — resource returned
  • 204 No Content — key matched no resource
  • 422 Unprocessable Entity — validation error

Extra chain method:

MethodDescription
.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, optional maxValue from args)
  • skip — number of results to skip before returning (integer, minValue: 1)
  • count — boolean; when true, the response includes the total number of matching resources in totalMatches
  • projection — 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 marked exclusive on the model, which are otherwise never returned (?projection=+encryptedNote)
    • Nested fields use dot notation (?projection=address.city or ?projection=-address.internalCode)
  • 200 OK — array of resources (with partial: 'deep')
  • 422 Unprocessable Entity

Extra chain methods:

MethodDescription
.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:

OptionTypeDescription
defaultLimitnumberDefault page size when client omits limit.
maxLimitnumberHard cap on limit.
defaultProjectionstring[]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:

  • projection query 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 returned
  • 422 Unprocessable Entity

Args-specific option — requestBody:

FieldTypeDescription
requestBody.typeType | stringUse a different type for the request body than the response type (e.g. strip _id or internal fields).
requestBody.descriptionstringDescription for the body in the schema.
requestBody.maxContentSizenumberMaximum allowed body size in bytes.
requestBody.immediateFetchbooleanOverride 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:

  • projection query 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 returned
  • 204 No Content — key matched no resource
  • 422 Unprocessable Entity

Extra chain methods:

MethodDescription
.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:

FieldTypeDescription
requestBody.typeType | stringOverride the body type.
requestBody.allowPatchOperatorsbooleanDefault true. Allow _$push/_$pull in the body.
requestBody.allowNullOptionalsbooleanDefault 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 OKaffected count returned
  • 422 Unprocessable Entity

Extra chain method:

MethodDescription
.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 OKaffected count returned
  • 422 Unprocessable Entity

Extra chain method:

MethodDescription
.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 OKaffected count returned
  • 422 Unprocessable Entity

Extra chain method:

MethodDescription
.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:

  • projection query parameter — controls which fields the returned resource includes in the response (supports -/+ prefixes and dot notation)
  • 200 OK — replaced resource returned
  • 204 No Content — key matched no resource
  • 422 Unprocessable Entity

Extra chain method:

MethodDescription
.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:

OperatorMeaning
=Equal
!=Not equal
<Less than
>Greater than
<=Less than or equal
>=Greater than or equal
inValue is in array
!inValue is not in array
likeString pattern match (case-sensitive)
!likeString pattern does not match
ilikeCase-insensitive like
!ilikeCase-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);
}
}

Methods · Query Parameters · Path Parameters