websockets overview
Websockets enable you to create a permanent tether between your client applications and your backend server. They are useful for broadcasting real-time updates between your backend, and are almost ubiquitous amongst modern web technology stacks.
One of the challenges when building custom websocket systems is scaling. As you multiply the number of web servers you have and tuck them behind a load balancer, there is no guarantee that a user will stay connected to one or another server. In some cases, they may be connected to your systems on multiple devices as well, further complicating the issue.
Psychic solves this complexity for you by providing you with two key features:
-
A distributed websocket system, built on top of redis's pub-sub mechanisms, which enables you to emit to all servers at once, enabling you to target a message to a specific user without having to worry about detecting which web server they are connected to.
-
The
Wsclass, a simple class to use for registering users and emitting to them from anywhere in your stack. This class abstracts away the complexity of connecting to redis adapters, making it easy to set up clean patterns for both registering and emitting to users.
To utilize the Ws class to your advantage, you first need to leverage the .register method when websocket connections. This is done within the conf/initializers/websockets.ts file, which create-psychic scaffolds for you when you opt into websockets:
// conf/initializers/websockets.ts
import resolveWebsocketUser from '@conf/system/resolveWebsocketUser.js'
function initializeWebsockets(wsApp: PsychicAppWebsockets) {
wsApp.on('ws:start', io => {
io.of('/').on('connection', async socket => {
const user = await resolveWebsocketUser(socket)
if (!user) {
socket.disconnect(true)
return
}
await Ws.register(socket, user.id)
})
})
}
resolveWebsocketUser is itself scaffolded boilerplate — a newly-generated app rejects every websocket connection until you replace its body with real auth logic. See Registering and Config for the full pattern, including how the front end hands back the auth token that resolveWebsocketUser resolves.
Once this is done, you are able to emit from anywhere in your application to this user using the emit method.
ws = new Ws(['/users/ping', 'users/alert', 'users/info'] as const)
await ws.emit(user.id, '/users/ping', { hello: 'world' })
To learn more about emitting to your users, see the Emitting guides