- Topics
- /
- Protocols
- &Design patterns
- /
- What you need to know about WebSockets and Pub/Sub
What you need to know about WebSockets and Pub/Sub
WebSockets and Pub/Sub are often mentioned in the same sentence so they must be related in some way. But what is their relationship, exactly?
Some people ask how they compare, but it’s not really about WebSockets vs. pub/sub.
WebSockets is a transport to shuttle messages from one web browser to another. Pub/Sub, on the other hand, is a design pattern that describes a logical way to structure your message-routing code so that it’s clear, maintainable, and flexible.
Not to downplay the power of WebSockets, but a transport only does one thing, which is send and receive messages between two apps.
There’s always more work to do on top to handle and process messages in your own code. Rather than coming up with the structure from scratch (maybe learning along the way), Pub/Sub provides a well-thought-out pattern you can repeat.
That might all sound a bit theoretical - so let’s unpack the relationship between WebSockets and Pub/Sub and explore some examples along the way.
What are WebSockets?
WebSockets are a communication protocol that enables realtime communication between a client (usually a web browser) and a server.
To accomplish this, they establish a persistent connection between the client and server, allowing them to exchange data in both directions without the need for explicit requests and responses.
Once the connection is established, the server or client can send messages at any time, and the other party receives them immediately, making them ideal for apps where realtime updates are required, such as a chat, multiplayer collaboration, realtime tracking, or a stock ticker.
We’ve written extensively about WebSockets - how they work, their pros and cons, and use cases - so if you have any unanswered questions about the fundamentals of WebSockets, you’ll find the answer in one of those links!
Here, we’ll focus on WebSockets as they pertain to our discussion about Pub/Sub.
As we’ve mentioned, WebSockets does one thing and does one thing really well - provide a transport to get messages from one web browser to another in realtime.
Under the hood, WebSockets handle a lot of the complexity you don’t want to think about, like negotiating the connection and handling framing. This makes WebSockets one of the most powerful and versatile transports in the browser.
Nevertheless, they are a basic building block that requires a layer on top so your server can accept messages and route them through your system (maybe doing some processing in between).
To give you an idea about where WebSockets ends and your own messaging layer begins, here’s a minimal example WebSocket server implementation in JavaScript. Take a moment to study the code and the comments carefully. Afterwards, we have a challenge for you!
// Start the server
const server = http.createServer()
const wsServer = new WebSocketServer({ server })
server.listen(8000, () => {
console.log(`WebSocket server is running on port ${port}`)
})
const connections = {};
// A new client connection request received
wsServer.on('connection', connection => {
// Generate a unique code for every user
const userId = uuidv4();
console.log(`Recieved a new connection.`)
// Store the new connection and handle messages
connections[userId] = connection
console.log(`${userId} connected.`)
})
Above, we initiate a WebSockets server and hook up the necessary event handlers. Apart from the essential WebSocket code, we also introduce a simplistic client object to keep track of all the connected clients.
Ah, yes - the challenge!
The example above is fairly straightforward and totally functional, yet - you probably agree - awfully contrived.
So the challenge is to consider the following real-world scenarios and assess how you might adapt the server code above to facilitate them:
Broadcast scenario: How would you send a message to every connected client?
Multicast scenario: How could you send a message to a subset of clients? For example, members of the #welcome channel in a chat app OR clients only wanting to receive specific updates
Unicast scenario: How can you facilitate sending a message to a specific client? Maybe based on a unique client ID or based on context like the participants of a one-on-one conversation?
In practice, there are many ways to handle this. However, rather than reinvent the wheel, it might be better to repeat a tried-and-true pattern to guarantee your code remains clean, clear, maintainable, and flexible!
How about Pub/Sub?
What is Pub/Sub?
Publish-subscribe, commonly known as Pub/Sub, is a design pattern that enables publishers and subscribers to communicate with one another by sending and receiving messages through a message broker.
In this arrangement, publishers publish messages with a topic to the broker. Subscribers can then subscribe to the topics they’re interested in - again - through the broker.
For example, in a crypto stock ticker-type app, the publisher might periodically publish a message with the topic btc-price
, and another with the topic eth-price
.
Subscribers can subscribe to one topic, or both (or none!), depending on which they’re interested in (if any) and will receive all messages published to the topics to which they subscribe. In any case, neither the subscriber or publisher are aware the other exists - everything goes through the broker.
To further illustrate the point, suppose for a moment you’re building a community chat application to rival Discord.
To facilitate one-on-one messaging, both participants would publish and subscribe to a topic with a unique name, possibly derived from their combined usernames - for example, userA-userB
.
When it comes to channels, anyone participating in in the #welcome channel, for example, would chatter by publishing and subscribing to the welcome-channel
topic under the hood.
As you can see, by publishing messages to named logical channels, it becomes clear from a high level how to send messages to a specific user (unicast), group of users (multicast), or every user (broadcast).
Remember, though, Pub/Sub is a pattern - an abstract solution to a commonly occurring problem, a blueprint, nothing specific! When you repeat the pattern, all the details like what transport to use are up to you.
What is the difference between WebSockets and Pub/Sub?
If you started this post wondering about WebSockets vs. Pub/Sub, it turns out they’re not at odds at all. Rather, they actually complement each other well, hence it’s common to see them mentioned together (even though they are different).
So, what are the differences?
In summary, WebSockets are fundamentally a transport to get data from A to B (ordinarily, two web browsers but WebSockets can work on mobile too).
On the other hand, Pub/Sub is a design pattern that describes a tried-and-true code pattern you can repeat in your messaging layer so that messages can reach their destination without the risk of you introducing tight coupling or other code smells into your codebase.
By following a pattern, you can work more quickly while ensuring your messaging code is plenty flexible to support your evolving requirements.
You can use WebSockets without Pub/Sub or with another design pattern. Similarly, Pub/Sub is frequently used without WebSockets. They’re not intrinsically linked in any way - they just go well together.
How to apply Pub/Sub with WebSockets
First of all, it’s worth mentioning that it’s not massively common to implement your own WebSockets messaging layer at all.
Earlier, when we described the stuff you have to do “on top” of WebSockets - well, that was just the tip of the iceberg.
Some other things you need to build out are authentication, authorization, reconnection logic, a way to resume missed messages, compression, multiplexing, and - while not strictly necessary - it is a good practice to have some kind of fallback in case the WebSocket connection is blocked by a firewall.
Add that all up, and you’re talking about a considerable amount of work that isn’t really unique to what you’re building.
It’s for this precise reason that open source WebSocket-based libraries like Socket.IO thrive (it has more than 60,000 stars on GitHub).
These sorts of libraries provide a common solution to all the aforementioned problems and tuck the logic under an easy-to-use API.
Crucially for our conversation about Pub/Sub, these libraries have their own ways of facilitating broadcast, multicast, and unicast scenarios. For example, Socket.IO introduces a concept called rooms that solve the same problem as the Pub/Sub pattern, albeit in a slightly different way. In SignalR (another WebSocket library), they use a concept called groups.
Say you want to start from scratch, though - how would you go about utilising the Pub/Sub pattern with WebSockets?
Extending the example WebSocket code from the above section, you could turn the server into a Pub/Sub message broker.
Using a data structure like a Set
in JavaScript, you might map clients to topics.
When a publisher publishes a message with a topic, the Set can be referenced to lookup subscribers interested in said topic before shuttling the message using the WebSocket transport.
In this situation, everything is stored in memory (in a Set
). Thinking ahead and imagining a production environment, this means the server can only scale vertically. What’s more, storing everything in one host’s memory introduces a single point of failure. Should your message broker go offline, any memory of who subscribed to what topic will be lost.
While such a simple approach might be appropriate for smaller projects, additional work is necessary to scale the server past the resources available on a single host.
Alternatively, you could use a hosted Pub/Sub platform.
The rise of hosted Pub/Sub APIs
At its core, Pub/Sub provides a framework for exchanging messages between publishers and subscribers.
As we’ve mentioned, publishers don’t send messages to specific subscribers in a point-to-point manner. Instead, an intermediary is used - a Pub/Sub message broker, which groups messages into logical named channels (or topics).
To implement Pub/Sub is to implement a message broker, and that comes with some challenges you need to think about:
Ensuring messages are always delivered and in the order they were sent
What happens if the subscriber cannot accept the message, for example because they went offline? Holding onto it forever will lead to trouble, so a timeout and dead letter queue must be considered
How to transport messages to clients using protocols like WebSockets
Hosted Pub/Sub APIs address all these challenges for you.
They provide robust infrastructure, improved developer experiences, and additional value-added features, making them a convenient choice for many businesses and developers.
This post is all about practical advice, so here is an example of how a Pub/Sub API like Ably looks in JavaScript.
Subscriber:
var ably = new Ably.Realtime('your-key')
var channel = ably.channels.get('btc-price')
// Subscribe to messages on channel
channel.subscribe('update', message => {
alert(`New BTC price ${message.data}`);
})
Publisher:
// Server
var ably = new Ably.Realtime('your-key')
var channel = ably.channels.get('btc-price')
// Publish a message to the wig-dam channel
channel.publish('update', '23950.81')
Above, we first instantiate Ably with an API key before publishing a message with the topic btc-price
. Most likely, this would happen in your server code.
In a separate snippet running on the web browser, the client can now subscribe to the btc-price
updates and receive them through serverless WebSocket connections.
Apart from being really quick and easy to set up initially, hosted Pub/Sub platforms like Ably are production-ready from day one.
Because the event broker is hosted and managed for you, you don’t have to think about provisioning server resources, implementing redundancies, or how to ensure messages take the shortest path through the network to guarantee the lowest latency. Platforms like Ably take care of all that for you while also offering client SDKs based on WebSockets that take care of all the stuff you have to add “on top” we mentioned before like authentication and recovering from dropped connections.
Recommended Articles
WebSocket API and protocol explained: How they work, are used and more
Get a better understanding of WebSockets, how they work, their pros and cons, and how to start building realtime experience with WebSockets.
What is Pub/Sub? The Publish/Subscribe model explained
Learn everything you need to know about Pub/Sub, a design pattern that’s used to implement event-driven architectures and realtime messaging systems.