openapi-tsk
v1.0.6
Published
openapi tool to use with NodeTskeleton template project
Downloads
30
Maintainers
Readme
OpenAPI-TSK 🚀
OpenAPI-tsk tool is one part of the NodeTskeleton
template project to install, to initialize and to interact with it.
NodeTskeleton
is a Clean Architecture
based template project
for NodeJs
using TypeScript
to implement with any web server framework
or even any user interface.
Go to NodeTskeleton
Using OpenAPI-TSK
The API documentation can already be generated automatically through a strategy in the method where the routes are configured using Open API.
You can see the API documentation in NodeTSKeleton project going to the next url once you have setup your local project:
localhost:3003/api/docs
But first, you have to setup the project, so if you want, you can do it very fast executing this command on your computer:
- Run it using NPX and replace
my-awesome-project
for your own project name
npx run-tsk setup project-name=my-awesome-project
For normal and typical nodejs projects go to Normal projects
The API documentation is done in the initializeRoutes method of each controller as shown below:
initializeRoutes(router: IRouter): void {
this.setRouter(router())
.addRoute({
method: HttpMethodEnum.POST,
path: "/v1/users/sign-up",
handlers: [this.singUp],
produces: [
{
applicationStatus: ApplicationStatus.INVALID_INPUT,
httpStatus: HttpStatusEnum.BAD_REQUEST,
},
{
applicationStatus: ApplicationStatus.SUCCESS,
httpStatus: HttpStatusEnum.CREATED,
},
],
});
}
Then once you have added your route, the same method is used to configure properties called model inside produce and apiDoc, and in this one you can have the following ways to configure your data models (Request, Response, Parameters) through the following Descriptor Objects:
// To describe a ResultT type (ResultTDescriber and TypeDescriber helps us to do it)
.addRoute({
method: HttpMethodEnum.POST,
path: "/v1/users/sign-up",
handlers: [this.singUp],
produces: [
{
applicationStatus: ApplicationStatus.INVALID_INPUT,
httpStatus: HttpStatusEnum.BAD_REQUEST,
model: {
contentType: HttpContentTypeEnum.APPLICATION_JSON,
scheme: new ResultDescriber({
type: PropTypeEnum.OBJECT,
props: ResultDescriber.defaultError(),
}),
},
},
{
applicationStatus: ApplicationStatus.SUCCESS,
httpStatus: HttpStatusEnum.CREATED,
model: {
contentType: HttpContentTypeEnum.APPLICATION_JSON,
scheme: new ResultTDescriber<TokenDto>({
name: TokenDto.name,
type: PropTypeEnum.OBJECT,
props: {
data: new TypeDescriber<TokenDto>({
name: TokenDto.name,
type: PropTypeEnum.OBJECT,
// Option one to describe a scheme response type
props: {
token: {
type: PropTypeEnum.STRING,
},
expiresIn: {
type: PropTypeEnum.NUMBER,
},
{ ... }
},
// Option two to describe a scheme response type
props: TypeDescriber.describeProps<TokenDtoType>({
token: PropTypeEnum.STRING,
expiresIn: PropTypeEnum.NUMBER,
owner: TypeDescriber.describeReference<OwnerType>(OwnerDto.name, {
email: PropTypeEnum.STRING,
sessionId: PropTypeEnum.STRING,
}),
}),
}),
...ResultDescriber.default(),
},
}),
},
},
{
applicationStatus: ApplicationStatus.UNAUTHORIZED,
httpStatus: HttpStatusEnum.UNAUTHORIZED,
model: {
contentType: HttpContentTypeEnum.APPLICATION_JSON,
scheme: new ResultDescriber({
type: PropTypeEnum.OBJECT,
props: ResultDescriber.defaultError(),
}),
},
},
],
description: "Self register user",
apiDoc: {
requireAuth: false,
requestBody: {
description: "User data",
contentType: HttpContentTypeEnum.APPLICATION_JSON,
required: true,
scheme: new TypeDescriber<IUserDto>({
name: UserDto.name,
type: PropTypeEnum.OBJECT,
props: TypeDescriber.describeProps<IUserDto>({
maskedUid: PropTypeEnum.STRING,
firstName: PropTypeEnum.STRING,
lastName: PropTypeEnum.STRING,
gender: PropTypeEnum.STRING,
email: PropTypeEnum.STRING,
passwordB64: PropTypeEnum.STRING,
}),
}),
},
},
}),
// Observation about ApiDocs TokenDto class for way two to describe a model as example
// Token classes
export type OwnerType = {
email: string;
sessionId: string;
};
export class OwnerDto implements OwnerType {
email: string;
sessionId: string;
constructor(props: OwnerType) {
this.email = props.email;
this.sessionId = props.sessionId;
}
}
export type TokenDtoType = {
token: string;
expiresIn: number;
owner: OwnerDto;
};
export class TokenDto implements TokenDtoType {
token: string;
expiresIn: number;
owner: OwnerDto;
constructor(props: TokenDtoType) {
this.token = props.token;
this.expiresIn = props.expiresIn;
this.owner = props.owner;
}
}
// To describe a simple Result type (ResultDescriber helps us to do it)
produces: [
{
applicationStatus: ApplicationStatus.INVALID_INPUT,
httpStatus: HttpStatusEnum.BAD_REQUEST,
model: {
contentType: HttpContentTypeEnum.APPLICATION_JSON,
scheme: new ResultDescriber({
type: PropTypeEnum.OBJECT,
props: ResultDescriber.defaultError(),
}),
},
},
],
apiDoc: {
requireAuth: false,
},
// To describe any object (TypeDescriber helps us to do it)
produces: [
{
applicationStatus: ApplicationStatus.SUCCESS,
httpStatus: HttpStatusEnum.SUCCESS,
model: {
contentType: HttpContentTypeEnum.TEXT_PLAIN,
scheme: new TypeDescriber<string>({
name: PropTypeEnum.STRING,
type: PropTypeEnum.PRIMITIVE,
props: {
primitive: PropTypeEnum.STRING,
},
}),
},
},
],
apiDoc: {
requireAuth: false,
},
To get an overall idea, here an example:
initializeRoutes(router: IRouter): void {
this.setRouter(router());
this.addRoute({
method: HttpMethodEnum.POST,
path: "/v1/auth/login",
handlers: [this.login],
produces: [
{
applicationStatus: ApplicationStatus.SUCCESS,
httpStatus: HttpStatusEnum.SUCCESS,
model: {
contentType: HttpContentTypeEnum.APPLICATION_JSON,
scheme: new ResultTDescriber<TokenDto>({
name: TokenDto.name,
type: PropTypeEnum.OBJECT,
props: {
data: new TypeDescriber<TokenDto>({
name: TokenDto.name,
type: PropTypeEnum.OBJECT,
props: TypeDescriber.describeProps<TokenDto>({
token: PropTypeEnum.STRING,
expiresIn: PropTypeEnum.NUMBER,
// This added section is only a demo to show how to use nested objects in the response
owner: TypeDescriber.describeReference<OwnerDto>(OwnerDto.name, {
email: PropTypeEnum.STRING,
sessionId: PropTypeEnum.STRING,
}),
}),
}),
...ResultDescriber.default(),
},
}),
},
},
{
applicationStatus: ApplicationStatus.UNAUTHORIZED,
httpStatus: HttpStatusEnum.UNAUTHORIZED,
model: {
contentType: HttpContentTypeEnum.APPLICATION_JSON,
scheme: new ResultDescriber({
type: PropTypeEnum.OBJECT,
props: ResultDescriber.defaultError(),
}),
},
},
],
description: "Login user",
apiDoc: {
requireAuth: false,
requestBody: {
description: "Credentials for login",
contentType: HttpContentTypeEnum.APPLICATION_JSON,
schema: new TypeDescriber<ICredentials>({
name: "Credentials",
type: PropTypeEnum.OBJECT,
props: TypeDescriber.describeProps<ICredentials>({
email: PropTypeEnum.STRING,
passwordB64: {
type: PropTypeEnum.STRING,
format: PropFormatEnum.BASE64,
},
}),
}),
},
},
});
}
Yes, I know what you're thinking, but no, I thought of that too.
When you have already registered (described) a model, it is not necessary to describe it again, simply use the RefTypeDescriber
class and with this the system will simply map internally the reference to the described model if it exists, otherwise, you will have an error in the generated file when it is going to be rendered.
this.addRoute({
method: HttpMethodEnum.GET,
path: "/v1/users/:userId",
handlers: [this.get],
produces: [
{
applicationStatus: ApplicationStatus.SUCCESS,
httpStatus: HttpStatusEnum.SUCCESS,
model: {
contentType: HttpContentTypeEnum.APPLICATION_JSON,
schema: new RefTypeDescriber({
type: PropTypeEnum.OBJECT,
name: Result.name,
}),
},
},
],
description: "Get a user",
apiDoc: {
requireAuth: true,
parameters: [
TypeDescriber.describeUrlParam({
name: "userId",
in: ParameterIn.PATH,
description: "User identifier",
scheme: {
type: PropTypeEnum.STRING,
},
}),
],
},
});
Once you run the application in DEV mode then the system will generate the file corresponding to the configuration you injected in the API.
The file is created in the root of the project with the name openapi.json
and it would look something like this:
{
"openapi": "3.0.3",
"info": {
"title": "NodeTSkeleton API",
"version": "1.0.0",
"description": "Api documentation for NodeTSkeleton project",
"contact": {
"name": "TSK Support",
"url": "https://github.com/harvic3/nodetskeleton",
"email": "[email protected]"
},
"license": {
"name": "BSD 3-Clause"
}
},
"servers": [
{
"url": "http://localhost:3003/api",
"description": "Local server"
}
],
"paths": {
"/v1/auth/logout": {
"delete": {
"description": "Logout user",
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ResultTClosedSession"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Result"
}
}
}
}
},
"security": [
{
"http": []
}
]
}
},
"/v1/auth/login": {
"post": {
"description": "Login user",
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ResultTTokenDto"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Result"
}
}
}
}
},
"requestBody": {
"description": "Credentials for login",
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Credentials"
}
}
}
}
}
},
"/status": {
"get": {
"description": "API status endpoint",
"responses": {
"200": {
"description": "Success",
"content": {
"text/plain": {
"schema": {
"type": "string"
}
}
}
}
}
}
},
"/v1/users/sign-up": {
"post": {
"description": "Self register user",
"responses": {
"201": {
"description": "Created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UserDto"
}
}
}
},
"400": {
"description": "Bad Request",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Result"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Result"
}
}
}
}
},
"requestBody": {
"description": "User data",
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UserDto"
}
}
}
}
}
},
"/v1/users/{userId}": {
"get": {
"description": "Get user",
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ResultTUserDto"
}
}
}
},
"400": {
"description": "Bad Request",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Result"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Result"
}
}
}
}
},
"parameters": [
{
"name": "userId",
"in": "path",
"description": "User identifier",
"required": true,
"allowEmptyValue": false,
"deprecated": false,
"schema": {
"type": "string"
}
}
]
}
}
},
"components": {
"schemas": {
"Object": {
"type": "object",
"properties": {
"closed": {
"type": "boolean",
"nullable": false
}
}
},
"ResultTClosedSession": {
"type": "object",
"properties": {
"message": {
"type": "string"
},
"statusCode": {
"type": "string"
},
"error": {
"type": "string"
},
"success": {
"type": "boolean"
},
"data": {
"$ref": "#/components/schemas/Object"
}
}
},
"Result": {
"type": "object",
"properties": {
"message": {
"type": "string",
"nullable": true
},
"statusCode": {
"type": "string"
},
"error": {
"type": "string"
},
"success": {
"type": "boolean"
}
}
},
"TokenDto": {
"type": "object",
"properties": {
"token": {
"type": "string",
"nullable": false
},
"expiresIn": {
"type": "number",
"nullable": false
}
}
},
"ResultTTokenDto": {
"type": "object",
"properties": {
"message": {
"type": "string"
},
"statusCode": {
"type": "string"
},
"error": {
"type": "string"
},
"success": {
"type": "boolean"
},
"data": {
"$ref": "#/components/schemas/TokenDto"
}
}
},
"Credentials": {
"type": "object",
"properties": {
"email": {
"type": "string",
"nullable": false
},
"passwordB64": {
"type": "string",
"nullable": false,
"format": "base64"
}
}
},
"UserDto": {
"type": "object",
"properties": {
"maskedUid": {
"type": "string",
"nullable": false
},
"firstName": {
"type": "string",
"nullable": false
},
"lastName": {
"type": "string",
"nullable": false
},
"email": {
"type": "string",
"nullable": false
},
"gender": {
"type": "string",
"nullable": false
}
}
},
"ResultTUserDto": {
"type": "object",
"properties": {
"message": {
"type": "string"
},
"statusCode": {
"type": "string"
},
"error": {
"type": "string"
},
"success": {
"type": "boolean"
},
"data": {
"$ref": "#/components/schemas/UserDto"
}
}
}
},
"securitySchemes": {
"http": {
"type": "http",
"description": "Bearer token",
"scheme": "bearer",
"bearerFormat": "JWT"
}
}
}
}
You can explore and follow the instructions in the official documentation about NodeTSKeleton Go to NodeTskeleton
NodeJS Typical projects
To use this tool in common NodeJS projects you can make something like the following strategy:
- First you will need to add something like the next code into your config file or similar:
// config/index.ts
export default {
Environments: {
Dev: "development",
// Other environments
},
apiDocsInfo: {
title: "Your-name-project API",
version: "1.0.0",
description: "Api documentation for your-name-project",
contact: {
name: "TSK Support",
url: "https://github.com/your-github-username/your-repo-name",
email: "[email protected]",
},
license: {
name: "BSD 3-Clause",
},
},
}
- Create a docs folder with the
ApiDocGenerator
class
// api/docs/index.ts
import { ApiDocGenerator } from "openapi-tsk";
import config from "../../config";
export const apiDocGenerator = new ApiDocGenerator(process.env.ENV ?? config.Environments.Dev, config.apiDocsInfo);
- Use the
ApiDocGenerator
instance class in your controller routes like following:
// In some controller
// The specs for GET status API route
apiDocGenerator.createRouteDoc({
method: HttpMethodEnum.GET,
path: "/status",
description: "Check if the API is online",
produces: [
{
applicationStatus: "200", httpStatus: HttpStatusEnum.SUCCESS,
model: {
contentType: HttpContentTypeEnum.TEXT_PLAIN,
scheme: new TypeDescriber<string>({
name: PropTypeEnum.STRING,
type: PropTypeEnum.PRIMITIVE,
props: TypeDescriber.describePrimitive(PropTypeEnum.STRING),
}),
},
},
],
});
/*
Don't worry about this routing style.
It's because it was using the "oas3-tools", but you can use the typical style for express like:
app.get("route-path", req, res, next)...
*/
export const statusGET = (req: Request, res: Response, next: NextFunction) => {
res.status(200).json("Api online at " + new Date().toISOString());
};
// The specs for POST user API route
apiDocGenerator.createRouteDoc({
method: HttpMethodEnum.POST,
path: "/v1/users/sign-up",
description: "Self register user",
produces: [
{
applicationStatus: "200",
httpStatus: HttpStatusEnum.CREATED,
model: {
contentType: HttpContentTypeEnum.APPLICATION_JSON,
scheme: new TypeDescriber<Omit<IUserDto, "passwordB64">>({
name: "UserDto",
type: PropTypeEnum.OBJECT,
props: TypeDescriber.describeProps<Omit<IUserDto, "passwordB64">>({
maskedUid: PropTypeEnum.STRING,
firstName: PropTypeEnum.STRING,
lastName: PropTypeEnum.STRING,
gender: PropTypeEnum.STRING,
email: PropTypeEnum.STRING,
}),
}),
},
},
// Add other ones as you need
],
apiDoc: {
requireAuth: false,
requestBody: {
description: "User data",
contentType: HttpContentTypeEnum.APPLICATION_JSON,
required: true,
scheme: new TypeDescriber<IUserDto>({
name: "User",
type: PropTypeEnum.OBJECT,
props: TypeDescriber.describeProps<IUserDto>({
maskedUid: PropTypeEnum.STRING,
firstName: PropTypeEnum.STRING,
lastName: PropTypeEnum.STRING,
gender: PropTypeEnum.STRING,
email: PropTypeEnum.STRING,
passwordB64: PropTypeEnum.STRING,
}),
}),
},
},
});
export const v1UsersSign_upPOST = function v1UsersSign_upPOST(req: Request, res: Response, next: NextFunction, body: IUserDto) {
res.status(200).json(body);
};
// The specs for GET user API route
apiDocGenerator.createRouteDoc({
method: HttpMethodEnum.GET,
path: "/v1/users/{maskedUid}",
description: "Get user by maskedUid",
produces: [
{
applicationStatus: "200",
httpStatus: HttpStatusEnum.SUCCESS,
model: {
contentType: HttpContentTypeEnum.APPLICATION_JSON,
// The way to get a created or to be created ref
scheme: new RefTypeDescriber({
name: "UserDto",
type: PropTypeEnum.OBJECT,
}),
},
},
// Add other ones as you need
],
apiDoc: {
requireAuth: true,
parameters: [
TypeDescriber.describeUrlParam({
name: "maskedUid",
in: ParameterIn.PATH,
description: "User maskedUid",
scheme: {
type: PropTypeEnum.STRING,
},
}),
],
},
});
export const v1UsersEmailGET = function v1UsersEmailGET(req: Request, res: Response, next: NextFunction, maskedUid: string) {
const userMock: IUserDto = {
maskedUid,
firstName: "John",
lastName: "Doe",
email: "[email protected]",
gender: Gender.MALE,
};
res.status(200).json(userMock);
};
- Finally you will have to put the next lines in the file were you are managing your web server application:
// In index.ts
import { apiDocGenerator } from "./api/docs";
// Setup for your server url
apiDocGenerator.setServerUrl(`http://localhost:${serverPort}`, "Local server");
apiDocGenerator.saveApiDoc(__dirname, "./openapi.json").finish();
- To conclude, is important to mention you that you would have to use a library for the
OpenAPI Swagger web interface
and setup it according to your framework, for example the library to use withexpress
isswagger-ui-express
.
Warning 💀
Use this resource at your own risk.