WebSocket Oriented API on Nest.js

In this article, I will tell you how I wrote a module for Nest.js that allows you to write a classic RESTFull API with swagger, but the client will be completely on WebSockets, sounds strange, doesn’t it? But it is very fast and convenient in the end)

The idea is that you write a classic documented api, with all the types and goodies.

  @Get(':id')
  @WsAction('user')
  getUser(@Param('id', ParseIntPipe) id: number): UserDto {
    return this.appService.getUser(id);
  }

Also, when setting up a module in nest.js, you can add socket validation so that everything is under authorization. Here is a small example from the project:

@Module({
    imports: [
      ConfigModule.forRoot(),
      WsModule.registerAsync({
        useFactory: (): IWsApiConfig => {
          return {
            // тут указывается та же валидация что была
            // использована для app.useGlobalPipes(new ValidationPipe(validationConfig));
            validationConfig,
            async validate(socket: Socket) {
              try {
                const authGuard = new (AuthGuard('jwt'))();
                const isAuth = await (authGuard.canActivate(new ExecutionContextHost([socket])) as Promise<boolean>);

                if (!isAuth) {
                  return HttpStatus.UNAUTHORIZED;
                }
              } catch (e) {
                return HttpStatus.UNAUTHORIZED;
              }
              return HttpStatus.OK;
            },
          };
        },
      }),
    ],
  })

Further on the client using the package swagger-typescript-api for example, automatically generate a full-fledged HTTP client under ts. In which all types and methods for http requests are described. But we only need types from it, however, you can also do a fallback in case of problems with sockets.

Plus, you can make the logic that with SSR make a request via HTTP and then already on WS.

export interface UserDto {
  id: number;
  firstName: string;
  email: string;
}

Move on)

What happens on the server:

  1. The application is assembled, when compiling the WsAction decorator, it collects all the information about the controller method, and puts it in its collection.

  2. Next, the application starts and the collected collection waits for a connection via client sockets, then subscribes to events whose names match the name of the method.

  3. The information with the config from the server is sent to the client, if necessary + connection status, when you can already pull the API.

  4. And all this walks on one channel, which increases the speed by ~ 10 times.

[Nest] LOG [InstanceLoader] AppModule dependencies initialized +0ms
[Nest] LOG [WsGateway] Add WS action: AppController => app:getUser
[Nest] LOG [WsGateway] Add WS action: BusinessController => business:createUser
[Nest] LOG [RoutesResolver] AppController {/api}: +2ms
[Nest] LOG [RouterExplorer] Mapped {/api/:id, GET} route +3ms
[Nest] LOG [RoutesResolver] BusinessController {/api/business}: +1ms
[Nest] LOG [RouterExplorer] Mapped {/api/business/create, POST} route +0ms
[Nest] LOG [NestApplication] Nest application successfully started +6ms

You can notice that actions were generated based on the namespace specified in the controller, and the name of the method to which the decorator was applied business:createUser and app:getUser

In fact, this is the name of the event that is sent from the client:

ws.emit('business:createUser', { payload });

I would have ended there, but asynchronous WS was not enough for me, I wanted everything to be synchronous, as everyone is used to:

await fetch('api/create-user', { payload })

To do this, I wrote a small client that promises asynchronous sockets and the result is:

const result = await webSocket.emit<ResultDto>('business:createUser', { payload });

I don’t want to write a lot of code here)

Module I published to npmturnip https://github.com/gustoase/nestjs-ws-api
Demo project to run poke on the knee https://github.com/gustoase/nestjs-ws-api-demo

How to start:

  1. npm i

  2. npm run start:dev

  3. cd client

  4. npm i

  5. npm rundev

  6. http://localhost:3001/

As a result, I want to say that a project at work that was successfully launched flies very quickly, there are no problems with it, there is authorization, validation too.
Also, due to timely notification from the server about the state of the API, you can show beautiful notifications)

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *