Skip to content
NestJS ns websockets 4 min read

Gateways & Message Handlers

A gateway is the WebSocket equivalent of a controller: a class decorated with @WebSocketGateway() whose methods react to named events sent over a persistent socket connection. Instead of mapping HTTP verbs and routes, you map event names to handler methods with @SubscribeMessage, pull the payload out with @MessageBody, and reach the underlying socket with @ConnectedSocket. This page shows how to wire those decorators together to receive events and push responses back to one client or to every connected client.

Defining a gateway

A gateway is just an @Injectable-style class registered in a module’s providers array. The @WebSocketGateway decorator turns it into a socket endpoint and accepts the same options as the underlying platform (Socket.IO by default). Each event handler is an ordinary async method annotated with @SubscribeMessage('eventName').

import {
  WebSocketGateway,
  SubscribeMessage,
  MessageBody,
} from '@nestjs/websockets';

@WebSocketGateway({ cors: { origin: '*' } })
export class ChatGateway {
  @SubscribeMessage('ping')
  handlePing(@MessageBody() data: string): string {
    return `pong: ${data}`;
  }
}

Register it like any provider so Nest’s DI container can construct it:

import { Module } from '@nestjs/common';
import { ChatGateway } from './chat.gateway';

@Module({
  providers: [ChatGateway],
})
export class ChatModule {}

When a client emits ping with the value "hello", the value returned from the handler is sent back to that same client as an acknowledgement.

Output:

client → emit('ping', 'hello')
client ← ack: "pong: hello"

Reading the payload with @MessageBody

@MessageBody() injects the data the client sent with the event. Without arguments it gives you the whole payload; pass a property name to extract a single field. Combine it with a DTO interface to keep handlers type-safe.

import {
  WebSocketGateway,
  SubscribeMessage,
  MessageBody,
} from '@nestjs/websockets';

interface ChatMessage {
  room: string;
  text: string;
}

@WebSocketGateway()
export class ChatGateway {
  @SubscribeMessage('message')
  handleMessage(@MessageBody() body: ChatMessage): ChatMessage {
    return { room: body.room, text: body.text.trim() };
  }

  @SubscribeMessage('rename')
  handleRename(@MessageBody('name') name: string): void {
    console.log(`Renamed to ${name}`);
  }
}

Accessing the socket with @ConnectedSocket

To do anything beyond returning a value — joining rooms, reading the client id, emitting to others — you need the socket instance itself. @ConnectedSocket() injects the connected client. With Socket.IO that is a Socket; you can declare both decorators in the same handler.

import {
  WebSocketGateway,
  SubscribeMessage,
  MessageBody,
  ConnectedSocket,
} from '@nestjs/websockets';
import { Socket } from 'socket.io';

@WebSocketGateway()
export class ChatGateway {
  @SubscribeMessage('join')
  handleJoin(
    @MessageBody('room') room: string,
    @ConnectedSocket() client: Socket,
  ): void {
    client.join(room);
    client.emit('joined', { room, id: client.id });
  }
}

Here client.emit sends an event only to the socket that triggered the handler, while client.join(room) subscribes it to a broadcast group.

Returning vs emitting responses

There are two ways to reply, and they behave differently. Returning a value (or a Promise/Observable) sends an acknowledgement back to the requesting client only — ideal for request/response semantics. Calling emit on a socket or server lets you target the sender, everyone, or a subset.

TechniqueRecipientUse it for
return valueThe requesting client (as an ack)One-to-one responses
client.emit('evt', data)The requesting clientTargeted push, custom event name
client.broadcast.emit('evt', data)Everyone except the sender”User joined” style notices
server.emit('evt', data)Every connected clientGlobal announcements
server.to(room).emit('evt', data)Clients in a roomScoped fan-out

To emit to all clients you need the server instance, exposed with @WebSocketServer().

import {
  WebSocketGateway,
  SubscribeMessage,
  MessageBody,
  ConnectedSocket,
  WebSocketServer,
} from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';

@WebSocketGateway({ cors: { origin: '*' } })
export class ChatGateway {
  @WebSocketServer()
  server: Server;

  @SubscribeMessage('chat')
  handleChat(
    @MessageBody() body: { room: string; text: string },
    @ConnectedSocket() client: Socket,
  ): { delivered: boolean } {
    // Fan out to everyone in the room...
    this.server.to(body.room).emit('chat', {
      from: client.id,
      text: body.text,
    });
    // ...and acknowledge the sender via the return value.
    return { delivered: true };
  }
}

The return value becomes the Socket.IO acknowledgement callback argument on the client. If the client emits without an ack callback, the returned value is simply ignored — so use emit when you need a guaranteed push.

Streaming multiple responses

Because handlers can return an Observable, a single inbound event can produce many outbound messages. Nest subscribes to the stream and emits each value back under the same event name.

import { SubscribeMessage, WebSocketGateway } from '@nestjs/websockets';
import { from, Observable } from 'rxjs';
import { map } from 'rxjs/operators';

@WebSocketGateway()
export class FeedGateway {
  @SubscribeMessage('countdown')
  countdown(): Observable<{ event: string; data: number }> {
    return from([3, 2, 1]).pipe(
      map((n) => ({ event: 'tick', data: n })),
    );
  }
}

Output:

client ← tick 3
client ← tick 2
client ← tick 1

Best Practices

  • Treat gateways like controllers: keep them thin and delegate business logic to injected @Injectable services.
  • Return a value for request/response acknowledgements, and reserve emit for server-initiated pushes and broadcasts.
  • Type your payloads with interfaces or DTOs and validate them with pipes so handlers never trust raw socket input.
  • Inject @ConnectedSocket() only when you genuinely need the socket; otherwise keep handlers pure and easy to test.
  • Name events explicitly and consistently (message, chat, join) and document the payload shape each one expects.
  • Scope broadcasts deliberately — prefer server.to(room) over server.emit to avoid leaking messages to unrelated clients.
Last updated June 14, 2026
Was this helpful?