Cell - OpenAPI Component
Add @celljs/openapi to the dependencies to document the API of your @celljs/mvc controllers: the application serves an OpenAPI 3.0 document at /openapi.json and Swagger UI at /api-docs. The document is built from the routes themselves (paths, @Param / @Query / @Header / @Cookie / @Body, TypeScript parameter and return types), so it stays in step with the code; @Api* decorators add what types cannot say. Example: examples/openapi-app.
import { Body, Controller, Get, Param, Post, Query } from '@celljs/mvc/lib/node';
import { ApiOperation, ApiProperty, ApiPropertyOptional, ApiResponse, ApiTags } from '@celljs/openapi';
import { IsInt, IsOptional, IsString, Min } from 'class-validator';
export class CreatePet {
@IsString() // class-validator decorators become the schema: type, required, enum, length, range...
@ApiProperty({ example: 'Tom' }) // ...and @ApiProperty adds a description, an example or the type of an array
name: string;
@IsInt() @Min(0) @IsOptional()
age?? number;
}
export class Pet extends CreatePet {
@ApiProperty()
id: number;
}
@Controller('pets')
@ApiTags('Pets')
export class PetController {
@Get()
@ApiResponse({ type: [Pet] }) // async methods and arrays need the type: TypeScript emits only Promise / Array
list(@Query() query: PetQuery): Promise<Pet[]> { ... }
@Get(':id')
@ApiOperation({ summary: 'Find a pet' })
@ApiResponse({ status: 404, description: 'No such pet' })
get(@Param('id') id: number): Pet { ... } // the return type is the 200 response
@Post()
create(@Body() pet: CreatePet): Pet { ... } // the class of the body is the request schema
}What goes into the document:
- Paths: every route of a controller,
:idas{id}; an optional part (/users(/:id)) gives one path with it and one without. Routes with a RegExp path are left out. - Parameters:
@Param('id'),@Query('q'),@Header('x-name'),@Cookie('name')with the type of the parameter;@Query() query: PetQuerygives one query parameter per property of the class. Path parameters missing from the method are added as strings. - Body:
@Body() pet: CreatePetas anapplication/jsonbody; named@Body('name')parameters as the properties of an object. - Responses: the
@ApiResponseones (on the class for all its methods, or on the method); without a 2xx one,200with the return type. The content type follows the view: JSON by default,text/plainfor@Text(),text/htmlfor@Html(). - Schemas: each class becomes an entry of
components.schemas, referred to by$ref. Its properties are the ones with class-validator decorators (read from class-validator's metadata when it is installed, e.g. with @celljs/validation; it is not a dependency of this package) or with@ApiProperty. A property is required unless it has@IsOptional()or@ApiPropertyOptional(). - Tags:
@ApiTags, otherwise the controller name withoutController.
| Decorator | On | Purpose |
|---|---|---|
@ApiTags(...tags) | class, method | Groups operations |
@ApiOperation({ summary, description, operationId, deprecated }) | method | Describes an operation; operationId defaults to <Class>_<method> |
@ApiResponse({ status, description, type, schema, contentType }) | class, method | A response; repeat it per status; type: [Pet] for a list |
@ApiProperty(options) / @ApiPropertyOptional(options) | property | A property of a request or response class; type, enum (a list or a TypeScript enum), description, example, format and any other schema field |
@ApiParam / @ApiQuery / @ApiHeader({ name, ... }) | method | Describes a parameter, or documents one the method reads from the request itself |
@ApiSecurity(name, scopes?) / @ApiBearerAuth() | class, method | Requires a security scheme; bearer is declared for @ApiBearerAuth() when components.securitySchemes has none |
@ApiExclude() | class, method | Leaves it out of the document |
Configuration
openapi:
enabled: true # false: serve neither the document nor the UI
url: /openapi.json # under server.path
ui:
enabled: true
url: /api-docs
cdn: https://cdn.jsdelivr.net/npm/swagger-ui-dist@5 # where the page loads Swagger UI from
document: # merged into the generated document
openapi: 3.0.3
info:
title: Pet Store
version: 1.0.0
servers:
- url: https://api.example.com
components:
securitySchemes:
apiKey: { type: apiKey, in: header, name: X-API-Key }The document and the page answer GET requests no controller answered, so a route of the application with the same path wins and other requests pay nothing. They are public unless @celljs/security protects them; to hide them in production, set enabled: false in the production config (cell.production.yml).
To write the document to a file without starting a server (for API clients, code generators or a review), fetch it from createTestApp() of @celljs/cli, as scripts/export-openapi.ts of the example does.
OpenApiDocumentProvider (@celljs/openapi/lib/node) gives the document in code; rebind it to change how it is built.