Outline
import { NestiaSwaggerComposer } from "@nestia/sdk";
import { INestApplication } from "@nestjs/common";
import { NestFactory } from "@nestjs/core";
import { SwaggerModule } from "@nestjs/swagger";
const main = async (): Promise<void> => {
const app: INestApplication = await NestFactory.create(ApplicationModule);
const document = await NestiaSwaggerComposer.document(app, {});
SwaggerModule.setup("api", app, document as any);
await app.listen(3_000);
};
main().catch(console.error);
If you want to build Swagger Document in runtime, compose it like above.
By the way, it needs plugin configuration on the tsconfig.json
file.
# BUILD SWAGGER DOCUUMET ONLY
npx nestia swagger
# BUILD SWAGGER/SDK/E2E AT THE SAME TIME
npx nestia all
Otherwise you want to generate a swagger file by CLI (Command Line Interface), configure nestia.config.ts
file and run the npx nestia swagger
command. Then, @nestia/sdk
will analyze your NestJS backend server code, and generate swagger.json
file.
When you want to build not only Swagger Document file, but also SDK (Software Development Kit) library and automated E2E (End-to-End) test functions at the same time, run npx nestia all
command instead.
Runtime Composition
Bootstrap
import { NestiaSwaggerComposer } from "@nestia/sdk";
import { INestApplication } from "@nestjs/common";
import { NestFactory } from "@nestjs/core";
import { SwaggerModule } from "@nestjs/swagger";
const main = async (): Promise<void> => {
const app: INestApplication = await NestFactory.create(ApplicationModule);
const document = await NestiaSwaggerComposer.document(app, {
openapi: "3.1",
servers: [
{
url: "http://localhost:3000",
description: "Localhost"
}
]
});
SwaggerModule.setup("api", app, document as any);
await app.listen(3_000);
};
main().catch(console.error);
Call NestiaSwaggerComposer.document()
function.
To compose Swagger Document in runtime and serve it through the Swagger UI in the NestJS application, import NestiaSwaggerComposer
module from @nestia/sdk
and call the NestiaSwaggerComposer.document()
function with the INestApplication
instance.
When you call the NestiaSwaggerComposer.document()
function, @nestia/sdk
will analyze your NestJS backed server code in the compilation level rapidly, and generate the Swagger Document object in the runtime. If you want to specify the OpenAPI version or server address(es), configure the second parameter of the NestiaSwaggerComposer.document()
function.
After that, deliver the generated Swagger Document object to the SwaggerModule.setup()
function of the @nestjs/swagger
module. Then, the Swagger UI would be served in the http://localhost:3000/api
address, and the swagger.json
file would be placed on the http://localhost:3000/api-json location. Note that, as the NestJS core team and SwaggerModule.setup()
function have not defined the exact OpenAPI document types, you’ve to cast the document
object to any
type.
Plugin Configuration
{
"compilerOptions": {
"strict": true,
"plugins": [
{ "transform": "typia/lib/transform" },
{ "transform": "@nestia/core/lib/transform" },
{ "transform": "@nestia/sdk/lib/transform" }, // essential
],
},
}
Configure plugin property of tsconfig.json
file like above.
To activate the runtime swagger composer by NestiaSwaggerComposer.document()
function, you have to configure the plugin on the tsconfig.json
file. If you’ve not done it yet, open the tsconfig.json
file and configure like above. If you don’t do that, the runtime swagger composer cannot find any API operations, so that the Swagger Document would be empty.
npm install --save-dev nestia@latest
npx nestia setup --runtime true
If you feel the tsconfig.json
file configuration annoying, you can do it with CLI command of nestia
. Just run the npx nestia setup --runtime true
command, then the tsconfig.json
file would be automatically configured with the plugin property.
If you do not specify the --runtime true
argument, so that run only the npx nestia setup
command, the CLI prompt will ask you whether to configure the runtime argument or not. Answer as yes
to configure it.
File Generation
Application Module
import { INestiaConfig } from "@nestia/sdk";
import { NestFactory } from "@nestjs/core";
// import { FastifyAdapter } from "@nestjs/platform-fastify";
import { YourModule } from "./src/YourModule";
const NESTIA_CONFIG: INestiaConfig = {
input: async () => {
const app = await NestFactory.create(YourModule);
// const app = await NestFactory.create(YourModule, new FastifyAdapter());
// app.setGlobalPrefix("api");
// app.enableVersioning({
// type: VersioningType.URI,
// prefix: "v",
// })
return app;
},
swagger: {
openapi: "3.1",
output: "dist/swagger.json",
security: {
bearer: {
type: "apiKey",
name: "Authorization",
in: "header",
},
},
servers: [
{
url: "http://localhost:3000",
description: "Local Server",
},
],
beautify: true,
},
};
export default NESTIA_CONFIG;
Make nestia.config.ts
file and run npx nestia swagger
command.
At first, create nestia.config.ts
file through npx nestia init
command. It would be placed on the top level directory of your NestJS backend project. For reference, tsconfig.json
file also must be placed in the top level directory, too. After creation, configure the nestia.config.ts
file referencing above example code and type definition.
At least, you’ve to configure those two properties:
input
: Accessor of controller classesswagger.output
: Path ofswagger.json
file
When you’ve completed above configuration, just run npx nestia swagger
command. Then, swagger.json
file would be newly generated, and placed into the $config.swagger.output
directory following your nestia.config.ts
configuration.
Multiple Files Generation
import { INestiaConfig } from "@nestia/sdk";
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./src/modules/AppModule";
import { BbsModule } from "./src/modules/BbsModule";
import { CommonModule } from "./src/modules/CommonModule";
export const NESTIA_CONFIGURATIONS: INestiaConfig[] = [
{
input: () => NestFactory.create(AppModule),
swagger: {
output: "swagger.json",
security: {
bearer: {
type: "apiKey",
},
},
},
},
{
input: () => NestFactory.create(BbsModule),
swagger: {
output: "bbs.swagger.json",
security: {
bearer: {
type: "apiKey",
},
},
},
},
{
input: () => NestFactory.create(CommonModule),
swagger: {
output: "common.swagger.json",
security: {
bearer: {
type: "apiKey",
},
},
},
},
];
export default NESTIA_CONFIGURATIONS;
You can build multiple Swagger Document files.
Just configure an array of INestiaConfig
instances like above example code.
Then, @nestia/sdk
will generate multiple Swagger Document files following the configurations.
Additional Properties
Additionally, you can configure the swagger
property in the nestia.config.ts
file.
swagger.openapi
: OpenAPI version specification."2.0"
"3.0"
"3.1"
(default)
swagger.beautify
: Whether to beautify JSON content or not.swagger.info
: API information. If not configured,package.json
content be utilized instead.swagger.servers
: List of server addresses.swagger.security
: Security schemes.swagger.tags
: List of tag names with description.swagger.decompose
: Whether to decompose query DTO as individual parameters.swagger.operationId
: Operation ID genereator.
For reference, if you do not configure swagger.info
property or omit some members of the information instance, @nestia/sdk
will utilize your package.json
file content instead. For example, if you omit the swagger.info.version
, your package.json
file’s version
property would be written instead.
Also, whether you configure swagger.openapi
version or not, the newly generated swagger.file
starts from the OpenAPI v3.1 specification emended by @samchon/openapi
. If your target OpenAPI version is lower than v3.1, @nestia/sdk
just downgrades the newly generated OpenAPI v3.1 content by calling OpenApi.downgrade()
function.
See detailed options:
export namespace INestiaConfig {
/**
* Building `swagger.json` is also possible.
*/
export interface ISwaggerConfig {
/**
* Output path of the `swagger.json`.
*
* If you've configured only directory, the file name would be the `swagger.json`.
* Otherwise you've configured the full path with file name and extension, the
* `swagger.json` file would be renamed to it.
*/
output: string;
/**
* OpenAPI version.
*
* If you configure this property to be `2.0` or `3.0`, the newly generated
* `swagger.json` file would follow the specified OpenAPI version. The newly
* generated `swagger.json` file would be downgraded from the OpenAPI v3.1
* specification by {@link OpenApi.downgrade} method.
*
* @default 3.1
*/
openapi?: "2.0" | "3.0" | "3.1";
/**
* Whether to beautify JSON content or not.
*
* If you configure this property to be `true`, the `swagger.json` file would
* be beautified with indentation (2 spaces) and line breaks. If you configure
* numeric value instead, the indentation would be specified by the number.
*
* @default false
*/
beautify?: boolean | number;
/**
* API information.
*
* If omitted, `package.json` content would be used instead.
*/
info?: Partial<OpenApi.IDocument.IInfo>;
/**
* List of server addresses.
*/
servers?: OpenApi.IServer[];
/**
* Security schemes.
*
* When generating `swagger.json` file through `nestia`, if your controllers or
* theirs methods have a security key which is not enrolled in here property,
* it would be an error.
*/
security?: Record<string, OpenApi.ISecurityScheme>;
/**
* List of tag names with description.
*
* It is possible to omit this property or skip some tag name even if
* the tag name is used in the API routes. In that case, the tag name
* would be used without description.
*
* Of course, if you've written a comment like `@tag {name} {descrition}`,
* you can entirely replace this property specification.
*/
tags?: OpenApi.IDocument.ITag[];
/**
* Decompose query DTO.
*
* If you configure this property to be `true`, the query DTO would be decomposed
* into individual query parameters per each property. Otherwise you set it to be
* `false`, the query DTO would be one object type which contains all of query
* parameters.
*
* @default false
*/
decompose?: boolean;
/**
* Operation ID generator.
*
* @param props Properties of the API endpoint.
* @returns Operation ID.
*/
operationId?(props: {
class: string;
function: string;
method: "HEAD" | "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
path: string;
}): string;
}
}
CLI Arguments
npx nestia swagger
npx nestia swagger --config nestia2.config.ts
npx nestia swagger --project tsconfig2.json
npx nestia swagger --config nestia3.config.ts --project tsconfig3.tsconfig.json
If you have a special configuration file that its file name is not nestia.config.ts
or the configuration file is not placed on the root directory of the project, you can specify it with --config
option like npx nestia swagger --config another.config.ts
.
Also, if you have a special tsconfig.json
file or the project file is not located in the root directory of the project, you can specify it with --project
argument like npx nestia swagger --project another.tsconfig.json
, too.
Special Tags
NestJS Decorators
Swagger generator fo @nestia/sdk
supports some NestJS decorators. Here is the list of them.
@nestjs/common
@Header()
@HttpCode()
@Version()
@nestjs/swagger
@ApiBasicAuth()
@ApiBearerAuth()
@ApiOAuth2()
@ApiSecurity()
@ApiTags()
@ApiExtension()
@nestia/core
@TypedException()
@SwaggerExample.Parameter()
@SwaggerExample.Response()
Controller Methods
Swagger generator @nestia/sdk
supports three type of comment tags for controller methods:
- Hiding
@deprecated
: mark asdeprecated
@internal
: hide, never be shown@hidden
: hide, never be shown@ignore
: hide, never be shown, even in the SDK side
- Labeling
@summary
: short description of endpoint@tag {name} {description?}
: grouppig with description@operationId {value}
: manual operation ID
- Security
@security {key}
: security scheme key@security {key} {...scopes}
: +scopes for OAuth2 type
At first, @internal
, @hidden
and @ignore
tags are used to hide the controller method from the Swagger Documents. When you use one of them, the controller method would not be written in the swagger.json
file. Otherwise, the @deprecated
tag is used to mark the controller method as deprecated. When you use it, Swagger Editor will show the deprecated message about the route method like below.
Also, the @summary
tag is used to write short description of the endpoint. By the way, the @summary
tag can be replaced by writing top sentence ends with .
symbol. The other one, @tag {name} {description?}
tag is used for only groupping. If you fill the description
part, it would be shown in the Swagger-UI.
The last one, @security
is a tag for security scheme. It specifies target security scheme by writing its key like @security {key}
. If target scheme type is OAuth2, and it has configured scopes, you can specify the scopes by writing scopes at the backward like @security {key} read write
.
For reference, target security schemes must be configured in the nestia.config.ts
file. If you use @security
tag that is not configured in the nestia.config.ts
file, it would be an error. Also, if you’ve configured @nestia/swagger
security decorator like @ApiSecurity
, @nestia/sdk
also can recognize it too.
import { TypedBody, TypedParam, TypedRoute } from "@nestia/core";
import { Controller } from "@nestjs/common";
import { ApiSecurity } from "@nestjs/swagger";
import typia, { tags } from "typia";
import { IBbsArticle } from "@api/lib/structures/IBbsArticle";
@Controller("bbs/articles/:section")
export class BbsArticlesController {
/**
* Would be shown without any mark.
*
* @param section Section code
* @param input Content to store
* @returns Newly archived article
*
* @tag public Some description describing public group...
* @summary Public API
* @security bearer
* @security oauth2 read write
*/
@TypedRoute.Post()
public async store(
@TypedParam("section") section: string,
@TypedBody() input: IBbsArticle.IStore,
): Promise<IBbsArticle> {
return {
...typia.random<IBbsArticle>(),
...input,
section,
};
}
/**
* Deprecated API.
*
* Would be marked as "deprecated".
*
* For reference, top sentence "Deprecated API." can replace the `@summary` tag.
*
* @param section Section code
* @param id Target article ID
* @param input Content to update
* @returns Updated content
*
* @deprecated
* @operationId updateArticle
* @security basic
* @security bearer
*/
@TypedRoute.Put(":id")
public async update(
@TypedParam("section") section: string,
@TypedParam("id") id: string & tags.Format<"uuid">,
@TypedBody() input: IBbsArticle.IStore,
): Promise<IBbsArticle> {
return {
...typia.random<IBbsArticle>(),
...input,
id,
section,
};
}
/**
* Would not be shown.
*
* @internal
*/
@ApiSecurity("custom") // LEGACY DECORATOR ALSO CAN BE USED
@TypedRoute.Delete(":id")
public erase(
@TypedParam("section") section: string,
@TypedParam("id") id: string & tags.Format<"uuid">,
): void {
section;
id;
}
}
DTO Properties
https://swagger.io/docs/specification/data-models/data-types/
You can utilize comments and tags to construct special fields of JSON schema.
If you write any comment on a property, it would fill the IJsonSchema.description
value. When you utilize Special tags of typia
, they would be placed into the proper properties of IJsonSchema
. Below is the list of supported type and comment tags in the @nestia/sdk
.
Also, such type and comment tags of DTO properties can be used to enhance validation logic of @nestia/core
library. Especially, @TypedBody.${method}()
, @TypedParam()
, @TypedRoute()
and @TypedQuery()
functions can use those tags for additional validation.
Let’s see how those type and comment tags work with example code.
- number
number & Type<{keyword}>
int32
uint32
uint64
int64
float
double
number & Minimum<{number}>
number & Maximum<{number}>
number & ExclusiveMaximum<{number}>
number & ExclusiveMinimum<{number}>
number & MultipleOf<{number}>
- bigint
bigint & Type<{keyword}>
int64
uint64
bigint & Minimum<{bigint}>
bigint & Maximum<{bigint}>
bigint & ExclusiveMaximum<{bigint}>
bigint & ExclusiveMinimum<{bigint}>
bigint & MultipleOf<{bigint}>
- string
string & MinLength<{number}>
string & MaxLength<{number}>
string & Pattern<{regex}>
string & Format<{keyword}>
email
uuid
ipv4
ipv6
url
date
: YYYY-MM-DDdate-time
:Date.toISOString()
export interface SpecialTag {
/**
* Deprecated tags are just used for marking.
*
* @title Unsigned integer
* @deprecated
*/
type: number & tags.Type<"uint32">;
/**
* Internal tagged property never be shown in JSON schema.
*
* It even doesn't be shown in other `typia` functions like `assert<T>()`.
*
* @internal
*/
internal: number[];
/**
* Hidden tagged property never be shown in JSON schema.
*
* However, it would be shown in other `typia` functions like `stringify<T>()`.
*
* @hidden
*/
hidden: boolean;
/**
* You can limit the range of number.
*
* Also, you can configure `default` property by comment tag.
*
* @default 30
*/
number?: number & tags.ExclusiveMinimum<19> & tags.Maximum<100>;
/**
* You can limit the length of string.
*/
string: string & tags.MinLength<3>;
/**
* You can limit the pattern of string.
*/
pattern: string & tags.Pattern<"^[a-z]+$">;
/**
* You can limit the format of string.
*/
format: null | (string & tags.Format<"date-time">);
/**
* You also can perform union type in type tags.
*/
ip: string & (tags.Format<"ipv4"> | tags.Format<"ipv6">);
/**
* In the Array case, only type tags can limit elements' type.
*/
array: Array<string & tags.Format<"uuid">> &
tags.MinItems<3> &
tags.MaxItems<100>;
}
Customization
Typia > JSON schema > Customization
If what you want is not just filling special properties of JSON schema spec, but to adding custom properties into the JSON schema definition, you can accomlish it with typia
feature. Define a type based on typia.tags.TagBase
or typia.tags.JsonSchemaPlugin
, and specify the schema
property type as you want.
For reference, the custom property must be started with x- prefix. It’s a rule of JSON schema.
import typia, { tags } from "typia";
type Monetary<Value extends string> = tags.TagBase<{
target: "number";
kind: "monetary";
value: Value;
schema: {
"x-monetary": Value;
};
}>;
type Placeholder<Value extends string> = tags.JsonSchemaPlugin<{
"x-placeholder": Value;
}>;
interface IAccount {
code: string & Placeholder<"Write you account code please">;
balance: number & Monetary<"dollar">;
};
typia.json.application<[IAccount]>();
Otherwise you wanna customize the swagger data, utilize the @SwaggerCustomizer()
decorator.
As you can see from the below example code, callback function defined in the @SwaggerCustomizer()
decorator is changing the swagger data, because it is called when the npx nestia swagger
command being executed. Furthermore, it is possible to add plugin property starting with x-
characters.
Also, this @SwaggerCustomizer()
decorator is especially useful when defining an authentication decorator. Developing the authentication logic in the decorator function, apply the @SwaggerCustomizer()
with OpenApi.IOperation.security
property. Then whenever the decorator function is called, the security scheme would be added into the swagger data.
import { ExecutionContext, createParamDecorator } from "@nestjs/common";
import { Singleton } from "tstl";
import { ShoppingSellerProvider } from "../providers/shoppings/actors/ShoppingSellerProvider";
import { SwaggerCustomizer } from "@nestia/core";
export const ShoppingSellerAuth =
(): ParameterDecorator =>
(
target: Object,
propertyKey: string | symbol | undefined,
parameterIndex: number
): void => {
SwaggerCustomizer((props) => {
props.route.security ??= [];
props.route.security.push({
bearer: [],
});
})(target, propertyKey as string, undefined!);
singleton.get()(target, propertyKey, parameterIndex);
};
const singleton = new Singleton(() =>
createParamDecorator(async (_0: any, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return ShoppingSellerProvider.authorize(request);
})()
);
Distribution
You can choose two options for swagger.json
file distribution.
The 1st is publishing the swagger.json
file in a public repo, and showing it through @nestia/editor
like below:
The 2nd way is to hosting the swagger.json
file in the NestJS backend server.
Read below example code, and follow it on yours:
import { NestiaSwaggerComposer } from "@nestia/sdk";
import { INestApplication } from "@nestjs/common";
import { NestFactory } from "@nestjs/core";
import { SwaggerModule } from "@nestjs/swagger";
const main = async (): Promise<void> => {
const app: INestApplication = await NestFactory.create(ApplicationModule);
const document = await NestiaSwaggerComposer.document(app, {});
SwaggerModule.setup("api", app, document as any);
await app.listen(3_000);
};
main().catch(console.error);