Page Contents

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: {
        '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 overriden!
  @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 = {
  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}):

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/strongloop/loopback-next/issues/997)

Parameter Decorator to support json objects

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:

/src/controllers/user.controller.ts

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 and required
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:

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 extention 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.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 extention.

@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 a filter query parameter
  • @param.where: For a where 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);
  }
}