WebSocket Gateways — thin transport, not business logic
The gateway is transport, not business logic. It receives a message, authorizes, hands off to a service.
nestjs-realtime-transport-thin-gateway
Why it matters
| Stake | If ignored |
|---|---|
| Hard to test |
|
How to fix
In NestJS, a WebSocket gateway has one responsibility. It does not share state with controllers, and business logic does not live inside it.
Examples
ts
@WebSocketGateway()
export class CollaborationGateway {
private documents = new Map<string, Document>(); // ← state in gateway
@SubscribeMessage('edit')
handleEdit(@MessageBody() data: EditMessage) {
const doc = this.documents.get(data.docId);
doc.applyEdit(data.edit);
// no way to test without opening a socket
}
}ts
@Injectable()
export class CollaborationService {
async applyEdit(docId: string, edit: Edit, userId: string): Promise<EditResult> {
const doc = await this.repo.findById(docId);
if (!this.policy.canEdit(user, doc)) throw new ForbiddenException();
return doc.apply(edit);
}
}
@WebSocketGateway()
export class CollaborationGateway {
constructor(private collab: CollaborationService) {}
@UseGuards(WsAuthGuard)
@SubscribeMessage('edit')
async handleEdit(@MessageBody() data: EditDto, @ConnectedSocket() client: AuthedSocket) {
const result = await this.collab.applyEdit(data.docId, data.edit, client.userId);
this.server.to(data.docId).emit('edit-applied', result);
}
}