Skip to content

Commit

Permalink
Merge pull request #151 from nartc/multiple-specs-swagger
Browse files Browse the repository at this point in the history
docs: documentations for Multiple Specifications support (swagger)
  • Loading branch information
kamilmysliwiec committed Sep 5, 2018
2 parents e84d835 + 65cab84 commit 4ff1556
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 54 deletions.
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -4,7 +4,7 @@
"license": "MIT",
"scripts": {
"ng": "ng",
"start": "ng serve --sourcemap=false",
"start": "ng serve --source-map=false",
"build": "ng build",
"build:prod": "ng build --prod --aot",
"test": "ng test",
Expand Down
31 changes: 31 additions & 0 deletions src/app/homepage/pages/recipes/swagger/swagger.component.html
Expand Up @@ -41,6 +41,37 @@ <h4>Bootstrap</h4>
The <code>SwaggerModule</code> automatically reflects all of your endpoints.
In the background, it's making use of <code>swagger-ui-express</code> and creates a live documentation.
</p>
<h4>Multiple Swagger Specifications</h4>
<p>
<code>SwaggerModule</code> also provides a way to support multiple Swagger Specifications. In other words, you can serve different documentations with different <code>SwaggerUI</code> on different endpoints.
</p>
<p>
In order to allow <code>SwaggerModule</code> to support <strong>multi-specs</strong>, your <code>NestJS</code> application must be written with <strong>Modularization</strong> approach.
<code>createDocument()</code> method takes in a 3rd argument: <strong>extraOptions</strong> which is an object where a property <code>include</code> expects an <strong>Array of Modules</strong>.
</p>
<p>
You can setup Multiple Specifications support as shown below:
</p>
<pre><code class="language-typescript">{{ secondaryBootstrapFile }}</code></pre>
<p>
Now you can start your server with the following command:
</p>
<pre><code class="language-bash">
$ npm run start</code></pre>
<p>
Navigate to <code>http://localhost:3000/api/cats</code> to see SwaggerUI for your Cats:
</p>
<figure><img src="/assets/swagger-cats.png" /></figure>
<p>
While <code>http://localhost:3000/api/docs</code> will expose a SwaggerUI for your Dogs:
</p>
<figure><img src="/assets/swagger-dogs.png" /></figure>
<blockquote class="warning">
<strong>Notice</strong> You have to construct a <strong>SwaggerOptions</strong> with <code>DocumentBuilder</code>,
run <code>createDocument()</code> against newly constructed <code>options</code> then immediately "serve" it with <code>setup()</code>
before you can start working on a second <strong>SwaggerOptions</strong> for a second Swagger Specification. This specific order is to
prevent <strong>Swagger Configurations</strong> being overriden by different <strong>Options</strong>.
</blockquote>
<h4>Body, query, path parameters</h4>
<p>
During the examination of the defined controllers, the <code>SwaggerModule</code> is looking for all used <code>@Body()</code>, <code>@Query()</code>, and <code>@Param()</code> decorators in the route handlers.
Expand Down
150 changes: 97 additions & 53 deletions 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';
Expand All @@ -28,29 +28,73 @@ 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 })
async create(createCatDto) {
this.catsService.create(createCatDto);
}
`;
}
}

get createCatDto() {
return `
get createCatDto() {
return `
import { ApiModelProperty } from '@nestjs/swagger';
export class CreateCatDto {
Expand All @@ -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 {
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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'
}`;
}
}
}
Binary file added src/assets/swagger-cats.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/swagger-dogs.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 4ff1556

Please sign in to comment.