[Nestia] I made backend simulator for frontend developers (similar with MSW, but fully automated)
Tags: opensource, typescript, nestjs, programming
Published: 2023-06-09 11:08:07

## Summary
![Nestia Logo](https://nestia.io/logo.png)

I made NestJS backend server simulator in my library [nestia](https://github.com/samchon/nestia).

  - Repository: https://github.com/samchon/nestia
  - Documents
    - SDK Library: https://nestia.io/docs/sdk/sdk/
    - E2E Functions Generator: https://nestia.io/docs/sdk/e2e/
    - Mockup Simulator: https://nestia.io/docs/sdk/simulator/

You can build the backend server simulator by only one line command. 

[nestia](https://github.com/samchon/nestia) will analyze your server codes, and generate imitation codes validating and composing mock-up data. It can be used in frontend application, without real backend server interaction.

  - Client (frontend) do not need to connect with backend server
  - Frontend can start development even if backend server is not ready
  - Backend do not need to waste time for mock-up data composition

```typescript
const article: IBbsArticle = await api.functional.bbs.articles.store(
    {
        simulate: true, // activate simulator
        host: "http://127.0.0.1", // not important when simulating
    },
    "notice",
    {
        title: "Hello, world!",
        content: "This is a test article.",
    },
);
```

Furthermore, [nestia](https://github.com/samchon/nestia) supports much more convenient and powerful features like below. With those features, you can get high productivity even gaining both high performance and easy development.

  - **Productivity**
    - Automatic e2e functions generator
    - NestJS simulator for frontend developers
    - SDK library for frontend developers
  - **Performance**
    - 25,000x faster validation
    - 200x faster JSON serialization
    - Totally 30x performance up
  - **Easy Development**
    - Only pure TypeScript type required
    - Besides, NestJS needs 3 times duplicated DTO definitions

p.s) Currently, frontend level backend server simulator is only possible, when the backend server be implemented by NestJS (+[nestia](https://github.com/samchon/nestia)). However, it would be possible in every backend server frameworks, soon.




## Preface
In nowadays, I've developed a NestJS backend server simulator in my library [nestia](https://github.com/samchon/nestia).

For reference, the simulator does not connect to remote backend server. It just imitates request data validation and response data construction, by itself. Therefore, frontend developers can start application development only with API interfaces, even if the backend server is not ready.

Below is a piece of SDK (Software Development Kit) code, generated by [nestia](https://github.com/samchon/nestia), supporting the simulation mode. AS you can see, the simulator be activated just by configuring `IConnection.simulate` value to be `true`. Otherwise, the SDK would connect to the remote backend server.

```typescript
/**
 * @packageDocumentation
 * @module api.functional.bbs.articles
 * @nestia Generated by Nestia - https://github.com/samchon/nestia 
 */
//================================================================
import { Fetcher } from "@nestia/fetcher";
import type { IConnection } from "@nestia/fetcher";
import typia from "typia";

import { NestiaSimulator } from "./../../../utils/NestiaSimulator";
import type { IBbsArticle } from "./../../../structures/IBbsArticle";

/**
 * Update an article.
 * 
 * @param section Section code
 * @param id Target article ID
 * @param input Content to update
 * @returns Updated content
 * 
 * @controller BbsArticlesController.update()
 * @path PUT /bbs/:section/articles/:id
 * @nestia Generated by Nestia - https://github.com/samchon/nestia
 */
export async function update(
    connection: IConnection,
    section: string,
    id: string,
    input: update.Input,
): Promise<update.Output> {
    return !!connection.simulate
        ? update.simulate(
              connection,
              section,
              id,
              input,
          )
        : Fetcher.fetch(
              connection,
              update.ENCRYPTED,
              update.METHOD,
              update.path(section, id),
              input,
          );
}
export namespace update {
    export type Input = IBbsArticle.IStore;
    export type Output = IBbsArticle;

    export const METHOD = "PUT" as const;
    export const PATH: string = "/bbs/:section/articles/:id";
    export const ENCRYPTED: Fetcher.IEncrypted = {
        request: false,
        response: false,
    };

    export const path = (section: string, id: string): string => {
        return `/bbs/${encodeURIComponent(section ?? "null")}/articles/${encodeURIComponent(id ?? "null")}`;
    }
    export const random = (g?: Partial<typia.IRandomGenerator>): Output =>
        typia.random<Output>(g);
    export const simulate = async (
        connection: IConnection,
        section: string,
        id: string,
        input: update.Input,
    ): Promise<Output> => {
        const assert = NestiaSimulator.assert({
            method: METHOD,
            host: connection.host,
            path: path(section, id)
        });
        assert.param("section")("string")(() => typia.assert(section));
        assert.param("id")("uuid")(() => typia.assert(id));
        assert.body(() => typia.assert(input));
        return random(
            typeof connection.simulate === 'object'
            && connection.simulate !== null
                ? connection.simulate
                : undefined
        );
    }
}
```

Within framework of backend developers, they also do not need to compose mock-up data, either. 

Mock-up data composition would be automated by the simulator, and backend developers may focus on the business logic like API interface designs and main program developments.

Like below example code, just define only API interfaces. Then, builds simulator and delivers to frontend developers. Then you backend developer can implement application at the same time with frontend developers in parallel.

For reference, [nestia](https://github.com/samchon/nestia) also can generate [e2e test functions automatically](https://dev.to/samchon/nestia-i-made-automatic-e2e-test-functions-generator-for-nestjs-4o3p). Therefore, if you've succeeded to determine API specs, you can concentrate only on the main program development.

```typescript
@Controller("bbs/articles")
export class BbsArticlesController {
    @TypedRoute.Post()
    public async store(
        @TypedBody() input: IBbsArticle.IStore,
    ): Promise<IBbsArticle> {
        return typia.random<IBbsArticle>();
    }
}
```




## Software Development Kit
![SDK](https://user-images.githubusercontent.com/13158709/215004990-368c589d-7101-404e-b81b-fbc936382f05.gif)

In traditional software development, frontend developers recognize backend server API spec by reading `Swagger Documents` or similar one like `RestDocs`. By the way, as frontend developers are not robots but humans, reading and re-writing API specs in frontend application is a very annoying and error-prone work.

> [nestia](https://github.com/samchon/nestia) also can generate Swagger Documents, and it is much evolved than traditional NestJS. However, I recommend NestJS backend developers to utilize SDK library much more, for frontend developers

In [nestia](https://github.com/samchon/nestia) case, it can generate SDK (Software Development Kit) library for frontend developers. As you can see from above example code, the SDK library is very simple and easy to use. Only `import` and `function` call statements are required. The SDK library will make frontend development much safer through type hints. NestJS backend server simulator also provided by the SDK library.

By the way, how [nestia](https://github.com/samchon/nestia) generates the SDK library? The secret is on your source code. [nestia](https://github.com/samchon/nestia) reads your NestJS backend server code directly (especially `controllers`), and analyzes which API routes are provided, and which DTO types are being used.

After these analyses, [nestia](https://github.com/samchon/nestia) writes `fetch` functios for each API routes, and import statemtns that are used in each controller classes. The SDK library is generated by such source code analyses and combininng fetch functions and import statements.

The NestJS backend server simulator, it is just a little extension of such SDK library.




## AOT Compilation
By the way, in the previous [#Summary](#summary) section, I'd told that [nestia](https://github.com/samchon/nestia) is much easier than traditional NestJS. I'd told that when defining DTO schema, [nestia](https://github.com/samchon/nestia) needs only pure TypeScript type, but traditinal NestJS needs triple times duplicated definitions.

Looking at below example DTO schema definitions, you may understand what I am saying.

```typescript
//----
// Traditional NestJS needs 3x duplicated definitions
//----
export class BbsArticle {
    @ApiProperty({
        type: () => AttachmentFile,
        nullable: true,
        isArray: true,
        description: "List of attached files.",
    })
    @Type(() => AttachmentFile)
    @IsArray()
    @IsOptional()
    @IsObject({ each: true })
    @ValidateNested({ each: true })
    files!: AttachmentFile[] | null;
}

//----
// Besides, nestia can understand pure TypeScript type
//----
export interface IBbsArticle {
    /**
     * List of attached files.
     */
    files: IAttachmentFile[] | null;
}

//----
// Therefore, advanced NestJS controller code can be
//----
@Controller("bbs/articles")
export class BbsArticlesController {
    @TypedRoute.Post()
    public async store(
        @TypedBody() input: IBbsArticle.IStore,
    ): Promise<IBbsArticle> {
        // just fine with pure interface
        //
        // validation is 25,000x times faster
        // JSON serialization is 200x faster
        ...
    }
}
```

Looking at above example code, someone may ask me:

> "Hey Samchon, how to validate the `IBbsArticle.IStore` type?
>
> If a client requests with invalid data, is it possible to reject? As you know, TypeScript interface does not have any schema info in the runtime, and it is the reason why traditional NestJS has enforced developers to define triple times duplicated DTO schemas."

However, my answer is "It is enougly possible". 

Also, even in this case, the secret is on your source code.

When you compile your NestJS backend servere, [nestia](https://github.com/samchon/nestia) will analyze your source codes, and analyzes how DTO schemas are being composed. Reading your DTO schemas and traveling properties of them, [nestia](https://github.com/samchon/nestia) will generate validation and JSON serialization codes for each API routes.

Looking at below compiled code, it is dedicatedly optimized for `IBbsArticle.IStore` type. It's the secret of [nestia](https://github.com/samchon/nestia), which does not require triple times duplicated DTO schema definitions, but requires only pure TypeScript type. Also, such compile time optimization through source code analyzing is called, "AOT (Ahead of Time) compilation".

```javascript
__param(2, core_1.default.TypedBody({ type: "assert", assert: input => {
        const __is = input => {
            const $is_custom = core_1.default.TypedBody.is_custom;
            const $is_url = core_1.default.TypedBody.is_url;
            const $io0 = input => "string" === typeof input.title && 3 <= input.title.length && 50 >= input.title.length && "string" === typeof input.body && (Array.isArray(input.files) && input.files.every(elem => "object" === typeof elem && null !== elem && $io1(elem)));
            const $io1 = input => (null === input.name || "string" === typeof input.name && 255 >= input.name.length && $is_custom("minLengt", "string", "1", input.name)) && (null === input.extension || "string" === typeof input.extension && 1 <= input.extension.length && 8 >= input.extension.length) && ("string" === typeof input.url && $is_url(input.url));
            return "object" === typeof input && null !== input && $io0(input);
        };
        if (false === __is(input))
            ((input, _path, _exceptionable = true) => {
                const $guard = core_1.default.TypedBody.guard;
                const $is_custom = core_1.default.TypedBody.is_custom;
                const $is_url = core_1.default.TypedBody.is_url;
                const $ao0 = (input, _path, _exceptionable = true) => ("string" === typeof input.title && (3 <= input.title.length || $guard(_exceptionable, {
                    path: _path + ".title",
                    expected: "string (@minLength 3)",
                    value: input.title
                })) && (50 >= input.title.length || $guard(_exceptionable, {
                    path: _path + ".title",
                    expected: "string (@maxLength 50)",
                    value: input.title
                })) || $guard(_exceptionable, {
                    path: _path + ".title",
                    expected: "string",
                    value: input.title
                })) && ("string" === typeof input.body || $guard(_exceptionable, {
                    path: _path + ".body",
                    expected: "string",
                    value: input.body
                })) && ((Array.isArray(input.files) || $guard(_exceptionable, {
                    path: _path + ".files",
                    expected: "Array<IAttachmentFile>",
                    value: input.files
                })) && input.files.every((elem, _index1) => ("object" === typeof elem && null !== elem || $guard(_exceptionable, {
                    path: _path + ".files[" + _index1 + "]",
                    expected: "IAttachmentFile",
                    value: elem
                })) && $ao1(elem, _path + ".files[" + _index1 + "]", true && _exceptionable) || $guard(_exceptionable, {
                    path: _path + ".files[" + _index1 + "]",
                    expected: "IAttachmentFile",
                    value: elem
                })) || $guard(_exceptionable, {
                    path: _path + ".files",
                    expected: "Array<IAttachmentFile>",
                    value: input.files
                }));
                const $ao1 = (input, _path, _exceptionable = true) => (null === input.name || "string" === typeof input.name && (255 >= input.name.length || $guard(_exceptionable, {
                    path: _path + ".name",
                    expected: "string (@maxLength 255)",
                    value: input.name
                })) && ($is_custom("minLengt", "string", "1", input.name) || $guard(_exceptionable, {
                    path: _path + ".name",
                    expected: "string (@minLengt 1)",
                    value: input.name
                })) || $guard(_exceptionable, {
                    path: _path + ".name",
                    expected: "(null | string)",
                    value: input.name
                })) && (null === input.extension || "string" === typeof input.extension && (1 <= input.extension.length || $guard(_exceptionable, {
                    path: _path + ".extension",
                    expected: "string (@minLength 1)",
                    value: input.extension
                })) && (8 >= input.extension.length || $guard(_exceptionable, {
                    path: _path + ".extension",
                    expected: "string (@maxLength 8)",
                    value: input.extension
                })) || $guard(_exceptionable, {
                    path: _path + ".extension",
                    expected: "(null | string)",
                    value: input.extension
                })) && ("string" === typeof input.url && ($is_url(input.url) || $guard(_exceptionable, {
                    path: _path + ".url",
                    expected: "string (@format url)",
                    value: input.url
                })) || $guard(_exceptionable, {
                    path: _path + ".url",
                    expected: "string",
                    value: input.url
                }));
                return ("object" === typeof input && null !== input || $guard(true, {
                    path: _path + "",
                    expected: "IBbsArticle.IStore",
                    value: input
                })) && $ao0(input, _path + "", true) || $guard(true, {
                    path: _path + "",
                    expected: "IBbsArticle.IStore",
                    value: input
                });
            })(input, "$input", true);
        return input;
    } })),
```

For reference, such AOT compilation optimization is much light and faster than general logics accessing to each properties through `for (const key in obj)` statements like traditional NestJS. Measuring benchmark, it's 25,000 times faster.

About this secret (`static` access vs `dynamic` access), I'll write another article later. This is related to v8 engine optimization, and I can sure that it would be much interesting than any other stories.

![Assert Benchmark](https://raw.githubusercontent.com/samchon/typia/master/benchmark/results/11th%20Gen%20Intel(R)%20Core(TM)%20i5-1135G7%20%40%202.40GHz/images/assert.svg)




## Random Generator
If you've read my article carefully, you may understand how to simulate response data, by yourself. 

Yes, the response data also be composed by AOT compilation skill. Let's read the simulator (SDK) code again, then you can find that [`typia.random<T>()`](https://typia.io/docs/random/) function be used. The [`typia.random<T>()`](https://typia.io/docs/random/) is the last secret of [nestia](https://github.com/samchon/nestia) generated simulator. It generates random response data by analyzing the response DTO type.

```typescript
/**
 * @packageDocumentation
 * @module api.functional.bbs.articles
 * @nestia Generated by Nestia - https://github.com/samchon/nestia 
 */
//================================================================
import { Fetcher } from "@nestia/fetcher";
import type { IConnection } from "@nestia/fetcher";
import typia from "typia";

import { NestiaSimulator } from "./../../../utils/NestiaSimulator";
import type { IBbsArticle } from "./../../../structures/IBbsArticle";

/**
 * Update an article.
 * 
 * @param section Section code
 * @param id Target article ID
 * @param input Content to update
 * @returns Updated content
 * 
 * @controller BbsArticlesController.update()
 * @path PUT /bbs/:section/articles/:id
 * @nestia Generated by Nestia - https://github.com/samchon/nestia
 */
export async function update(
    connection: IConnection,
    section: string,
    id: string,
    input: update.Input,
): Promise<update.Output> {
    return !!connection.simulate
        ? update.simulate(
              connection,
              section,
              id,
              input,
          )
        : Fetcher.fetch(
              connection,
              update.ENCRYPTED,
              update.METHOD,
              update.path(section, id),
              input,
          );
}
export namespace update {
    export type Input = IBbsArticle.IStore;
    export type Output = IBbsArticle;

    export const METHOD = "PUT" as const;
    export const PATH: string = "/bbs/:section/articles/:id";
    export const ENCRYPTED: Fetcher.IEncrypted = {
        request: false,
        response: false,
    };

    export const path = (section: string, id: string): string => {
        return `/bbs/${encodeURIComponent(section ?? "null")}/articles/${encodeURIComponent(id ?? "null")}`;
    }
    export const random = (g?: Partial<typia.IRandomGenerator>): Output =>
        typia.random<Output>(g);
    export const simulate = async (
        connection: IConnection,
        section: string,
        id: string,
        input: update.Input,
    ): Promise<Output> => {
        const assert = NestiaSimulator.assert({
            method: METHOD,
            host: connection.host,
            path: path(section, id)
        });
        assert.param("section")("string")(() => typia.assert(section));
        assert.param("id")("uuid")(() => typia.assert(id));
        assert.body(() => typia.assert(input));
        return random(
            typeof connection.simulate === 'object'
            && connection.simulate !== null
                ? connection.simulate
                : undefined
        );
    }
}
```

When you compile the SDK library, such script would be written in the `update.random()` function, through AOT compilation. 

The parameter `g?: Partial<typia.IRandomGenerator>` is a random seeder, but it is not neccessary.

```javascript
export.update = update;
(function update() {
    update.random = (g) => (generator => {
        const $generator = typia_1.default.random.generator;
        const $pick = typia_1.default.random.pick;
        const $ro0 = (_recursive = false, _depth = 0) => { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2; return ({
            id: (_d = (_c = (_b = ((_a = generator === null || generator === void 0 ? void 0 : generator.customs) !== null && _a !== void 0 ? _a : $generator.customs)) === null || _b === void 0 ? void 0 : _b.string) === null || _c === void 0 ? void 0 : _c.call(_b, [
                {
                    name: "format",
                    value: "uuid"
                }
            ])) !== null && _d !== void 0 ? _d : ((_e = generator === null || generator === void 0 ? void 0 : generator.uuid) !== null && _e !== void 0 ? _e : $generator.uuid)(),
            section: (_j = (_h = (_g = ((_f = generator === null || generator === void 0 ? void 0 : generator.customs) !== null && _f !== void 0 ? _f : $generator.customs)) === null || _g === void 0 ? void 0 : _g.string) === null || _h === void 0 ? void 0 : _h.call(_g, [])) !== null && _j !== void 0 ? _j : ((_k = generator === null || generator === void 0 ? void 0 : generator.string) !== null && _k !== void 0 ? _k : $generator.string)(),
            created_at: (_p = (_o = (_m = ((_l = generator === null || generator === void 0 ? void 0 : generator.customs) !== null && _l !== void 0 ? _l : $generator.customs)) === null || _m === void 0 ? void 0 : _m.string) === null || _o === void 0 ? void 0 : _o.call(_m, [
                {
                    name: "format",
                    value: "date-time"
                }
            ])) !== null && _p !== void 0 ? _p : ((_q = generator === null || generator === void 0 ? void 0 : generator.datetime) !== null && _q !== void 0 ? _q : $generator.datetime)(),
            title: (_u = (_t = (_s = ((_r = generator === null || generator === void 0 ? void 0 : generator.customs) !== null && _r !== void 0 ? _r : $generator.customs)) === null || _s === void 0 ? void 0 : _s.string) === null || _t === void 0 ? void 0 : _t.call(_s, [
                {
                    name: "minLength",
                    value: "3"
                },
                {
                    name: "maxLength",
                    value: "50"
                }
            ])) !== null && _u !== void 0 ? _u : ((_v = generator === null || generator === void 0 ? void 0 : generator.string) !== null && _v !== void 0 ? _v : $generator.string)(((_w = generator === null || generator === void 0 ? void 0 : generator.integer) !== null && _w !== void 0 ? _w : $generator.integer)(3, 50)),
            body: (_0 = (_z = (_y = ((_x = generator === null || generator === void 0 ? void 0 : generator.customs) !== null && _x !== void 0 ? _x : $generator.customs)) === null || _y === void 0 ? void 0 : _y.string) === null || _z === void 0 ? void 0 : _z.call(_y, [])) !== null && _0 !== void 0 ? _0 : ((_1 = generator === null || generator === void 0 ? void 0 : generator.string) !== null && _1 !== void 0 ? _1 : $generator.string)(),
            files: ((_2 = generator === null || generator === void 0 ? void 0 : generator.array) !== null && _2 !== void 0 ? _2 : $generator.array)(() => $ro1(_recursive, _recursive ? 1 + _depth : _depth))
        }); };
        const $ro1 = (_recursive = false, _depth = 0) => { var _a, _b, _c, _d, _e; return ({
            name: $pick([
                () => null,
                () => { var _a, _b, _c, _d, _e, _f; return (_d = (_c = (_b = ((_a = generator === null || generator === void 0 ? void 0 : generator.customs) !== null && _a !== void 0 ? _a : $generator.customs)) === null || _b === void 0 ? void 0 : _b.string) === null || _c === void 0 ? void 0 : _c.call(_b, [
                    {
                        name: "minLengt",
                        value: "1"
                    },
                    {
                        name: "maxLength",
                        value: "255"
                    }
                ])) !== null && _d !== void 0 ? _d : ((_e = generator === null || generator === void 0 ? void 0 : generator.string) !== null && _e !== void 0 ? _e : $generator.string)(((_f = generator === null || generator === void 0 ? void 0 : generator.integer) !== null && _f !== void 0 ? _f : $generator.integer)(5, 255)); }
            ])(),
            extension: $pick([
                () => null,
                () => { var _a, _b, _c, _d, _e, _f; return (_d = (_c = (_b = ((_a = generator === null || generator === void 0 ? void 0 : generator.customs) !== null && _a !== void 0 ? _a : $generator.customs)) === null || _b === void 0 ? void 0 : _b.string) === null || _c === void 0 ? void 0 : _c.call(_b, [
                    {
                        name: "minLength",
                        value: "1"
                    },
                    {
                        name: "maxLength",
                        value: "8"
                    }
                ])) !== null && _d !== void 0 ? _d : ((_e = generator === null || generator === void 0 ? void 0 : generator.string) !== null && _e !== void 0 ? _e : $generator.string)(((_f = generator === null || generator === void 0 ? void 0 : generator.integer) !== null && _f !== void 0 ? _f : $generator.integer)(1, 8)); }
            ])(),
            url: (_d = (_c = (_b = ((_a = generator === null || generator === void 0 ? void 0 : generator.customs) !== null && _a !== void 0 ? _a : $generator.customs)) === null || _b === void 0 ? void 0 : _b.string) === null || _c === void 0 ? void 0 : _c.call(_b, [
                {
                    name: "format",
                    value: "url"
                }
            ])) !== null && _d !== void 0 ? _d : ((_e = generator === null || generator === void 0 ? void 0 : generator.url) !== null && _e !== void 0 ? _e : $generator.url)()
        }); };
        return $ro0();
    })(g);
})((update = exports.update) || (exports.update = {}));
```




## Closing
In current article, I've introduced NestJS backend server simulator through SDK library. However, as you know, frontend level backend server simulation is only possible when the backend server be developed with NestJS (+[nestia](https://github.com/samchon/nestia)).

By the way, I'm developing a new library `@nestia/migrate`, which can convert `swagger.json` file to a NestJS project. It's still in development, but most reached to the goal. After the `@nestia/migrate` library be released, you can simulate any backend server, even it's not developed with NestJS.

Look forward to it, application development would be much easier.