UNPKG

@loopback/docs

Version:

Documentation files rendered at [https://loopback.io](https://loopback.io)

960 lines (799 loc) 24.5 kB
--- lang: en title: 'OpenAPI Decorators' keywords: LoopBack 4.0, LoopBack, Node.js, TypeScript, OpenAPI, Decorator sidebar: lb4_sidebar permalink: /doc/en/lb4/Decorators_openapi.html --- ## 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](../Route.md) and [LoopBack Controller](../Controller.md) 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)`](https://loopback.io/doc/en/lb4/apidocs.openapi-v3.api.html) `@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](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#pathsObject) 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: ```ts @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 overriden! @get('/greet') greet(@param.query.number('limit') name: string) {} } app.controller(MyController); ``` A more detailed explanation can be found in [Specifying Controller APIs](../Controller.md#specifying-controller-apis) ### Operation Decorator Syntax: [`@operation(verb: string, path: string, spec?: OperationObject)`](https://loopback.io/doc/en/lb4/apidocs.openapi-v3.operation.html) `@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](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#operation-object). You can specify the verb, path, parameters, and response as a specification of your endpoint, for example: ```ts 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)`](https://loopback.io/doc/en/lb4/apidocs.openapi-v3.get.html) Same Syntax for decorators [`@post`](https://loopback.io/doc/en/lb4/apidocs.openapi-v3.post.html) , [`@put`](https://loopback.io/doc/en/lb4/apidocs.openapi-v3.put.html) , [`@patch`](https://loopback.io/doc/en/lb4/apidocs.openapi-v3.patch.html) , [`@del`](https://loopback.io/doc/en/lb4/apidocs.openapi-v3.del.html) You can call these sugar operation decorators as a shortcut of `@operation`. For example: ```ts class MyController { @get('/greet', spec) greet(name: string) {} } ``` is equivalent to ```ts class MyController { @operation('GET', '/greet', spec) greet(name: string) {} } ``` ### Parameter Decorator Syntax: see [API documentation](https://loopback.io/doc/en/lb4/apidocs.openapi-v3.param.html) `@param` is applied to controller method parameters to generate an OpenAPI parameter specification for them. For example: ```ts 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`, or `path`. - type: A [common name of OpenAPI primitive data type](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#data-types). - name: Name of the parameter. It should be a `string`. A list of available shortcuts for `query` can be found in [API document](https://loopback.io/doc/en/lb4/apidocs.openapi-v3.param.query.html), along with the shortcuts for `path` and `header`. An equivalent example using the shortcut decorator would be: ```ts 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](../Controller.md#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 {% include note.html content=" 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. ```json { "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](https://loopback.io/doc/en/lb4/apidocs.openapi-v3.requestbody.html) `@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](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#requestBodyObject) contains properties `description`, `required`, and `content`: ```ts 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`: ```ts 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](../Model.md#model-decorator)._ 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: {% include code-caption.html content="/src/controllers/user.controller.ts" %} ```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` ```ts 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 ```ts 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 ```ts 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](https://loopback.io/doc/en/lb4/apidocs.openapi-v3.requestbody.array.html) `@requestBody.array` marks the request body to accept arrays. It is a lightweight wrapper around `@requestBody` and accepts the same parameters. ```ts 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, ```ts 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](https://github.com/loopbackio/loopback-next/issues/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, ```ts 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: {% include note.html content="Arrays should be defined with [`@requestBody.array`](#@requestBody.array) instead." %} ```ts 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. ```ts @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: ```ts 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](https://loopback.io/doc/en/lb4/apidocs.openapi-v3.deprecated.html), [OpenAPI Operation Specification](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#operation-object) 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. ```ts @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 [API document](https://loopback.io/doc/en/lb4/apidocs.openapi-v3.visibility.html) 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]. ```ts @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](https://loopback.io/doc/en/lb4/apidocs.openapi-v3.oas.html#oas-variable), [OpenAPI Response Specification](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#response-object) 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. ```ts @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!'}); } } ``` ```json { "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. ```ts 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!'}); } } ``` ```json { "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. ```ts 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: ```ts 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](https://loopback.io/doc/en/lb4/apidocs.openapi-v3.oas.html#oas-variable), [OpenAPI Operation Specification](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#operation-object) 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. ```ts @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](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#tag-object). 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: ```ts 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: ```ts 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); } } ```