FORMA

WebSocket(网关)

WebSocket 通过 @WebSocketGateway 实现,常配合 Socket.IO。

在 NestJS 中,WebSocket 功能通过 网关(Gateway) 来实现。网关是一个使用 @WebSocketGateway() 装饰器标记的类,提供了与 HTTP 控制器(Controller)类似的、基于装饰器的 WebSocket 服务器开发体验,是目前构建实时服务的主流选择。得益于其良好的平台抽象,NestJS 的网关可以兼容 socket.io 和原生 ws 等多个 WebSocket 库。

核心构件

1. @WebSocketGateway() 装饰器

用于将一个类标记为 WebSocket 网关。它可以接收一个配置对象来指定端口、命名空间和跨域资源共享(CORS)等选项。从功能上看,它类似 HTTP 中的 @Controller(),但与之并列,是与 HTTP 层平行的 WebSocket 独立网关,通常不建议通过注入 HttpService 等方式将其与 HTTP 处理器组在一起工作。

typescript
import {
  WebSocketGateway,
  WebSocketServer,
  SubscribeMessage,
  OnGatewayInit,
  OnGatewayConnection,
  OnGatewayDisconnect,
} from "@nestjs/websockets";
import { Server, Socket } from "socket.io";

@WebSocketGateway({
  cors: { origin: "*" }, // 允许跨域(开发环境),生产环境需细化
  namespace: "chat", // 可选命名空间
  // port: 3001,         // 不指定则使用 HTTP 服务器端口
})
export class ChatGateway implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect {
  // ... 实现生命周期方法
}

常见的配置选项

  • port:指定 WebSocket 服务器的端口。默认为与 HTTP 服务器相同的端口(例如 3000)。如果显式指定使用 port: 3001,但同端口 3001 已被其他服务占用,就会导致冲突并报错。故额外指定端口时,需确保其空闲。
  • namespace:命名空间用于将连接分组。默认根命名空间是 /(空字符串)。通过 namespace: 'chat' 可以让客户端连接到 ws://localhost/chathttps://domain/chat(Socket.IO)。
  • cors:跨域资源共享配置,对于 WebSocket 环境尤为重要。
    • Socket.IO 下直接在 @WebSocketGateway() 中配置 cors 对象。
    • 使用原生 ws 库时,CORS 通常由上层 HTTP 服务器通过 app.enableCors() 统一处理(需格外留意中间件的顺序)。

2. @WebSocketServer() 装饰器

用于获取底层的 WebSocket 服务器实例,以便向所有连接到该网关的客户端广播消息。

typescript
@WebSocketServer()
server: Server;  // 对于 socket.io,类型为 Server;对于 ws 库,可能是 WebSocketServer

获取到 server 实例后,即可调用 emit / send 等方法主动推送消息。

3. @SubscribeMessage() 装饰器

用于监听客户端发送的特定事件,是一个被动的消息处理器,需要等待客户端触发事件。

typescript
@SubscribeMessage('message')
handleMessage(client: Socket, payload: any): void {
  console.log(`Received message: ${payload.text}`);
  // 直接返回数据会向当前客户端发送一个以方法名为事件的响应
  // 注意:如果返回 `WsResponse`,其 `event` 字段默认与方法名相同;如不特殊关注响应事件名称,可保持简单返回对象。
  return { event: 'message', data: 'Message received!' };
}

@SubscribeMessage 支持复杂的返回场景

  • 直接返回一个对象:将对象作为 data,并自动使用 'message' 作为响应事件名。
  • 完全手动控制:返回 Observable
  • 发送到不同客户端:既不依赖 @WebSocketServer 也不返回数据,可以注入 @WebSocketServer() server: Server 并通过 server.to(room).emit(...) 任意控制消息的分发。
  • 你还可以在网关中注入自定义服务,并与数据库、队列、业务逻辑等进行更深度的集成。

4. 生命周期钩子

通过实现相应接口,可以监控 WebSocket 连接的生命周期事件,适用于鉴权、清理资源等场景:

接口方法触发时机
OnGatewayInitafterInit(server: Server)WebSocket 服务器初始化后
OnGatewayConnectionhandleConnection(client: Socket, ...args: any[])新客户端连接时
OnGatewayDisconnecthandleDisconnect(client: Socket)客户端断开连接时

📦 项目集成

第一步:安装依赖

如果要使用 socket.io

bash
npm install @nestjs/websockets @nestjs/platform-socket.io socket.io

如果要使用原生库 ws,可安装:

bash
npm install @nestjs/websockets @nestjs/platform-ws ws

第二步:定义网关

将核心逻辑写在网关类里。一般情况下,网关自身也是 @Injectable() 的,可依赖注入其他服务。 注意:通常情况下不需要在模块的 providers 中重复注册网关;默认情况下,NestJS 的 WebSocketsModule 会自动识别并实例化使用 @WebSocketGateway() 装饰器的类(该行为适用于全局模式)。但如果要对网关进行显式的模块管理,依然推荐在某些模块的 providers: [ChatGateway] 里注册,这能确保在被其他模块引用时,网关实例是受控的。

第三步:注册模块

@nestjs/websockets 并不导出 WebSocketsModule;网关本质上只是一个普通的 @Injectable() 类,只需像其他 Provider 一样注册到某个模块的 providers 中即可,NestJS 会自动识别其 @WebSocketGateway() 元数据并接管连接:

typescript
// app.module.ts(或对应的功能模块)
import { Module } from "@nestjs/common";
import { ChatGateway } from "./chat.gateway";

@Module({
  providers: [ChatGateway], // 直接注册即可,无需额外导入某个 "WebSocket 模块"
})
export class AppModule {}

如果希望网关只属于某个特定功能模块(不在全局范围共享实例),将其 providers 声明在该功能模块中即可,无需其他配置。

🧩 适配器(Adapter)选择

NestJS 的 WebSocketAdapter 接口抽象了不同 WebSocket 库之间的差异,让网关代码可以平滑切换底层实现。

  • Socket.IO 适配器@nestjs/platform-socket.io): 提供更丰富的功能,如自动降级(轮询+xhr)、房间(Rooms)支持、命名空间(Namespaces)和中间件。官方示例文档详尽,适合大部分实时通信场景。
  • 原生 WebSocket(ws)适配器@nestjs/platform-ws): 原生库 ws 更为轻量,性能更高,适合对传输体积和原始吞吐量极其敏感的场景。不提供自动降级和房间等内置高级特性,需要手动实现分组逻辑。两种适配器都支持使用 app.useWebSocketAdapter(new WsAdapter(app)) 来指定。

在实际项目里,@nestjs/websockets 是所有 WebSocket 功能的基础包,而 @nestjs/platform-socket.io 是 socket.io 的专属适配器实现,@nestjs/platform-ws 则是 ws 的专属适配器。

️ 安全认证(Authentication)

WebSocket 认证主要通过在连接建立阶段验证 Token 来实现,例如提取 Socket.IO 握手时的查询参数或请求头。

Socket.IO 示例

typescript
import { WebSocketGateway, OnGatewayConnection } from "@nestjs/websockets";
import { Socket } from "socket.io";

@WebSocketGateway()
export class AuthGateway implements OnGatewayConnection {
  handleConnection(client: Socket) {
    const token = client.handshake.auth.token;
    // 验证 token,若无效则断开连接
    if (!token || !this.validateToken(token)) {
      client.disconnect();
      return;
    }
    // 认证通过后可执行额外逻辑
  }
}

原生 ws 示例: 原生 ws 适配器下,可通过 client.upgradeReq(或 Node.js 的 IncomingMessage)获取 headers 中的 authorization 字段。所以在实际集成时,建议优先考虑使用官方推荐的 @nestjs/platform-ws,而不要直接依赖 ws 的原始回调。

对于生产环境,建议将认证逻辑独立为一个**守卫(Guard)**或中间件,以提高代码的可维护性。

📈 高级技巧与最佳实践

房间(Rooms)管理

对于聊天室这类多房间的场景,可以利用 socket.join() / socket.leave() 以及 server.to(room).emit()

typescript
@SubscribeMessage('joinRoom')
handleJoinRoom(client: Socket, roomName: string) {
  client.join(roomName);
  client.emit('joinedRoom', roomName);
}

@SubscribeMessage('messageToRoom')
handleMessageToRoom(client: Socket, payload: { room: string; message: string }) {
  this.server.to(payload.room).emit('roomMessage', payload.message);
}

网关设计与可测试性

推荐将网关仅作为轻量级消息路由层,具体的业务逻辑(如消息持久化、推送通知等)应委托给独立的服务(Service)处理。这样可以保持网关代码的简洁,并使其更易于单元测试。

typescript
// 单一职责的网关示例:只负责收发,业务逻辑交给 service
constructor(private messageService: MessageService) {}

@SubscribeMessage('message')
async handleMessage(client: Socket, payload: any) {
  const response = await this.messageService.processMessage(payload);
  // 返回给当前客户端或广播
  return response;
}

部署与性能

  • 扩展性:Socket.IO 和 WebSocket 都支持使用 Redis Adapter 进行水平扩展,允许多个 Node.js 进程之间共享连接信息和广播消息。基本思路是先安装 socket.io-redis 包,再通过 IoAdapter 启用 Redis 适配器。官方文档提供了现成的集成步骤。
  • 负载均衡:如使用 Nginx 或 HAProxy,务必启用 proxy_pass 并配置负载均衡策略(例如基于 ip_hashsticky session),确保同一客户端始终路由到同一后端服务,避免长连接中断或认证失败。在实际生产环境里,还需要额外验证 WebSocket 连接升级(Upgrade)头是否被防火墙或代理正确转发。

总结

概念核心作用
@WebSocketGateway()声明一个类为 WebSocket 网关,并定义端口、命名空间、CORS 等
@WebSocketServer()获取底层服务器实例,用于主动广播消息
@SubscribeMessage()定义消息监听器,处理客户端发送的特定事件
生命周期钩子监听连接、断开、初始化等事件,处理认证/资源清理
适配器(Adapter)抽象底层 WebSocket 库(Socket.IO / ws),灵活切换
认证与授权handleConnection 或自定义守卫中验证客户端身份
房间与命名空间逻辑隔离,实现群组通信等功能

对于绝大多数实时通信场景,Socket.IO 提供了最丰富的开箱即用功能,开发效率很高。若对性能和包体积有极致要求,可考虑 原生 ws进行定制开发。官方文档的示例都比较简略,建议结合官方教程和社区实战示例来进一步完善鉴权,也可多阅读 NestJS 的 WebSocket 官方文档 来获得更全面的配置信息。

参考文献

以下链接在编写时均可正常访问:

资料说明
NestJS 文档官方
Gateways本章主题
Request lifecycle执行顺序

Series

new

19 / 19