DEV Community

Cover image for Stop using Swagger-UI and MSW, but SDK instead
Jeongho Nam
Jeongho Nam

Posted on • Edited on

Stop using Swagger-UI and MSW, but SDK instead

Summary

If you're a frontend developer, stop using swagger-ui and msw.

Instead, you can build SDK (Software Development Kit) automatically.

With the SDK, you don't need to be suffered from swagger documents reading and msw mocking anymore. You can only focus on your business logic, and build your frontend application much more quickly and safely.

Traditional way - swagger-ui

Image description

If you're a frontend developer, and have received swagger documents from backend, you may open it on swagger-ui.

Reading a swagger documents from swagger-ui, you may implement interaction code with backend server using axios or fetch function. You also need to write DTO (Data Transfer Object) structures looking at JSON schema definitions. Some professional frontend developers even build a mocking backend server through msw for testing.

During above processes, you may take a mistake that mis-reading API specs, or mis-writing DTO structures. Also, mocking the backend server with msw, you can misunderstand the real backend server's API specs. As long as front-end developers are human, not robots, these are all common mistakes we see all around us.

By the way, such common mistakes sometimes cause serious problems in the real world. At least, such mistakes never be caught by compiler, but only by runtime. The more mistakes we make, the harder it is to catch and debug. Of course, even if there are no mistakes, redundant DTO or fetch function implementation and msw mocking code by hand is very laborious and cumbersome works.

  • Hand-writings
    • fetch functions
    • DTO structures
    • msw mocking code
  • Redundant and laborious works
  • Mistakes are not caught in compile time

New era - Software Development Kit

npm install -g @nestia/migrate
npx @nestia/migrate swagger.json outputDirectory
Enter fullscreen mode Exit fullscreen mode

By the way, if such processes can be fully automated, how it would be?

Setup @nestia/migrate and run above build command. Then @nestia/migrate will analyze the target swagger.json file, and write TypeScript codes instead of you. The automatically generated TypeScript codes will contain all of the following features, and I call it as SDK (Software Development Kit).

  • fetch functions
  • DTO structures
  • Mockup simulator for testing

Looking around the newly built SDK files, you can find that fetch functions are gathered into src/api/functional directory, and converted DTO types are stored into src/structures directory. When you open one of them, then you may understand how the SDK looks like.

SDK

Right is client (frontend) code utilizing SDK

For reference, left is migrated NestJS server code

Demonstration

Let's see how the SDK looks like with demo project.

You can repeat the same SDK generation process by typing below commands.

git clone https://github.com/samchon/nestia-template

cd nestia-template
npm install
npm run build:swagger

npm install -g @nestia/migrate
npx @nestia/migrate packages/api/swagger.json ../bbs
Enter fullscreen mode Exit fullscreen mode

src/api/structures/IBbsArticle.ts

Comparing automatically generated TypeScript DTO structure type with original swagger.json file's JSON schema definition, you can find that it was perfectly converted. It succeeded to revive every property names and types exactly even including format comment tags.

export type IBbsArticle = {
    /**
     * Primary Key.
     * 
     * @format uuid
     */
    id: string;
    /**
     * Section code.
     * 
     */
    section: string;
    /**
     * Name of nickname of writer.
     * 
     */
    writer: string;
    /**
     * List of snapshot contents.
     * 
     * Whenever updating an article, its contents would be accumulated.
     * 
     */
    snapshots: Array<IBbsArticle.ISnapshot>;
    /**
     * Creation time of the article.
     * 
     * @format date-time
     */
    created_at: string;
}
Enter fullscreen mode Exit fullscreen mode

src/api/functional/bbs/articles/index.ts

Automatically generated fetch function is also perfect.

/**
 * Update article.
 * 
 * When updating, this BBS system does not overwrite the content, but accumulate it.
 * Therefore, whenever an article being updated, length of {@link IBbsArticle.snapshots}
 * would be increased and accumulated.
 * 
 * @param section Target section
 * @param id Target articles id
 * @param input Content to update
 * @returns Newly created content info
 * 
 * @controller BbsArticlesController.putById()
 * @path PUT /bbs/articles/:section/:id
 * @nestia Generated by Nestia - https://github.com/samchon/nestia
 */
export async function putById(
    connection: IConnection,
    section: string,
    id: string,
    body: IBbsArticle.IUpdate,
): Promise<putById.Output> {
    return !!connection.simulate
        ? putById.simulate(
              connection,
              section,
              id,
              body,
          )
        : Fetcher.fetch(
              connection,
              putById.ENCRYPTED,
              putById.METHOD,
              putById.path(section, id),
              body,
          );
}
export namespace putById {
    export type Input = IBbsArticle.IUpdate;
    export type Output = IBbsArticle.ISnapshot;

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

    export const path = (section: string, id: string): string => {
        return `/bbs/articles/${encodeURIComponent(section ?? "null")}/${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,
        body: putById.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")("string")(() => typia.assert(id));
        assert.body(() => typia.assert(body));
        return random(
            typeof connection.simulate === 'object' &&
                connection.simulate !== null
                ? connection.simulate
                : undefined
        );
    }
}
Enter fullscreen mode Exit fullscreen mode

Also, looking at the above fetch function again detaily, you may find that SDK function is embedding mockup simulator code.

When connection.simulate property be configured, internal putById.simulate() function be called, and it checks the input parameters through typia.assert<T>() function. After the validations, the putById.simulate() function returns a random value through typia.random<T>() function.

This is the mockup simulator what I mean. It simply composes the mockup data just by using typia.random<T>() function, which has an ability that analyzing the target type T and generate perfectly typed matched random data.

Of course, if you don't want random mockup data, but want to compose it by yourself, just do it.

@nestia/migrate

In actually, I had developed @nestia/migrate for migration from other languages (or other frameworks) to NestJS. As I'm a big fan of NestJS, hoping much more backend developers to use NestJS in their backend projects, I had built such conversion tool.

Also, I had developed another convenient tool that generating SDK and mockup simulator for frontend developers. By the way, I suddendly realized that they can be combined together. Although, those SDK generator and migration tool had been developed for different purposes, if they are combined together, they can be a perfect tool for frontend developers, despite origin purpose was for backend development.

So, I combined them together, and introduce it in here article. Frontend developers, stop using swagger-ui and msw. Instead, let's use @nestia/migrate command to build SDK. Then, your frontend development would be much more easier, faster, and safer.

I think @nestia/migrate can be a new solution for effective frontend development. Do you agree with me?

Top comments (12)

Collapse
 
fyodorio profile image
Fyodor

That’s a nice approach, and I (being a frontend dev) like the attitude very much 👍 There are some considerations though that I’d suggest to take into account, as I genuinely believe that there’s no “one size fits all” solution.

  1. Swagger UI is just a tool, one of many, which makes work with OpenAPI specification generated from BE code a bit more convenient. There’s a bunch of them. And there’s quite a few tools that allow generating frontends (framework-specific btw) from OAS in a flexible way.
  2. OAS is not just the way to “read BE”, at least in team work. It’s a living documentation and a contract, BE-FE interface so to speak, which allows to reason about different features without digging into implementation.
  3. OAS is a good industry standard. And it allows to build framework-agnostic tools on top of it. That’s probably a more universal approach, as it provides a way to work with APIs both in design-first and code-first way, either you use swagger, or stoplight, or whatnot.

I’m pretty sure none of that is a news for you, so it’s not to bring some controversy to the topic, rather an opinion in response to the suggestion in summary section. It’s not just that universal answer probably.

But I will definitely give it a try as a big fan of Nest.js myself 👍

Collapse
 
samchon profile image
Jeongho Nam

I agree with your insight and always try to follow OAS standards.

The reason for introducing this tool is that TypeScript's openapi-generator is immature, so many front-end developers read and write interaction code by themselves without automating SDK generation.

Of course, as @nestia/migrate is not matured yet, it could be another immature SDK generator for someone's insight \o/.

Collapse
 
andrei_stoian_dc4bd5ad372 profile image
Andrei Stoian

Have you tried swagger-typescript-api?

Collapse
 
beenotung profile image
Beeno Tung

You may try "npm init rpc", it's also generate client sdk based on server implementation. You don't need to specific the interface in multiple places with it.

Thread Thread
 
fyodorio profile image
Fyodor

That's interesting. Do you use it for something? What does it make under the hood actually? The package and repo look quite weird and obscure.

Thread Thread
 
beenotung profile image
Beeno Tung

I use the create-* package to generate the skeleton of backend server, and it generates the typescript client on the fly.
I can then use the client sdk in SPA (angular or react).
I know there are at least 10+ e-commerce projects based on this design (booking, office automation, e-shop, e.t.c.)

Collapse
 
maxim_mazurok profile image
Maxim Mazurok

Interesting stuff, you might want to check out Orval. It has sdk/dto generation as well as MSW generation with fakerjs.
I'll look more into this project, hopefully I'll be able to utilise it without doing a rewrite of backend from C# to JS.

Collapse
 
orlein profile image
orlein

Samchon's approach is very revolutionary. He deserves much more attention.

Collapse
 
jvmlet profile image
Furer Alexander

Revolutionary ?! Haven't you heard about open api tools generator project that exists for years and has really BIIIG community and adopters? And btw, bindings for so many languages and frameworks, not just ts/js ?

Collapse
 
samchon profile image
Jeongho Nam • Edited

My program is not revolutionary, but is not it the 1st that supporting mock up simulation? Also, I experienced openapi-generator, but it was not enough usable, especially restoring JSON schema to TypeScript definition.

Thread Thread
 
jvmlet profile image
Furer Alexander

1.Google for pact - you won't need another mockup tool.
2.We are generating ts for pretty complex open api schema without any issue

Thread Thread
 
samchon profile image
Jeongho Nam
  1. Thanks, I will study it
  2. I often utilize discriminated union type due to business logic reason, but openapi-generator be broken