Opérations
En termes d’OpenAPI, les chemins sont des points de terminaison (ressources), tels que /users
ou /reports/summary
, que votre API expose, et les opérations sont les méthodes HTTP utilisées pour manipuler ces chemins, telles que GET
, POST
ou DELETE
.
Tags
Pour attacher un contrôleur à un tag spécifique, utilisez le @ApiTags(...tags)
décorateur.
@ApiTags('cats')@Controller('cats')export class CatsController {}
En-têtes
Pour définir des en-têtes personnalisés attendus dans la requête, utilisez @ApiHeader()
.
@ApiHeader({ name: 'X-MyHeader', description: 'En-tête personnalisé',})@Controller('cats')export class CatsController {}
Réponses
Pour définir une réponse HTTP personnalisée, utilisez le @ApiResponse()
décorateur.
@Post()@ApiResponse({ status: 201, description: 'Le dossier a été créé avec succès.' })@ApiResponse({ status: 403, description: 'Interdit.' })async create(@Body() createCatDto: CreateCatDto) { return this.catsService.create(createCatDto);}
Nest fournit un ensemble de décorateurs de réponse API abrégés qui héritent du décorateur @ApiResponse
:
@ApiOkResponse()
@ApiCreatedResponse()
@ApiAcceptedResponse()
@ApiNoContentResponse()
@ApiMovedPermanentlyResponse()
@ApiFoundResponse()
@ApiBadRequestResponse()
@ApiUnauthorizedResponse()
@ApiNotFoundResponse()
@ApiForbiddenResponse()
@ApiMethodNotAllowedResponse()
@ApiNotAcceptableResponse()
@ApiRequestTimeoutResponse()
@ApiConflictResponse()
@ApiPreconditionFailedResponse()
@ApiTooManyRequestsResponse()
@ApiGoneResponse()
@ApiPayloadTooLargeResponse()
@ApiUnsupportedMediaTypeResponse()
@ApiUnprocessableEntityResponse()
@ApiInternalServerErrorResponse()
@ApiNotImplementedResponse()
@ApiBadGatewayResponse()
@ApiServiceUnavailableResponse()
@ApiGatewayTimeoutResponse()
@ApiDefaultResponse()
@Post()@ApiCreatedResponse({ description: 'Le dossier a été créé avec succès.' })@ApiForbiddenResponse({ description: 'Interdit.' })async create(@Body() createCatDto: CreateCatDto) { return this.catsService.create(createCatDto);}
Pour spécifier un modèle de retour pour une requête, nous devons créer une classe et annoter toutes les propriétés avec le @ApiProperty()
décorateur.
export class Cat { @ApiProperty() id: number;
@ApiProperty() name: string;
@ApiProperty() age: number;
@ApiProperty() breed: string;}
Le modèle Cat
peut ensuite être utilisé en combinaison avec la propriété type
du décorateur de réponse.
@ApiTags('cats')@Controller('cats')export class CatsController { @Post() @ApiCreatedResponse({ description: 'Le dossier a été créé avec succès.', type: Cat, }) async create(@Body() createCatDto: CreateCatDto): Promise<Cat> { return this.catsService.create(createCatDto); }}
Ouvrons le navigateur et vérifions le modèle Cat
généré.
Téléchargement de Fichiers
Vous pouvez activer le téléchargement de fichiers pour une méthode spécifique avec le décorateur @ApiBody
associé à @ApiConsumes()
. Voici un exemple complet utilisant la technique de Téléchargement de Fichiers.
@UseInterceptors(FileInterceptor('file'))@ApiConsumes('multipart/form-data')@ApiBody({ description: 'Liste de chats', type: FileUploadDto,})uploadFile(@UploadedFile() file) {}
Où FileUploadDto
est défini comme suit :
class FileUploadDto { @ApiProperty({ type: 'string', format: 'binary' }) file: any;}
Pour gérer le téléchargement de plusieurs fichiers, vous pouvez définir FilesUploadDto
comme suit :
class FilesUploadDto { @ApiProperty({ type: 'array', items: { type: 'string', format: 'binary' } }) files: any[];}
Extensions
Pour ajouter une extension à une requête, utilisez le décorateur @ApiExtension()
. Le nom de l’extension doit être préfixé par x-
.
@ApiExtension('x-foo', { hello: 'world' })
Avancé : Generic ApiResponse
Avec la possibilité de fournir des Définitions Brutes, nous pouvons définir un schéma générique pour Swagger UI.
Voici un exemple de DTO :
export class PaginatedDto<TData> { @ApiProperty() total: number;
@ApiProperty() limit: number;
@ApiProperty() offset: number;
results: TData[];}
Nous évitons de décorer results
car nous fournirons une définition brute pour cela plus tard. Maintenant, définissons un autre DTO et nommons-le CatDto
comme suit :
export class CatDto { @ApiProperty() name: string;
@ApiProperty() age: number;
@ApiProperty() breed: string;}
Avec cela en place, nous pouvons définir une réponse PaginatedDto<CatDto>
comme suit :
@ApiOkResponse({ schema: { allOf: [ { $ref: getSchemaPath(PaginatedDto) }, { properties: { results: { type: 'array', items: { $ref: getSchemaPath(CatDto) } } } } ] }})async findAll(): Promise<PaginatedDto<CatDto>> {}
Dans cet exemple, nous spécifions que la réponse aura le schéma PaginatedDto
et que la propriété results
sera de type Array<CatDto>
.
Enfin, puisque PaginatedDto
n’est pas directement référencé par aucun contrôleur, le SwaggerModule
ne sera pas en mesure de générer une définition de modèle correspondante pour le moment. Dans ce cas, nous devons l’ajouter comme un Modèle Supplémentaire. Par exemple, nous pouvons utiliser le décorateur @ApiExtraModels()
au niveau du contrôleur :
@Controller('cats')@ApiExtraModels(PaginatedDto)export class CatsController {}
Si vous exécutez Swagger maintenant, le swagger.json
généré pour ce point de terminaison spécifique devrait avoir la réponse définie.
{ "responses": { "200": { "description": "", "content": { "application/json": { "schema": { "allOf": [ { "$ref": "#/components/schemas/PaginatedDto" }, { "properties": { "results": { "$ref": "#/components/schemas/CatDto" } } } ] } } } } }}
Pour le rendre réutilisable, nous pouvons créer un décorateur personnalisé pour PaginatedDto
:
export const ApiPaginatedResponse = <TModel extends Type<any>>(model: TModel) => { return applyDecorators( ApiExtraModels(PaginatedDto, model), ApiOkResponse({ schema: { allOf: [ { $ref: getSchemaPath(PaginatedDto) }, { properties: { results: { type: 'array', items: { $ref: getSchemaPath(model) } } } } ] } }) );}
Avec cela en place, nous pouvons utiliser le décorateur personnalisé @ApiPaginatedResponse()
sur notre point de terminaison :
@ApiPaginatedResponse(CatDto)async findAll(): Promise<PaginatedDto<CatDto>> {}
Pour les outils de génération de clients, cette approche pose une ambiguïté quant à la façon dont la réponse PaginatedResponse<TModel>
est générée pour le client. Le code suivant est un exemple de résultat d’un générateur de client pour le point de terminaison ci-dessus.
findAll(): Observable<{ total: number; limit: number; offset: number; results: CatDto[] }> {}
Comme vous pouvez le voir, le Type de Retour ici est ambigu. Pour contourner ce problème, vous pouvez ajouter une propriété title
au schema
pour ApiPaginatedResponse
:
export const ApiPaginatedResponse = <TModel extends Type<any>>(model: TModel) => { return applyDecorators( ApiOkResponse({ schema: { title: `PaginatedResponseOf${model.name}`, allOf: [ // ... ], }, }) );}
Maintenant, le résultat de l’outil de génération de client deviendra :
findAll(): Observable<PaginatedResponseOfCatDto> {}