How to use Redis Pub/Sub with Node.js and Socket.IO

·7 min read
How to use Redis Pub/Sub with Node.js and Socket.IO

Building a Real-Time Stock Price Tracking App with Socket.IO and Redis. In this project, we'll explore how to build a real-time stock price tracking application using Socket.IO and Redis. This application allows users to subscribe to the stock price updates of their chosen companies and receive live price updates on their screen.

Introduction

The world of finance moves at a rapid pace, and keeping track of stock prices in real-time is crucial for investors and traders. In this project, we'll explore how to build a real-time stock price tracking application using Socket.IO and Redis. This application allows users to subscribe to the stock price updates of their chosen companies and receive live price updates on their screen. I have used the following technologies to build this application:

  • Node.js - JavaScript runtime environment
  • Express - Web application framework for Node.js
  • Socket.IO - JavaScript library for real-time communication between clients and servers
  • Redis - In-memory data structure store (used for message brokering)

If new to Redis then check out this Redis Introduction to get started.

Prerequisites

To follow along with this project, you'll need to have the following installed on your machine:

Understanding Publishers and Subscribers

In the realm of real-time communication, publishers and subscribers play crucial roles in delivering timely updates to interested parties. Redis Pub/Sub (Publish/Subscribe) is a messaging pattern that facilitates this communication. Let's break down the concepts:

Publishers

Publishers are components responsible for sending messages, updates, or events to specific channels in the Redis Pub/Sub system. In our stock price tracking application, the publisher generates random stock price updates for various companies and publishes them to Redis channels. These messages are then broadcasted to all subscribers interested in the corresponding companies.

Subscribers

Subscribers, on the other hand, are components that listen to specific channels and receive messages published by publishers. In our application, subscribers are the connected clients who have selected companies to track. When a stock price update is published to a Redis channel, the subscribers interested in that company receive the update in real-time via Socket.io communication.

Redis Pub/Sub in Action

Here's how Redis Pub/Sub works in the context of our real-time stock price tracking application:

  • Publisher Generates Updates: In the stockPublish.ts file, a publisher generates random stock price updates for different companies at regular intervals. These updates are JSON messages containing the company name and price.

  • Publishing to channel: The publisher uses the redisPub instance from the redisService.ts file to publish these messages to Redis channels. Each company's updates are published to a unique channel named stock-price-{company}.

  • Subscribers Subscribe to channel: When a client on the frontend selects a company and clicks the "Subscribe" button, a WebSocket message is sent to the server. The server uses Socket.IO to create a subscription to the corresponding Redis channel for that company.

  • Message Handling: The subscriber (client) is now subscribed to a specific Redis channel. When the publisher publishes a stock price update to that channel, Redis broadcasts the message to all subscribers listening to the channel.

  • Broadcasting to Subscribers: The server, acting as a middleman, listens to Redis messages on the subscribed channels using the redisSub instance. When a message is received, it is then broadcasted to the connected clients through Socket.IO's real-time WebSocket communication.

This process ensures that clients who have subscribed to specific companies receive their corresponding stock price updates as soon as they are generated, creating a seamless real-time experience.

Project Setup

To get started, create a new directory for the project and initialize a new Node.js project:

mkdir stock-price-pub-sub
cd stock-price-pub-sub
npm init -y

Next, install the following dependencies:

npm install express socket.io redis

Project Structure

The project structure is as follows:

stock-price-pub-sub
├── package.json
├── package-lock.json
├── README.md
├── src
   └── index.html
   └── index.ts
   └── sockethandler.ts
   └── cache
      └── redisService.ts
      └── stockPublish.ts
      └── stockSubscribe.ts
 
  • index.html - The frontend of the application. This file contains the HTML and JavaScript code for the frontend.
  • index.ts - The main entry point of the application. This file sets up the Express server and Socket.IO connections.
  • sockethandler.ts - This file contains the logic for handling Socket.IO connections and subscriptions.
  • cache/redisService.ts - This file contains the logic for connecting to Redis and publishing messages to Redis.
  • cache/stockPublish.ts - This file contains the logic for generating stock price updates and publishing them to Redis.
  • cache/stockSubscribe.ts - This file contains the logic for subscribing to Redis channels and handling messages.

Frontend - HTML and JavaScript

The frontend of the application is a simple HTML page with a dropdown menu to select a company and a "Subscribe" button. Here's how the frontend works:

  1. Users can select a company from the dropdown menu.
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
  <!-- ... -->
</head>
<body>
  <h1>Stock Price Tracking</h1>
  <div>
    <h2>Select a company to track:</h2>
    <select id="companySelect">
      <!-- Options for different companies -->
      <option value="AAPL">Apple Inc. (AAPL)</option>
      <option value="GOOGL">Alphabet Inc. (GOOGL)</option>
      <option value="AMZN">Amazon.com Inc. (AMZN)</option>
      <option value="TSLA">Tesla Inc. (TSLA)</option>
    </select>
    <button id="subscribeButton">Subscribe</button>
  </div>
  <div>
    <h2>Stock Price Updates:</h2>
    <ul id="priceUpdates"></ul>
  </div>
  
  <!-- Socket.IO script and JavaScript code -->
</body>
</html>
  1. When users click the "Subscribe" button, the following JavaScript code is executed:
<!-- index.html (continued) -->
<!-- Load Socket Lib -->
<script src="/socket.io/socket.io.js"></script>
<script>
  const socket = io();
  const companySelect = document.getElementById('companySelect');
  const subscribeButton = document.getElementById('subscribeButton');
  const priceUpdates = document.getElementById('priceUpdates');
 
  subscribeButton.addEventListener('click', () => {
    const company = companySelect.value;
    socket.emit('subscribeToCompany', company);
  });
 
  socket.on('stockPriceUpdate', (update) => {
    const li = document.createElement('li');
    li.textContent = `${update.company} - Price: $${update.price}`;
    priceUpdates.appendChild(li);
  });
</script>

Backend - Node.js, Express, Socket.IO, and Redis

The backend of the application is built using Node.js, Express for serving the HTML file, Socket.IO for WebSocket communication, and Redis for message brokering. Here's how the backend works:

  1. The server is set up using Express, and the HTML file is served to clients visiting the root URL ("/"):
// index.ts
// ... Express and server setup ...
app.get("/", (req, res) => {
  res.sendFile(__dirname + "/index.html");
});
  1. Socket.IO is used to establish WebSocket connections with clients:
// index.ts (continued)
// ... Express and server setup ...
const io = new SocketIOServer(server);
  1. Client Subscriptions: Clients subscribe to a specific company's stock price updates using the "Subscribe" button. This subscription information is used to create a unique Redis channel:
// sockethandler.ts
export const handleSocketConnections = (io: SocketIOServer) => {
  io.on("connection", (socket: Socket) => {
    // ... Connection handling ...
 
    socket.on("subscribeToCompany", (company: string) => {
      const channel = `stock-price-${company}`;
      subscribedChannel = channel;
 
      redisSub.subscribe(`stock-price-${company}`);
 
      subscribeToChannel(channel, (channel, message) => {
        messageHandler(channel, message, company, socket);
      });
 
      socket.on("disconnect", () => {
        unsubscribeFromChannel(subscribedChannel);
      });
    });
 
    // ... Additional cleanup on socket disconnect ...
  });
};
  1. Redis Pub/Sub Mechanism: Stock price updates are generated and published to Redis at regular intervals:
// stockPublish.ts
import { redisPub } from "./redisService";
 
const companies = ["AAPL", "GOOGL", "AMZN", "TSLA"];
 
setInterval(() => {
  const randomCompany = companies[Math.floor(Math.random() * companies.length)];
  const price = (Math.random() * 1000).toFixed(2);
  redisPub
    .publish(
      `stock-price-${randomCompany}`,
      JSON.stringify({ company: randomCompany, price })
    )
    .catch((error) => {
      console.error("Error publishing message:", error);
    });
}, 2000);
  1. When the server receives a stock price update on the Redis channel, it broadcasts this update to all connected clients subscribed to that company using Socket.IO:
// sockethandler.ts (continued)
const messageHandler = (
  channel: string,
  message: string,
  company: string,
  socket: Socket
) => {
  if (channel === `stock-price-${company}`) {
    socket.emit("stockPriceUpdate", JSON.parse(message));
  }
};

Running the Application

//package.json
{
  "scripts": {
    "start:dev": "nodemon src/index.ts"
  }
}

To run the application, follow these steps:

npm run start:dev
When selected a company and clicking the "Subscribe" button, you should see stock price updates for that company in the browser.
When you select multiple company, you should see stock price updates for all company instead.

Benefits and Use Cases

Redis Pub/Sub offers several benefits that make it a powerful choice for building real-time communication systems:

  • Decoupling: Publishers and subscribers are decoupled, meaning they don't need to know each other's details. Publishers publish messages to channels, and subscribers receive messages from channels without direct interaction.

  • Scalability: Redis's high-performance nature makes it suitable for handling a large number of messages and subscribers. It can be easily scaled to accommodate growing demands.

  • Real-Time Updates: Redis Pub/Sub facilitates real-time updates, making it ideal for applications that require instant notifications, such as stock tracking, social media feeds, and IoT data streaming.

Redis Pub/Sub is not limited to stock tracking applications alone. It can be applied to various scenarios, including chat applications, live feed displays, real-time analytics dashboards, and more. Its ability to efficiently distribute messages to multiple subscribers makes it a versatile tool for building interactive and dynamic applications.

Code repository

The complete code for this example is available on GitHub.

Source Code

Conclusion

By combining the power of Socket.IO for real-time communication and Redis for message brokering, we've built a functional stock price tracking application that keeps users informed about the latest stock price updates. This project provides a solid foundation for further development, such as adding more companies, incorporating authentication for personalized subscriptions, and enhancing the frontend design.

Real-time applications like this are not only valuable for tracking stock prices but can also serve as a starting point for building various other real-time monitoring and notification systems. Whether it's financial data, IoT sensor readings, or social media updates, the combination of WebSocket communication and message brokering with Redis opens up a world of possibilities for creating responsive and dynamic applications.

Next Steps