Your first gateway
Open the cats API to socket.io clients: a gateway on the app's own port, message handlers that acknowledge with their return value, a property picked out of the payload, and an event sent back to the sender instead of an acknowledgement.
What you'll learn
- Explain what a WebSocket connection adds to HTTP, and what socket.io adds to WebSockets: events, acknowledgements, rooms
- Turn a provider into a gateway with @WebSocketGateway(), on the HTTP server's port, registered in a module's providers
- Handle events with @SubscribeMessage() and read the payload, or one of its properties, with @MessageBody()
- Tell the two ways a handler answers apart: a return value is the acknowledgement, a { event, data } object is an event to the sender, and null or undefined sends nothing at all
The shelter's front desk wants a screen that shows new arrivals the moment they are registered, and volunteers want their phones to buzz when a cat they like is adopted. Over HTTP the client has to ask: every few seconds, "anything new?", which is wasteful when nothing changed and late when something did. A WebSocket is a connection that stays open after the first request, so the server can send whenever it has something to say, and the client can send many messages without a new request each time.
Nest builds on socket.io, the library its WebSocket chapter uses by default (@nestjs/platform-socket.io). socket.io adds what a bare WebSocket lacks: messages are named events with a payload, a client can ask for an acknowledgement (an answer to that one message), and the server can send to one client, to everyone, or to a group. This course connects the cats API to socket.io, starting with a gateway that answers questions.
A gateway
A gateway is a provider marked with @WebSocketGateway(). Like any provider it injects services through its constructor, and Nest creates it only when it is listed in a module's providers. With no arguments it serves socket.io on the HTTP server's own port: the app's routes and its sockets share port 3000.
@WebSocketGateway()
export class KennelsGateway {
constructor(private readonly kennels: KennelsService) {}
@SubscribeMessage('kennels:count')
count(): number {
return this.kennels.findAll().length;
}
}
@SubscribeMessage('kennels:count') routes every kennels:count event to that method, the way @Get() routes a request. Event names are free text; resource:action keeps them readable as a gateway grows. At boot Nest logs each one: [WebSocketsController] KennelsGateway subscribed to the "kennels:count" message.
The payload
The client sends a payload with the event: a string, a number, an object. @MessageBody() gives the handler all of it, and @MessageBody('kennel') picks one property out of an object payload, as @Body('name') does for a request:
@SubscribeMessage('kennels:find')
find(@MessageBody('kennel') kennel: string): Kennel | { error: string } {
return this.kennels.findOne(kennel) ?? { error: `No kennel ${kennel}` };
}
Two ways to answer
What a handler returns is sent back in one of two ways.
A plain value is the acknowledgement. On the client, a callback as the last argument of emit() receives it, or emitWithAck() resolves with it:
const socket = io('http://localhost:3000');
const kennel = await socket.emitWithAck('kennels:find', { kennel: 'A' });
An object of the shape { event, data }, typed WsResponse in @nestjs/websockets, is sent instead as an event named event, to the client that sent the message. Use it when the answer is something the client listens for anyway, or when an answer may come without being asked for. It is not an acknowledgement: a client waiting with emitWithAck() waits in vain.
One return value sends nothing at all: null or undefined. Nest drops nil replies, so a lookup that returns undefined for a missing record leaves the client waiting for an acknowledgement that never comes. Answer a miss with something, an error object here, or the exception the filters lesson shows.
Your task
CatsService holds Tom and Luna and can find, list and create cats. Open it to socket.io clients.
- Make
CatsGatewaya gateway on the app's port, and register it where Nest will create it. cats:listacknowledges every cat.cats:findreceives{ id }and acknowledges that cat, or{ error: 'Cat 9 not found' }for a cat the shelter does not have.cats:addreceives{ name, age }, stores the cat, and acknowledges it with its new id.cats:greetreceives a name and answers the sender with acats:greetingevent:Hello, Ada! 3 cats are waiting.
Run the tests, then open the Endpoints tab's Socket mode: connect a client, emit cats:find with { "id": 1 }, and read the acknowledgement and the frame log.
When it fails
ada could not connect: xhr poll error (connect ECONNREFUSED 127.0.0.1:3000): nothing serves socket.io on the port. The class has no@WebSocketGateway(), or it is not in any module'sproviders, so Nest never created it. (In a browser the error is socket.io-client'sxhr poll errortoo; the reason after it is the engine's.)no acknowledgement for "cats:find" within 1s: the handler returnedundefinedornull, or returned{ event, data }, which is an event, not an acknowledgement.acknowledged with {"error":"Cat [object Object] not found"}: the handler has no@MessageBody, so its first parameter received the socket itself. Without decorators a handler is called with(client, payload).- The greeting arrives as an acknowledgement: the handler returned the string instead of
{ event: 'cats:greeting', data }.
Remember
- A gateway is a provider with
@WebSocketGateway(), on the HTTP port unless told otherwise, listed inproviders. @SubscribeMessage('event')routes an event;@MessageBody()and@MessageBody('key')read its payload.- A return value is the acknowledgement;
{ event, data }is an event to the sender;nullandundefinedsend nothing.
Stuck? Show a hint
cats.gateway.ts: @WebSocketGateway() on the class; @SubscribeMessage('cats:list') returning findAll(); @SubscribeMessage('cats:find') with @MessageBody('id') id: number, returning the cat ?? { error: `Cat ${id} not found` }; @SubscribeMessage('cats:add') with @MessageBody() returning create(); @SubscribeMessage('cats:greet') returning { event: 'cats:greeting', data: `Hello, ${name}! ...` }. cats.module.ts: CatsGateway in providers.