20

GitHub - samchon/nestia: Automatic SDK and Document generator for the NestJS

 2 years ago
source link: https://github.com/samchon/nestia
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Nestia

Automatic SDK and swagger.json generator for the NestJS.

# INSTALL NESTIA
npm install --save-dev nestia

# WHEN ALL OF THE CONTROLLRES ARE GATHERED INTO A DIRECTORY
npx nestia sdk "src/controller" --out "src/api"

# REGULAR NESTJS PATTERN
npx nestia sdk "src/**/*.controller.ts" --out "src/api"

# BUILDING SWAGGER.JSON IS ALSO POSSIBLE
npx nestia swagger "src/controller" -- out "swagger.json"

Don't write any swagger comment and DTO decorator. Just run the Nestia up.

  • No swagger comment/decorator
  • No DTO comment/decorator
  • Only pure NestJS code is required

When you're developing a backend server using the NestJS, you don't need any extra dedication, for delivering the Rest API to the client developers, like writing the swagger comments or DTO decorators.

You just run this Nestia up, then Nestia would generate the SDK automatically, by analyzing your controller classes in the compliation and runtime level. With the automatically generated SDK through this Nestia, client developer also does not need any extra work, like reading swagger and writing the duplicated interaction code. Client developer only needs to import the SDK and calls matched function with the await symbol.

Even generating the swagger.json without any swagger comment and DTO decorator is also possible. When generating the swagger.json, no DTO comment and decorator is required, either. Use only the pure interface definitions.

import api from "@samchon/bbs-api";
import { IBbsArticle } from "@samchon/bbs-api/lib/structures/bbs/IBbsArticle";
import { IPage } from "@samchon/bbs-api/lib/structures/common/IPage";

export async function test_article_read(connection: api.IConnection): Promise<void>
{
    // LIST UP ARTICLE SUMMARIES
    const index: IPage<IBbsArticle.ISummary> = await api.functional.bbs.articles.index
    (
        connection,
        "free",
        { limit: 100, page: 1 }
    );

    // READ AN ARTICLE DETAILY
    const article: IBbsArticle = await api.functional.bbs.articles.at
    (
        connection,
        "free",
        index.data[0].id
    );
    console.log(article.title, aritlce.body, article.files);
}

Usage

Installation

npm install --save-dev nestia

Installing the Nestia is very easy.

Just type the npm install --save-dev nestia command in your NestJS backend project.

SDK generation

npx nestia sdk <source_controller_directory> --out <output_sdk_directory>

npx nestia sdk "src/**/*.controller.ts" --out "src/api"
npx nestia sdk "src/controllers" --out "src/api"
npx nestia sdk "src/controllers/consumers" "src/controllers/sellers" --out "src/api"
npx nestia sdk "src/controllers" --exclude "src/**/Fake*.ts" --out "src/api"

To generate a SDK library through the Nestia is very easy.

Just type the nestia sdk <input> --out <output> command in the console. When there're multiple source directories containing the NestJS controller classes, type all of them separating by a space word. If you want to exclude some directories or files from the SDK generation, the --exclude option would be useful.

Also, when generating a SDK using the cli options, compilerOptions would follow the tsconfig.json, that is configured for the backend server. If no tsconfig.json file exists in your project, the configuration would be default option (ES5 with strict mode). If you want to use different compilerOptions with the tsconfig.json, you should configure the nestia.config.ts.

Swagger generation

npx nestia <source_controller_of_directory> --out <output_path>

npx nestia swagger "src/**/*.controller.ts" --out "./"
npx nestia swagger "src/controllers" --out "./swagger.json"
npx nestia swagger "src/consumers" "src/sellers" --out "actors.json"
npx nestia swagger "src/controllers" --exclude "src/**/Fake*.ts" -out "./" 

The Nestia even supports the swagger.json generation and it's also extermely easy.

Jsut type the nestia swagger <input> --out <output> command in the console. When there're multiple source directories containing the NestJS controller classes, type all of them separating by a space word. If you want to exclude some directories or files from the swagger.json generation, the --exclude option would be useful.

Also, when generating a SDK using the cli options, compilerOptions would follow the tsconfig.json, that is configured for the backend server. If no tsconfig.json file exists in your project, the configuration would be default option (ES5 with strict mode). If you want to use different compilerOptions with the tsconfig.json, you should configure the nestia.config.ts.

Dependencies

npx nestia install

SDK library generated by the Nestia requires the nestia-fetcher module. Also, the typescript-is and typescript-json modules can be required following your nestia.config.ts options.

The npx nestia install command installs those dependencies with package.json configuration.

{
  "name": "payments-server-api",
  "dependencies": {
    "nestia-fetcher": "^2.0.1",
    "typescript-is": "^0.19.0",
    "typescript-json": "^2.0.9"
  }
}

Advanced

nestia.config.ts

/**
 * Definition for the `nestia.config.ts` file.
 * 
 * @author Jeongho Nam - https://github.com/samchon
 */
export interface IConfiguration
{
    /**
     * List of files or directories containing the NestJS controller classes.
     */
    input: string | string[] | IConfiguration.IInput;

    /**
     * Output directory that SDK would be placed in.
     */
    output?: string;

    /**
     * Compiler options for the TypeScript.
     * 
     * If omitted, the configuration would follow the `tsconfig.json`.
     */
    compilerOptions?: ts.CompilerOptions;

    /**
     * Whether to assert parameter types or not.
     * 
     * If you configure this option to be `true`, all of the function parameters would be
     * checked through the [typescript-is](https://github.com/woutervh-/typescript-is).
     */
    assert?: boolean;

    /**
     * Whether to optimize JSON string conversion 2x faster or not.
     * 
     * If you configure this option to be `true`, the SDK library would utilize the
     * [typescript-json](https://github.com/samchon/typescript-json) and the JSON string
     * conversion speed really be 2x faster.
     */
    json?: boolean;

    /**
     * Building `swagger.json` is also possible.
     */
    swagger?: IConfiguration.ISwagger;
}
export namespace IConfiguration
{
    /**
     * List of files or directories to include or exclude to specifying the NestJS 
     * controllers.
     */
    export interface IInput
    {
        /**
         * List of files or directories containing the NestJS controller classes.
         */
        include: string[];

        /**
         * List of files or directories to be excluded.
         */
        exclude?: string[];
    }

    /**
     * Building `swagger.json` is also possible.
     */
    export interface ISwagger
    {
        /**
         * Output path of the `swagger.json`.
         * 
         * If you've configure only directory, the file name would be `swagger.json`. 
         * Otherwise you configure file name and extension, the `swagger.json` file would
         * be renamed to what you've configured.
         */
        output: string;
    }
}

Instead of specifying input and output directories using the cli options, you can specify those directories as an independent configuration file. It's the nestia.config.ts and with the nestia.config.ts file, you also configure independent TypeScript compiler option from the tsconfig.json.

Write below content as the nestia.config.ts file and place it onto the root directory of your backend project. After the configuration, you can generate the SDK only with the npx nestia sdk command, without any directory specification.

import type nestia from "nestia";

const config: nestia.IConfiguration = {
    input: "src/controllers",
    output: "src/api",
    assert: false
};
export default config;

Alternative options for the regular NestJS project:

export = {
    input: "src/**/*.controller.ts",
    /* input: {
        include: ["src/controllers/**\/*.controller.ts"],
        exclude: ["src/controllers/**\/fake_*.controller.ts"]
    },*/
    output: "src/api",
    assert: true
}

Recommended Structures

When developing a NestJS backend server with this Nestia, I recommend you to follow below directory structure. The key princinple of below structure is to gathering all of the DTO interface structures into the src/api/structures directory and gather all of the controller classes into the src/controllers directory.

If you place the SDK onto the src/api directory and gather all of the DTO interface structures into the src/api/structures directory, you can publish the SDK library very easily without any special configuration. Also when you're develop the test automation program, you can implement the API testing features very convenienty through the automatically generated SDK through this Nestia.

  • src
    • api
      • functional: automatically generated SDK functions
      • structures: DTO structures
    • controllers
    • providers
    • models
    • test: Test automation program using SDK functions
  • package.json
  • tsconfig.json
  • nestia.config.ts

For your deep understanding about this directory structure with this Nestia, I've prepared an example backend project. Looking around the example repository and reading the README.md of it, you can feel that such directory structure is how convenient for SDK publishing and test automation program implementation.

Demonstration

To demonstrate which SDK codes would be generated by this Nestia:

  • Representative files

  • Demonstration Projects
    • absolute: Absolute path with the baseUrl option
    • alias@api: The src/api directory has been aliased by paths option
    • alias@src: Entire src directory has been aliased by paths option
    • default: No tsconfig.json and nestia.config.ts
    • esnext: ECMAScript target version is ESNEXT
    • exclude: Exclude option using the --exclude commad
    • nestia.config.ts: Configured nestia.config.ts with assert mode
    • reference: Configured input files as src/**/*.controller.ts
    • tsconfig.json: Special configuration through the tsconfig.json

As you can see from the below code, the Nestia can use the interface directory.

You dont' need to define any extra comment or decorator function to make the DTO (Data Transfer Object). Just define the DTO as a pure interface structure, then Nestia will do everything instead of you.

If you're afraiding because your type is union or intersection, I can say that it does not matter. Even when generic or conditional type comes, it does not matter. Just enjoy the pure TypeScript type.

/**
 * Comment wrote on an article.
 * 
 * @author Jeongho Nam - https://github.com/samchon
 */
export interface ISaleComment
{
    /**
     * Primary Key.
     */
    id: number;

    /**
     * Type of the writer.
     */
    writer_type: "seller" | "consumer";

    /**
     * Name of the writer.
     */
    writer_name: string;

    /**
     * Contents of the comments.
     * 
     * When the comment writer tries to modify content, it would not modify the comment
     * content but would be accumulated. Therefore, all of the people can read how
     * the content has been changed.
     */
    contents: ISaleComment.IContent[];

    /**
     * Creation time.
     */
    created_at: string;
}

export namespace ISaleComment
{
    /**
     * Store info.
     */
    export interface IStore
    {
        /**
         * Body of the content.
         */
        body: string;
    }

    /**
     * Content info.
     */
    export interface IContent extends IStore
    {
        /**
         * Creation time.
         */
        created_at: string;
    }
}

Controller

If you've decided to adapt this Nestia and you want to generate the SDK directly, you don't need any extra work. Just keep you controller class down and do noting. The only one exceptional case that you need an extra dedication is, when you want to explain about the API function to the client developers through the comments.

@nest.Controller("consumers/:section/sales/:saleId/questions")
export class ConsumerSaleQuestionsController
{
    /**
     * Store a new question.
     * 
     * @param request Instance of the Express.Request
     * @param section Code of the target section
     * @param saleId ID of the target sale
     * @param input Content to archive
     * 
     * @return Newly archived question
     * @throw 400 bad request error when type of the input data is not valid
     * @throw 401 unauthorized error when you've not logged in yet
     */
    @nest.Post()
    public store
        (
            @nest.Request() request: express.Request,
            @nest.Param("section") section: string, 
            @nest.Param("saleId") saleId: number, 
            @nest.Body() input: ISaleQuestion.IStore
        ): Promise<ISaleQuestion>;
}

When you run the Nestia up using the upper controller class ConsumerSaleQuestionsController, the Nestia would generate below function for the client developers, by analyzing the ConsumerSaleQuestionsController class in the compilation and runtime level.

As you can see, the comments from the ConsumerSaleQuestionsController.store() are fully copied to the SDK function. Therefore, if you want to deliver detailed description about the API function, writing the detailed comment would be tne best choice.

/**
 * Store a new question.
 * 
 * @param connection connection Information of the remote HTTP(s) server with headers (+encryption password)
 * @param request Instance of the Express.Request
 * @param section Code of the target section
 * @param saleId ID of the target sale
 * @param input Content to archive
 * @return Newly archived question
 * @throw 400 bad request error when type of the input data is not valid
 * @throw 401 unauthorized error when you've not logged in yet
 * 
 * @nestia Generated by Nestia - https://github.com/samchon/nestia
 * @controller ConsumerSaleQuestionsController.store()
 * @path POST /consumers/:section/sales/:saleId/questions/
 */
export function store
    (
        connection: IConnection,
        section: string,
        saleId: number,
        input: Primitive<store.Input>
    ): Promise<store.Output>
{
    return Fetcher.fetch
    (
        connection,
        store.ENCRYPTED,
        store.METHOD,
        store.path(section, saleId),
        input
    );
}
export namespace store
{
    export type Input = Primitive<ISaleInquiry.IStore>;
    export type Output = Primitive<ISaleInquiry<ISaleArticle.IContent>>;

    export const METHOD = "POST" as const;
    export const PATH: string = "/consumers/:section/sales/:saleId/questions";
    export const ENCRYPTED: Fetcher.IEncrypted = {
        request: true,
        response: true,
    };

    export function path(section: string, saleId: number): string
    {
        return `/consumers/${section}/sales/${saleId}/questions`;
    }
}

swagger.json

Even the swagger.json generation does not require any swagger comment and DTO decorator.

The Nestia will generate the perfect swagger.json automatically, by analyzing your source code (DTO interface and controller class) in the compilation and runtime level. Furthermore, your descriptive comments would be automatically assigned into the adequate description property in the swagger.json.

{
  "paths": {
    "/consumers/{section}/sales/{saleId}/comments/{articleId}": {
      "post": {
        "tags": [],
        "parameters": [
          {
            "name": "section",
            "in": "path",
            "description": "Code of the target section",
            "schema": {
              "type": "string",
              "nullable": false
            },
            "required": true
          },
          {
            "name": "saleId",
            "in": "path",
            "description": "ID of the target sale",
            "schema": {
              "type": "number",
              "nullable": false
            },
            "required": true
          },
          {
            "name": "articleId",
            "in": "path",
            "description": "ID of the target article",
            "schema": {
              "type": "number",
              "nullable": false
            },
            "required": true
          }
        ],
        "requestBody": {
          "description": "Content to write",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ISaleComment.IStore"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Newly archived comment",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ISaleComment"
                }
              }
            }
          },
          "400": {
            "description": "bad request error when type of the input data is not valid"
          },
          "401": {
            "description": "unauthorized error when you've not logged in yet"
          },
          "403": {
            "description": "forbidden error when you're a seller and the sale is not yours"
          },
          "404": {
            "description": "not found error when unable to find the matched record"
          }
        },
        "description": "Store a new comment."
      }
    }
  },
  "components": {
    "schemas": {
      "ISaleComment": {
        "type": "object",
        "properties": {
          "id": {
            "description": "Primary Key.",
            "type": "number",
            "nullable": false
          },
          "writer_type": {
            "description": "Type of the writer.",
            "type": "string",
            "nullable": false
          },
          "writer_name": {
            "description": "Name of the writer.",
            "type": "string",
            "nullable": false
          },
          "contents": {
            "description": "Contents of the comments.\n\nWhen the comment writer tries to modify content, it would not modify the comment\ncontent but would be accumulated. Therefore, all of the people can read how\nthe content has been changed.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ISaleComment.IContent"
            },
            "nullable": false
          },
          "created_at": {
            "description": "Creation time.",
            "type": "string",
            "nullable": false
          }
        },
        "nullable": false,
        "required": [
          "id",
          "writer_type",
          "writer_name",
          "contents",
          "created_at"
        ],
        "description": "Comment wrote on an article."
      },
      "ISaleComment.IContent": {
        "type": "object",
        "properties": {
          "created_at": {
            "description": "Creation time.",
            "type": "string",
            "nullable": false
          },
          "body": {
            "description": "Body of the content.",
            "type": "string",
            "nullable": false
          }
        },
        "nullable": false,
        "required": [
          "created_at",
          "body"
        ],
        "description": "Content info."
      },
      "ISaleComment.IStore": {
        "type": "object",
        "properties": {
          "body": {
            "description": "Body of the content.",
            "type": "string",
            "nullable": false
          }
        },
        "nullable": false,
        "required": [
          "body"
        ],
        "description": "Store info."
      }
    }
  }
}

Appendix

Template Project

https://github.com/samchon/backend

I support template backend project using this Nestia* library, backend.

Reading the README content of the backend template repository, you can find lots of example backend projects who've been generated from the backend. Furthermore, those example projects guide how to generate SDK library from the Nestia and how to distribute the SDK library thorugh the NPM module.

Therefore, if you're planning to compose your own backend project using this Nestia, I recommend you to create the repository and learn from the backend template project.

Nestia-Helper

https://github.com/samchon/nestia-helper

Helper library of the NestJS with Nestia.

nestia-helper is a type of helper library for Nestia by enhancing decorator functions. Also, all of the decorator functions provided by this nestia-helper are all fully compatible with the Nestia, who can generate SDK library by analyzing NestJS controller classes in the compilation level.

Of course, this nestia-helper is not essential for utilizing the NestJS and Nestia. You can generate SDK library of your NestJS developed backend server without this nestia-helper. However, as decorator functions of this nestia-helper is enough strong, I recommend you to adapt this nestia-helper when using NestJS and Nestia.

Safe-TypeORM

https://github.com/samchon/safe-typeorm

safe-typeorm is another library that what I've developed, helping TypeORM in the compilation level and optimizes DB performance automatically without any extra dedication.

Therefore, this Nestia makes you to be much convenient in the API interaction level and safe-typeorm helps you to be much convenient in the DB interaction level. With those Nestia and safe-typeorm, let's implement the backend server much easily and conveniently.

  • When writing SQL query,
    • Errors would be detected in the compilation level
    • Auto Completion would be provided
    • Type Hint would be supported
  • You can implement App-join very conveniently
  • When SELECTing for JSON conversion
    • App-Join with the related entities would be automatically done
    • Exact JSON type would be automatically deduced
    • The performance would be automatically tuned
  • When INSERTing records
    • Sequence of tables would be automatically sorted by analyzing dependencies
    • The performance would be automatically tuned

Archidraw

https://www.archisketch.com/

I have special thanks to the Archidraw, where I'm working for.

The Archidraw is a great IT company developing 3D interior editor and lots of solutions based on the 3D assets. Also, the Archidraw is the first company who had adopted this Nestia on their commercial backend project, even this Nestia was in the alpha level.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK