使用 Socket.IO 适配器构建 WebSocket 网关
实现 @WebSocketGateway 处理程序、房间和生命周期钩子,以支持实时通信。
使用 Socket.IO 适配器构建 WebSocket 网关 是 CoddyKit 上的免费 NestJS Enterprise Backend APIs 课时。 这是第 1 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 NestJS Enterprise Backend APIs 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 NestJS Enterprise Backend APIs 课程共包含 4 节课。
本课时的部分内容尚未翻译,以英文显示。
Why Gateways Exist
REST handles request/response, but real-time features (chat, presence, live dashboards) need a persistent, bidirectional channel. NestJS wraps this with a WebSocket Gateway.
- A gateway is a provider decorated with
@WebSocketGateway(). - By default Nest uses the Socket.IO platform adapter (
@nestjs/platform-socket.io). - Gateways are normal Nest classes: they support dependency injection, guards, pipes, and interceptors just like controllers.
Install the deps with npm i @nestjs/websockets @nestjs/platform-socket.io socket.io.
Declaring a Gateway
Decorate a class with @WebSocketGateway() and register it as a provider in a module. You can pass a port and options such as namespace and CORS config.
@SubscribeMessage('event')binds a handler to an inbound Socket.IO event.- The return value (or a
WsResponse) is emitted back to the calling client.
Below, clients sending ping receive a pong reply.
import { WebSocketGateway, SubscribeMessage, MessageBody } from '@nestjs/websockets';
@WebSocketGateway({ namespace: '/chat', cors: { origin: '*' } })
export class ChatGateway {
@SubscribeMessage('ping')
handlePing(@MessageBody() data: string): { event: string; data: string } {
return { event: 'pong', data: `received: ${data}` };
}
}Accessing the Server Instance
To broadcast to many clients you need the underlying Socket.IO Server. Inject it with the @WebSocketServer() property decorator.
server.emit('event', payload)sends to every connected client in the namespace.- This is the foundation for fan-out features like global announcements.
import { WebSocketGateway, WebSocketServer, SubscribeMessage, MessageBody } from '@nestjs/websockets';
import { Server } from 'socket.io';
@WebSocketGateway({ namespace: '/chat' })
export class ChatGateway {
@WebSocketServer()
server: Server;
@SubscribeMessage('broadcast')
handleBroadcast(@MessageBody() message: string): void {
this.server.emit('announcement', message);
}
}The Connected Socket
Each client has its own Socket. Inject it per-handler with @ConnectedSocket() to read auth data, the socket id, or to reply only to that client.
client.emit(...)targets just the sender.client.iduniquely identifies the connection.client.handshakeexposes headers, query, and auth handed in at connect time.
import { WebSocketGateway, SubscribeMessage, MessageBody, ConnectedSocket } from '@nestjs/websockets';
import { Socket } from 'socket.io';
@WebSocketGateway({ namespace: '/chat' })
export class ChatGateway {
@SubscribeMessage('whoami')
whoAmI(@ConnectedSocket() client: Socket, @MessageBody() _: unknown): void {
client.emit('identity', { id: client.id, token: client.handshake.auth?.token });
}
}Lifecycle Hooks
Gateways can implement lifecycle interfaces to react to connection events:
OnGatewayInit→afterInit(server)runs once the server is ready.OnGatewayConnection→handleConnection(client)fires per new client.OnGatewayDisconnect→handleDisconnect(client)fires when a client leaves.
Use these for presence tracking, auth on connect, and cleanup.
import { WebSocketGateway, OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect } from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';
import { Logger } from '@nestjs/common';
@WebSocketGateway({ namespace: '/chat' })
export class ChatGateway implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect {
private readonly logger = new Logger(ChatGateway.name);
afterInit(server: Server): void {
this.logger.log('Gateway initialized');
}
handleConnection(client: Socket): void {
this.logger.log(`Client connected: ${client.id}`);
}
handleDisconnect(client: Socket): void {
this.logger.log(`Client disconnected: ${client.id}`);
}
}Rooms: Grouping Sockets
A room is a server-side label you attach to sockets so you can broadcast to a subset. A socket can belong to many rooms.
client.join('room')adds the socket to a room.client.leave('room')removes it.- Socket.IO automatically puts each socket in a room named after its own
client.id.
Rooms are how you build per-channel chat, per-tenant dashboards, or per-document collaboration.
import { WebSocketGateway, SubscribeMessage, MessageBody, ConnectedSocket } from '@nestjs/websockets';
import { Socket } from 'socket.io';
@WebSocketGateway({ namespace: '/chat' })
export class ChatGateway {
@SubscribeMessage('joinRoom')
joinRoom(@ConnectedSocket() client: Socket, @MessageBody() room: string): void {
client.join(room);
client.emit('joined', room);
}
}Broadcasting to a Room
Target a room with server.to('room').emit(...). To exclude the sender, emit from the client socket: client.to('room').emit(...) sends to everyone in the room except that client.
server.to(room).emit(...)→ everyone in the room.client.to(room).emit(...)→ everyone in the room but the sender.
import { WebSocketGateway, WebSocketServer, SubscribeMessage, MessageBody, ConnectedSocket } from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';
@WebSocketGateway({ namespace: '/chat' })
export class ChatGateway {
@WebSocketServer() server: Server;
@SubscribeMessage('sendToRoom')
sendToRoom(
@ConnectedSocket() client: Socket,
@MessageBody() body: { room: string; text: string },
): void {
client.to(body.room).emit('message', { from: client.id, text: body.text });
}
}Acknowledgements with WsResponse
Socket.IO supports request/response style acknowledgements. In Nest you can return a value, a WsResponse<T> object, or even an Observable that streams multiple emissions back.
- Return
{ event, data }to emit a named event back to the caller. - Returning an
Observableemits one message per value, useful for progress updates.
import { WebSocketGateway, SubscribeMessage, MessageBody, WsResponse } from '@nestjs/websockets';
import { from, Observable } from 'rxjs';
import { map } from 'rxjs/operators';
@WebSocketGateway({ namespace: '/jobs' })
export class JobsGateway {
@SubscribeMessage('countdown')
countdown(@MessageBody() n: number): Observable<WsResponse<number>> {
const ticks = Array.from({ length: n }, (_, i) => n - i);
return from(ticks).pipe(map((value) => ({ event: 'tick', data: value })));
}
}Validation with Pipes
Gateways reuse Nest's ValidationPipe, but errors are thrown as WsException rather than HTTP exceptions. Apply the pipe at the handler or gateway level and validate a DTO via @MessageBody().
- Set
transform: trueso plain payloads become class instances. - A
WsExceptionis serialized to anexceptionevent on the client.
import { WebSocketGateway, SubscribeMessage, MessageBody } from '@nestjs/websockets';
import { UsePipes, ValidationPipe } from '@nestjs/common';
import { IsString, MinLength } from 'class-validator';
class SendMessageDto {
@IsString() @MinLength(1)
text: string;
}
@WebSocketGateway({ namespace: '/chat' })
export class ChatGateway {
@UsePipes(new ValidationPipe({ transform: true }))
@SubscribeMessage('send')
send(@MessageBody() dto: SendMessageDto): { event: string; data: string } {
return { event: 'sent', data: dto.text };
}
}Authenticating on Connect
Authenticate clients during handleConnection by reading the token from the handshake and disconnecting unauthorized sockets early. Storing the user on client.data makes it available to every later handler.
- Token usually arrives via
handshake.auth.tokenor anAuthorizationheader. - Call
client.disconnect()to reject bad clients before they join rooms.
import { WebSocketGateway, OnGatewayConnection } from '@nestjs/websockets';
import { Socket } from 'socket.io';
import { JwtService } from '@nestjs/jwt';
@WebSocketGateway({ namespace: '/chat' })
export class ChatGateway implements OnGatewayConnection {
constructor(private readonly jwt: JwtService) {}
async handleConnection(client: Socket): Promise<void> {
try {
const token = client.handshake.auth?.token as string;
client.data.user = await this.jwt.verifyAsync(token);
} catch {
client.emit('error', 'Unauthorized');
client.disconnect();
}
}
}Swapping the Adapter for Scale
Out of the box, Socket.IO state lives in a single process. To run multiple Nest instances behind a load balancer you must share room/emit state. Use a custom adapter with the Redis adapter so broadcasts reach clients on other nodes.
- Subclass
IoAdapterand attach@socket.io/redis-adapterincreateIOServer. - Register it via
app.useWebSocketAdapter(new RedisIoAdapter(app))inmain.ts.
import { IoAdapter } from '@nestjs/platform-socket.io';
import { ServerOptions } from 'socket.io';
import { createAdapter } from '@socket.io/redis-adapter';
import { createClient } from 'redis';
export class RedisIoAdapter extends IoAdapter {
private adapterConstructor: ReturnType<typeof createAdapter>;
async connectToRedis(): Promise<void> {
const pub = createClient({ url: 'redis://localhost:6379' });
const sub = pub.duplicate();
await Promise.all([pub.connect(), sub.connect()]);
this.adapterConstructor = createAdapter(pub, sub);
}
createIOServer(port: number, options?: ServerOptions): any {
const server = super.createIOServer(port, options);
server.adapter(this.adapterConstructor);
return server;
}
}Quick Check
Test your understanding of room broadcasting semantics.
Recap
You built a real-time layer with NestJS WebSocket gateways on the Socket.IO adapter:
- Gateway basics:
@WebSocketGateway()providers with@SubscribeMessagehandlers; inject the server via@WebSocketServer()and the per-client socket via@ConnectedSocket(). - Lifecycle hooks:
afterInit,handleConnection, andhandleDisconnectfor init, presence, and cleanup. - Rooms:
client.join/leave, thenserver.to(room)for everyone orclient.to(room)to exclude the sender. - Robustness: validate payloads with
ValidationPipe(errors becomeWsException), authenticate inhandleConnection, and use acknowledgements/Observables for replies. - Scale: swap in a Redis-backed
IoAdapterso broadcasts work across multiple instances.
常见问题解答
「使用 Socket.IO 适配器构建 WebSocket 网关」课时是免费的吗?
是的 — 「使用 Socket.IO 适配器构建 WebSocket 网关」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 NestJS Enterprise Backend APIs 课程的其余内容,请升级到 CoddyKit PRO。 NestJS Enterprise Backend APIs 课程共包含 4 节课。
「使用 Socket.IO 适配器构建 WebSocket 网关」这节课中我会学到什么?
实现 @WebSocketGateway 处理程序、房间和生命周期钩子,以支持实时通信。 你通过在浏览器中直接运行的动手代码来练习 NestJS Enterprise Backend APIs,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 NestJS Enterprise Backend APIs 需要有经验吗?
无需任何先前经验。CoddyKit 上的 NestJS Enterprise Backend APIs 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 1 节课,共 4 节。
「使用 Socket.IO 适配器构建 WebSocket 网关」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 NestJS Enterprise Backend APIs 课中编写并运行代码吗?
能。每节 NestJS Enterprise Backend APIs 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- 使用 Socket.IO 适配器构建 WebSocket 网关
- 验证并保护 Socket 连接
- 用于单向推送的服务器发送事件
- 使用 Redis 发布/订阅适配器扩展实时能力