From d608649db54b6a880c09cde18bcda537f43397a4 Mon Sep 17 00:00:00 2001
From: Chau Tran Bootstrap
The SwaggerModule
automatically reflects all of your endpoints.
In the background, it's making use of swagger-ui-express
and creates a live documentation.
+ SwaggerModule
also provides a way to support multiple Swagger Specifications. In other words, you can serve different documentations with different SwaggerUI
on different endpoints.
+
+ In order to allow SwaggerModule
to support multi-specs, your NestJS
application must be written with Modularization approach.
+ createDocument()
method takes in a 3rd argument: extraOptions which is an object where a property include
expects an Array of Modules.
+
+ You can setup Multiple Specifications support as shown below: +
+{{ secondaryBootstrapFile }}
+ + Now you can start your server with the following command: +
+
+$ npm run start
+
+ Navigate to http://localhost:3000/api/cats
to see SwaggerUI for your Cats:
+
+ While http://localhost:3000/api/docs
will expose a SwaggerUI for your Dogs:
+
+ Notice You have to construct a SwaggerOptions withDocumentBuilder
, + runcreateDocument()
against newly constructedoptions
then immediately "serve" it withsetup()
+ before you can start working on a second SwaggerOptions for a second Swagger Specification. This specific order is to + prevent Swagger Configurations being overriden by different Options. +
During the examination of the defined controllers, the `@6tP=)x<7E@oXVJyV%YD q!bICp(}}
zm!4s6t=I_#EN<5d>cx>&bQ0WU;H?Kea6mcs^)Ma(A#8SwaggerModule
is looking for all used @Body()
, @Query()
, and @Param()
decorators in the route handlers.
diff --git a/src/app/homepage/pages/recipes/swagger/swagger.component.ts b/src/app/homepage/pages/recipes/swagger/swagger.component.ts
index 8024b6e51c..3f7d343453 100644
--- a/src/app/homepage/pages/recipes/swagger/swagger.component.ts
+++ b/src/app/homepage/pages/recipes/swagger/swagger.component.ts
@@ -1,14 +1,14 @@
-import { Component, ChangeDetectionStrategy } from '@angular/core';
+import { ChangeDetectionStrategy, Component } from '@angular/core';
import { BasePageComponent } from '../../page/page.component';
@Component({
- selector: 'app-swagger',
- templateUrl: './swagger.component.html',
- changeDetection: ChangeDetectionStrategy.OnPush
-})
+ selector: 'app-swagger',
+ templateUrl: './swagger.component.html',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ })
export class SwaggerComponent extends BasePageComponent {
- get bootstrapFile() {
- return `
+ get bootstrapFile() {
+ return `
import { NestFactory } from '@nestjs/core';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { ApplicationModule } from './app.module';
@@ -28,18 +28,62 @@ async function bootstrap() {
await app.listen(3001);
}
bootstrap();`;
- }
+ }
- get postHandler() {
- return `
+ get secondaryBootstrapFile() {
+ return `
+import { NestFactory } from '@nestjs/core';
+import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
+import { ApplicationModule } from './app.module';
+
+// imports CatsModule and DogsModule;
+
+async function bootstrap() {
+ const app = await NestFactory.create(ApplicationModule);
+
+ /**
+ * createDocument(application, configurationOptions, extraOptions);
+ *
+ * createDocument method takes in an optional 3rd argument "extraOptions"
+ * which is an object with "include" property where you can pass an Array
+ * of Modules that you want to include in that Swagger Specification
+ * E.g: CatsModule and DogsModule will have two separate Swagger Specifications which
+ * will be exposed on two different SwaggerUI with two different endpoints.
+ */
+
+ const options = new DocumentBuilder()
+ .setTitle('Cats example')
+ .setDescription('The cats API description')
+ .setVersion('1.0')
+ .addTag('cats')
+ .build();
+ const catDocument = SwaggerModule.createDocument(app, options, { include: [CatsModule] });
+ SwaggerModule.setup('api/cats', app, catDocument);
+
+ const secondOptions = new DocumentBuilder()
+ .setTitle('Dogs example')
+ .setDescription('The dogs API description')
+ .setVersion('1.0')
+ .addTag('dogs')
+ .build();
+ const dogDocument = SwaggerModule.createDocument(app, secondOptions, { include: [DogsModule] });
+ SwaggerModule.setup('api/dogs', app, dogDocument);
+
+ await app.listen(3001);
+}
+bootstrap();`;
+ }
+
+ get postHandler() {
+ return `
@Post()
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}`;
- }
+ }
- get postHandlerJs() {
- return `
+ get postHandlerJs() {
+ return `
@Post()
@Bind(Body())
@ApiImplicitBody({ name: 'CreateCatDto', type: CreateCatDto })
@@ -47,10 +91,10 @@ async create(createCatDto) {
this.catsService.create(createCatDto);
}
`;
- }
+ }
- get createCatDto() {
- return `
+ get createCatDto() {
+ return `
import { ApiModelProperty } from '@nestjs/swagger';
export class CreateCatDto {
@@ -63,10 +107,10 @@ export class CreateCatDto {
@ApiModelProperty()
readonly breed: string;
}`;
- }
+ }
- get createCatDtoJs() {
- return `
+ get createCatDtoJs() {
+ return `
import { ApiModelProperty } from '@nestjs/swagger';
export class CreateCatDto {
@@ -79,10 +123,10 @@ export class CreateCatDto {
@ApiModelProperty({ type: String })
readonly breed;
}`;
- }
+ }
- get apiModelProperty() {
- return `
+ get apiModelProperty() {
+ return `
export declare const ApiModelProperty: (metadata?: {
description?: string;
required?: boolean;
@@ -109,56 +153,56 @@ export declare const ApiModelProperty: (metadata?: {
xml?: any;
example?: any;
}) => PropertyDecorator;`;
- }
+ }
- get apiModelPropertyOptional() {
- return `
+ get apiModelPropertyOptional() {
+ return `
@ApiModelProperty({ required: false })`;
- }
+ }
- get arrayProperty() {
- return `
+ get arrayProperty() {
+ return `
@ApiModelProperty({ type: String, isArray: true })
readonly names: string[];`;
- }
+ }
- get useTags() {
- return `
+ get useTags() {
+ return `
@ApiUseTags('cats')
@Controller('cats')
export class CatsController {}`;
- }
+ }
- get response() {
- return `
+ get response() {
+ return `
@Post()
@ApiResponse({ status: 201, description: 'The record has been successfully created.'})
@ApiResponse({ status: 403, description: 'Forbidden.'})
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}`;
- }
+ }
- get customResponse() {
- return `
+ get customResponse() {
+ return `
@Post()
@ApiCreatedResponse({ description: 'The record has been successfully created.'})
@ApiForbiddenResponse({ description: 'Forbidden.'})
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}`;
- }
+ }
- get bearerAuth() {
- return `
+ get bearerAuth() {
+ return `
@ApiUseTags('cats')
@ApiBearerAuth()
@Controller('cats')
export class CatsController {}`;
- }
+ }
- get apiImplicitQuery() {
- return `
+ get apiImplicitQuery() {
+ return `
export const ApiImplicitQuery = (metadata: {
name: string;
description?: string;
@@ -168,28 +212,28 @@ export const ApiImplicitQuery = (metadata: {
enum?: SwaggerEnumType;
collectionFormat?: 'csv' | 'ssv' | 'tsv' | 'pipes' | 'multi';
}): MethodDecorator`;
- }
+ }
- get enumImplicitQuery() {
- return `
+ get enumImplicitQuery() {
+ return `
@ApiImplicitQuery({ name: 'role', enum: ['Admin', 'Moderator', 'User'] })
async filterByRole(@Query('role') role: UserRole = UserRole.User) {
// role returns: UserRole.Admin, UserRole.Moderator OR UserRole.User
}`;
- }
+ }
- get enumProperty() {
- return `
+ get enumProperty() {
+ return `
@ApiModelProperty({ enum: ['Admin', 'Moderator', 'User']})
role: UserRole;`;
- }
+ }
- get userRoleEnum() {
- return `
+ get userRoleEnum() {
+ return `
export enum UserRole {
Admin = 'Admin',
Moderator = 'Moderator',
User = 'User'
}`;
- }
+ }
}
diff --git a/src/assets/swagger-cats.png b/src/assets/swagger-cats.png
new file mode 100644
index 0000000000000000000000000000000000000000..68e723415ea4417dabc8ee415e25a2c3cd60ba66
GIT binary patch
literal 175642
zcmeFZcTiJX-#?07K?J3$G_fGkq$5=Yqy|u=6I!TJLJuSqQBeVbgH-7q0RibX1gRn&
zq?3f+N$8=4+|4=9d7m?L@5~GL{&jzIm?3PEz4lt`TR-2j5}=``KuLasoQ#Z&Qb|$n
z1sNInCK=gzk&EYncbpuDNn~V~@7c)8YADIdvS~O&Ep6;9$jB4}BBL+73~!}>J;$e{
zsA%nXQSlP;Qg80&jqDfaZr-|dCxY?Xn?PppOGlosUrpq-EwYWI@zHIr$gn26BpNBoccmEe~h
zlc|3DUFx=Uxx-G1iMdE-u|rh%gwe1S5rhrC6eR7Fa=*HGUb{l3czIeV|6TZ*-G^iq
zTncwO&%ExY*t#dUF84>kL*-=7_otqUL^{2HgSLpz9KToz^0H%_en}Sb)viqJ3>lrC
zVY`>w`OUN
zwly=J5#iSjxuixzeM{(_WzqMyB31bt20anwH;e#k
zU`Ut9P9jm%V*e`GKV&Zc|E+?FSv|r3RBTk-<3I#2Tdiv|JlEOF=0d7TFAJVTV95Ni
z=lxu+ZH6?}G&J1iH)IT$T)p#)c70%QFeB-sA!Iw&edt7Dd+~st{I}42M?Kn%|6Fy`
zX6z)ow`*u_Gq$P5e^c;th&rYyro1+V*SV5*Gjs6U0uRy>Up!Q9Dzw*;xifOYsWShZ
z>fY)V%8*8rTE^YO2}Y*-nO#jU8VzFGaGacKN
zkEJf!3cQ{c2Bte{VkN#V_Bdg93k|7^56#Hqt)7AH2moV_q1=C)Rbl$CH#RN%JgHb2
zKP3X5W0OkZ7HB!4Q-prgsK^fN%Dnrq%y-pHlCL3GlOEP?liRYC|GwAx{@(E!sbDEbq8e+#
zWxM-vgcOy!X-1L6-JFWVL+y)oenSP?hRb}Z_q{c|4jiRCQdI|3QA|cexSq}8#w>`y
z1;Qlf&Bt8Kd%C^B1Qa|X3r?xWJ$cF^m{vOz Jw4VE&dUQkpc&s7w<&@Uf9
zM3NC8lPfjbJ#`BdE7Tm?JUAyWUnJ+)8lAVHXx)}yV!=_WQ+){wC#WwaGmSbe5Vh4yk|GH}Gg%XsOC0BM#vXIAC{M~mwF(BWD?J({adPd>M`U=gY;u-ZDqH57c
zd@l`?vd&J-V{WzH=M=kkYdE`})BfX2lE_szZ%e@qWs2x$9w{o