Route Decorators
Route decorators are used to expose controller methods as REST API operations. If you are not familiar with the concept of Route or Controller, please see LoopBack Route and LoopBack Controller to learn more about them.
By calling a route decorator, you provide OpenAPI specification to describe the endpoint which the decorated method maps to. You can choose different decorators accordingly or do a composition of them:
API Decorator
Syntax:
@api(spec: ControllerSpec)
@api
is a decorator for the controller class and is appended just before it’s
declared. @api
is used when you have multiple
Paths Objects
that contain all path definitions of your controller. Please note the api specs
defined with @api
will override other api specs defined inside the controller.
For example:
@api({
basePath: '/',
paths: {
'/greet': {
get: {
// You can specify `operationId` as an universal property that's
// understood by other frameworks.
operationId: 'MyController.greet',
'x-operation-name': 'greet',
'x-controller-name': 'MyController',
parameters: [{name: 'name', schema: {type: 'string'}, in: 'query'}],
responses: {
'200': {
description: 'greeting text',
content: {
'application/json': {
schema: {type: 'string'},
},
},
},
},
},
},
},
})
class MyController {
// The operation endpoint defined here will be overridden!
@get('/greet')
greet(@param.query.number('limit') name: string) {}
}
app.controller(MyController);
A more detailed explanation can be found in Specifying Controller APIs
Operation Decorator
Syntax:
@operation(verb: string, path: string, spec?: OperationObject)
@operation
is a controller method decorator. It exposes a Controller method as
a REST API operation and is represented in the OpenAPI spec as an
Operation Object.
You can specify the verb, path, parameters, and response as a specification of
your endpoint, for example:
const spec = {
// You can specify `operationId` as an universal property that's
// understood by other frameworks.
operationId: 'MyController.checkExist'
parameters: [{name: 'name', schema: {type: 'string'}, in: 'query'}],
responses: {
'200': {
description: 'greeting text',
content: {
'application/json': {
schema: {type: 'boolean'},
},
},
},
},
};
class MyController {
@operation('HEAD', '/checkExist', spec)
checkExist(name: string) {}
}
Commonly-used Operation Decorators
Syntax:
@get(path: string, spec?: OperationObject)
Same Syntax for decorators
@post
,
@put
,
@patch
,
@del
You can call these sugar operation decorators as a shortcut of @operation
. For
example:
class MyController {
@get('/greet', spec)
greet(name: string) {}
}
is equivalent to
class MyController {
@operation('GET', '/greet', spec)
greet(name: string) {}
}
Parameter Decorator
Syntax: see API documentation
@param
is applied to controller method parameters to generate an OpenAPI
parameter specification for them.
For example:
import {get, param} from '@loopback/rest';
const categorySpec = {
name: 'category',
in: 'path',
required: true,
schema: {type: 'string'},
};
const pageSizeSpec = {
name: 'pageSize',
in: 'query',
required: false,
schema: {type: 'integer', format: 'int32'},
};
class MyController {
@get('Pets/{category}')
list(
@param(categorySpec) category: string,
@param(pageSizeSpec) pageSize?: number,
) {}
}
Writing the whole parameter specification is tedious, so we’ve created shortcuts
to define the params with the pattern @param.${in}.${type}(${name})
:
- in: The parameter location. It can be one of the following values:
query
,header
, orpath
. - type: A common name of OpenAPI primitive data type.
- name: Name of the parameter. It should be a
string
.
A list of available shortcuts for query
can be found in
API document,
along with the shortcuts for path
and header
.
An equivalent example using the shortcut decorator would be:
class MyController {
@get('/Pets/{category}')
list(
@param.path.string('category') category: string,
@param.query.number('pageSizes') pageSize?: number,
) {}
}
You can find specific use cases in Writing Controller methods
The parameter location cookie is not supported yet, see (https://github.com/loopbackio/loopback-next/issues/997)
Parameter Decorator to support json objects
Note:
LoopBack has switched the definition of json query params from the exploded
,
deep-object
style to the url-encoded
style definition in Open API spec.
The parameter decorator @param.query.object
is applied to generate an Open API
definition for query parameters with JSON values. The generated definition
currently follows the url-encoded
style as shown below.
{
"in": "query",
"content": {
"application/json": {
"schema": {}
}
}
}
The above style where the schema is wrapped
under content[‘application/json’]
supports receiving url-encoded
payload for a json query parameter as per Open
API specification.
To filter results from the GET ‘/todo-list’ endpoint in the todo-list example
with a specific relation, { “include”: [ { “relation”: “todo” } ] }, the following
url-encoded
query parameter can be used,
http://localhost:3000/todos?filter=%7B%22include%22%3A%5B%7B%22relation%22%3A%22todoList%22%7D%5D%7D
As an extension to the url-encoded style, LoopBack also supports queries with exploded values for json query parameters.
GET /todos?filter[where][completed]=false
// filter={where: {completed: 'false'}}
RequestBody Decorator
Syntax: see API documentation
@requestBody()
is applied to a controller method parameter to generate OpenAPI
requestBody specification for it.
Only one parameter can be decorated by @requestBody
per controller method.
A typical
OpenAPI requestBody specification
contains properties description
, required
, and content
:
requestBodySpec: {
description: 'a user',
required: true,
content: {
'application/json': {...schemaSpec},
'application/text': {...schemaSpec},
},
}
In order to use @requestBody
in a parameter type, the model in the parameter
type must be decorated with @model
and @property
:
import {model, property} from '@loopback/repository';
import {Address} from './address.model';
@model()
class User {
@property()
firstname: string;
@property()
lastname: string;
@property()
address: Address;
}
To learn more about decorating models and the corresponding OpenAPI schema, see model decorators.
The model decorators allow type information of the model to be visible to the
spec generator so that @requestBody
can be used on the parameter:
import {User} from '../models/user.model';
import {put} from '@loopback/rest';
class UserController {
@put('/Users/{id}')
async replaceUser(
@param.path.string('id') id: string,
@requestBody() user: User,
) {}
}
For the simplest use case, you can leave the input of @requestBody
empty since
we automatically detect the type of user
and generate the corresponding schema
for it. The default content type is set to be application/json
.
You can also customize the generated requestBody
specification in three ways:
- Add the optional fields
description
andrequired
class MyController {
@put('/Users/{id}')
async replaceUser(
@param.path.string('id') id: string,
@requestBody({
description: 'a modified user',
required: true,
})
user: User,
) {}
}
- Override the content type or define multiple content types
class MyController {
@put('/Users/{id}')
async replaceUser(
@param.path.string('id') id: string,
@requestBody({
content: {
// leave the schema as empty object, the decorator will generate it for both.
'application/text': {},
'application/xml': {},
},
})
user: User,
) {}
}
- Override the schema specification
import {UserSchema, User} from '../model/user.schema';
class MyController {
@put('/Users/{id}')
async replaceUser(
@param.path.string('id') id: string,
@requestBody({
content: {
'application/json': UserSchema,
},
})
user: User,
) {}
}
@requestBody.array
Syntax: see API documentation
@requestBody.array
marks the request body to accept arrays. It is a
lightweight wrapper around @requestBody
and accepts the same parameters.
class MyController {
@post('/Users')
async addUsers(@requestBody.array() users: User[]) {}
}
@requestBody.file
@requestBody.file
marks a request body for multipart/form-data
based file
upload. For example,
import {post, Request, requestBody} from '@loopback/rest';
class MyController {
@post('/pictures')
upload(
@requestBody.file()
request: Request,
) {
// ...
}
}
We plan to support more @requestBody
shortcuts in the future. You can track
the feature in
story 1064.
x-ts-type extension
To simplify schema definition and reference, LoopBack allows x-ts-type
extension for the OpenAPI schema object. The x-ts-type
points to a model class
or simple types. It can be used for parameters, request body and responses. For
example,
import {model, property} from '@loopback/repository';
import {requestBody, post, get} from '@loopback/rest';
@model()
class MyModel {
@property()
name: string;
}
export class MyController {
@get('/', {
responses: {
'200': {
description: 'hello world',
content: {'application/json': {schema: {'x-ts-type': MyModel}}},
},
},
})
hello() {
return 'hello world';
}
@post('/')
greet(
@requestBody({
content: {'application/json': {schema: {'x-ts-type': MyModel}}},
})
body: MyModel,
) {
return `hello ${body.name}`;
}
}
The x-ts-type
can be used for array and object properties too:
Note:
Arrays should be defined with
@requestBody.array
instead.
const schemaWithArrayOfMyModel = {
type: 'array',
items: {
'x-ts-type': MyModel,
},
};
const schemaDeepArrayOfMyModel = {
type: 'array',
items: {
type: 'array',
items: {
'x-ts-type': MyModel,
},
},
};
const schemaWithObjectPropOfMyModel = {
type: 'object',
properties: {
myModel: {
'x-ts-type': MyModel,
},
},
};
export class SomeController {
@post('/my-controller')
greetObjectProperty(
@requestBody({
content: {'application/json': {schema: schemaWithObjectPropOfMyModel}},
})
body: {
myModel: MyModel;
},
): string {
return `hello ${body.myModel.name}!`;
}
@get('/my-controllers', {
responses: {
'200': {
description: 'hello world',
content: {'application/json': {schema: schemaWithArrayOfMyModel}},
},
},
})
everyone(): MyModel[] {
return [{name: 'blue'}, {name: 'red'}];
}
@post('/my-controllers')
greetEveryone(
@requestBody({
content: {'application/json': {schema: schemaDeepArrayOfMyModel}},
})
body: MyModel[][],
): string {
return `hello ${body.map(objs => objs.map(m => m.name))}`;
}
}
anyOf, allOf, oneOf, not
The x-ts-type
extension is also valid as a value in allOf
, anyOf
, oneOf
,
and not
schema keys.
@model
class FooModel extends Model {
@property()
foo: string;
}
@model
class BarModel extends Model {
@property()
bar: string;
}
@model
class BazModel extends Model {
@property()
baz: string;
}
class MyController {
@get('/some-value', {
responses: {
'200': {
description: 'returns a union of two values',
content: {
'application/json': {
schema: {
not: {'x-ts-type': BazModel},
allOf: [{'x-ts-type': FooModel}, {'x-ts-type': BarModel}],
},
},
},
},
},
})
getSomeValue() {
return {foo: 'foo', bar: 'bar'};
}
}
When the OpenAPI spec is generated, the xs-ts-type
is mapped to
{$ref: '#/components/schemas/MyModel'}
and a corresponding schema is added to
components.schemas.MyModel
of the spec.
Convenience Decorators
While you can supply a fully valid OpenAPI specification for the class-level
@api
decorator, and full operation OpenAPI specification for @operation
and
the other convenience decorators, there are also a number of utility decorators
that allow you to supply specific OpenAPI information without requiring you to
use verbose JSON.
Shortcuts for the OpenAPI Spec (OAS) Objects
All of the above are direct exports of @loopback/rest
, but they are also
available under the oas
namespace:
import {oas} from '@loopback/rest';
@oas.api({})
class MyController {
@oas.get('/greet/{id}')
public greet(@oas.param('id') id: string) {}
}
This namespace contains decorators that are specific to the OpenAPI
specification, but are also similar to other well-known decorators available,
such as @deprecated()
@oas.deprecated
API document, OpenAPI Operation Specification
This decorator can currently be applied to class and a class method. It will set
the deprecated
boolean property of the Operation Object. When applied to a
class, it will mark all operation methods of that class as deprecated, unless a
method overloads with @oas.deprecated(false)
.
This decorator does not currently support marking (parameters)[https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#parameter-object] as deprecated.
@oas.deprecated()
class MyController {
@oas.get('/greet')
public async function greet() {
return 'Hello, World!'
}
@oas.get('/greet-v2')
@oas.deprecated(false)
public async function greetV2() {
return 'Hello, World!'
}
}
class MyOtherController {
@oas.get('/echo')
@oas.deprecated()
public async function echo() {
return 'Echo!'
}
}
@oas.visibility
This decorator can be applied to class and/or a class method. It will set the
x-visibility
property, which dictates if a class method appears in the OAS3
spec. When applied to a class, it will mark all operation methods of that class,
unless a method overloads with @oas.visibility(<different visibility type>)
.
When not explicitly defined, x-visibility
is undefined
, which is identical
to x-visibility: 'documented
.
Currently, the supported values are documented
or undocumented
.
This decorator does not currently support marking (parameters)[https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#parameter-object].
@oas.visibility('undocumented')
class MyController {
@oas.get('/greet')
async function greet() {
return 'Hello, World!'
}
@oas.get('/greet-v2')
@oas.visibility('documented')
async function greetV2() {
return 'Hello, World!'
}
}
class MyOtherController {
@oas.get('/echo')
@oas.visibility('undocumented')
async function echo() {
return 'Echo!'
}
}
@oas.response
API document, OpenAPI Response Specification
This decorator lets you easily add response specifications using Models
from
@loopback/repository
. The convenience decorator sets the content-type
to
application/json
, and the response description to the string value in the
http-status
module. The models become references through the x-ts-type
schema extension.
@model()
class SuccessModel extends Model {
constructor(err: Partial<SuccessModel>) {
super(err);
}
@property({default: 'Hi there!'})
message: string;
}
class GenericError extends Model {
@property()
message: string;
}
class MyController {
@oas.get('/greet')
@oas.response(200, SuccessModel)
@oas.response(500, GenericError)
greet() {
return new SuccessModel({message: 'Hello, world!'});
}
}
{
"paths": {
"/greet": {
"get": {
"responses": {
"200": {
"description": "Ok",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SuccessModel"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GenericError"
}
}
}
}
}
}
}
}
}
Using many models
For a given response code, it’s possible to have a path that could return one of
many errors. The @oas.response
decorator lets you pass multiple Models as
arguments. They’re combined using an anyOf
keyword.
class FooNotFound extends Model {
@property()
message: string;
}
class BarNotFound extends Model {
@property()
message: string;
}
class BazNotFound extends Model {
@property()
message: string;
}
class MyController {
@oas.get('/greet/{foo}/{bar}')
@oas.response(404, FooNotFound, BarNotFound)
@oas.response(404, BazNotFound)
greet() {
return new SuccessModel({message: 'Hello, world!'});
}
}
{
"paths": {
"/greet": {
"get": {
"responses": {
"404": {
"description": "Not Found",
"content": {
"application/json": {
"schema": {
"anyOf": [
{"$ref": "#/components/schemas/FooNotFound"},
{"$ref": "#/components/schemas/BarNotFound"},
{"$ref": "#/components/schemas/BazNotFound"}
]
}
}
}
}
}
}
}
}
}
Using ReferenceObject, ResponseObjects, ContentObjects
You don’t have to use loopback Models
to use this convenience decorator. Valid
ReferenceObjects
, ContentObjects
, and ResponseObjects
are also valid.
class MyController {
// this is a valid SchemaObject
@oas.get('/schema-object')
@oas.response(200, {
type: 'object',
properties: {
message: 'string',
},
required: 'string',
})
returnFromSchemaObject() {
return {message: 'Hello, world!'};
}
// this is a valid ResponseObject
@oas.get('/response-object')
@oas.response(200, {
content: {
'application/pdf': {
schema: {
type: 'string',
format: 'base64',
},
},
},
})
returnFromResponseObject() {
return {message: 'Hello, world!'};
}
// this is a valid ResponseObject
@oas.get('/reference-object')
@oas.response(200, {$ref: '#/path/to/schema'})
returnFromResponseObject() {
return {message: 'Hello, world!'};
}
}
Using @oas.response.file
@oas.response.file
is a shortcut decorator to describe response object for
file download. For example:
import {get, oas, param, RestBindings, Response} from '@loopback/rest';
class MyController {
@get('/files/{filename}')
@oas.response.file('image/jpeg', 'image/png')
download(
@param.path.string('filename') fileName: string,
@inject(RestBindings.Http.RESPONSE) response: Response,
) {
// use response.download(...);
}
}
Using more options
The @oas.response
convenience decorator makes some assumptions for you in
order to provide a level of convenience. The @operation
decorator and the
method convenience decorators let you write a full, complete, and completely
valid OperationObject
.
@oas.tags
API document, OpenAPI Operation Specification
This decorator can be applied to a controller class and to controller class
methods. It will set the tags
array string property of the Operation Object.
When applied to a class, it will mark all operation methods of that class with
those tags. Usage on both the class and method will combine the tags.
@oas.tags('Foo', 'Bar')
class MyController {
@oas.get('/greet')
public async greet() {
// tags will be [Foo, Bar]
}
@oas.tags('Baz')
@oas.get('/echo')
public async echo() {
// tags will be [Foo, Bar, Baz]
}
}
This decorator does not affect the top-level tags
section defined in the
OpenAPI Tag Object specification.
This decorator only affects the spec partial generated at the class level. You
may find that your final tags also include a tag for the controller name.
Shortcuts for Filter and Where params
CRUD APIs often expose REST endpoints that take filter
and where
query
parameters. For example:
class TodoController {
async find(
@param.query.object('filter', getFilterSchemaFor(Todo))
filter?: Filter<Todo>,
): Promise<Todo[]> {
return this.todoRepository.find(filter);
}
async findById(
@param.path.number('id') id: number,
@param.query.object('filter', getFilterSchemaFor(Todo))
filter?: Filter<Todo>,
): Promise<Todo> {
return this.todoRepository.findById(id, filter);
}
async count(
@param.query.object('where', getWhereSchemaFor(Todo)) where?: Where<Todo>,
): Promise<Count> {
return this.todoRepository.count(where);
}
}
To simplify the parameter decoration for filter
and where
, we introduce two
sugar decorators:
@param.filter
: For afilter
query parameter@param.where
: For awhere
query parameter
Now the code from above can be refined as follows:
class TodoController {
async find(
@param.filter(Todo)
filter?: Filter<Todo>,
): Promise<Todo[]> {
return this.todoRepository.find(filter);
}
async findById(
@param.path.number('id') id: number,
@param.filter(Todo, {exclude: 'where'}) filter?: FilterExcludingWhere<Todo>,
): Promise<Todo> {
return this.todoRepository.findById(id, filter);
}
async count(@param.where(Todo) where?: Where<Todo>): Promise<Count> {
return this.todoRepository.count(where);
}
}