PHASE 6 — Topic 22: Using the Redis Adapter to Scale Socket.IO Across Multiple Instances

We understood the problem in the last post: multiple server instances can't broadcast to each other's clients by default. This post covers the solution, the Redis Adapter, and how to wire it into our project.

How the Redis Adapter Works, Conceptually

The Redis Adapter uses Redis's Pub/Sub mechanism as a shared messaging layer between server instances. Here's the flow for a broadcast, like io.to("room1").emit(...):

  1. The message is sent to all matching clients connected to the current server, exactly as before
  2. The same message is also published to a Redis channel
  3. Every other Socket.IO server instance, subscribed to that same channel, receives it, and relays it to its own matching connected clients

From your application code's perspective, nothing changes. You still write io.emit(...), io.to(room).emit(...), socket.broadcast.emit(...), exactly as we've been doing throughout this course. The adapter works transparently underneath, you don't rewrite your event logic to use it.

Installing the Required Packages


    npm install @socket.io/redis-adapter redis

This assumes a Redis server is available for your app to connect to, either running locally for development or hosted for production.

Setting Up the Adapter

Update server.ts to create Redis clients and attach the adapter:


    import { createClient } from "redis";
    import { createAdapter } from "@socket.io/redis-adapter";

    const pubClient = createClient({ url: process.env.REDIS_URL || "redis://localhost:6379" });
    const subClient = pubClient.duplicate();

    await Promise.all([pubClient.connect(), subClient.connect()]);

    const io = new Server<
        ClientToServerEvents,
        ServerToClientEvents,
        InterServerEvents,
        SocketData
    >(httpServer, {
        path: "/api/socket",
        adapter: createAdapter(pubClient, subClient),
    });

A few important details:

  • The adapter needs two separate Redis client connections, one dedicated to publishing (pubClient), one dedicated to subscribing (subClient). This is a Redis requirement, not a Socket.IO one, a single connection cannot both publish and subscribe at the same time
  • pubClient.duplicate() creates the second connection using the same configuration, so you only need to define the connection details once
  • Both clients must be connected, using await, before the adapter is created

Where This Code Fits in Our server.ts Structure

Since pubClient.connect() and subClient.connect() are asynchronous, and our existing server.ts structure uses app.prepare().then(...), place the Redis connection setup before creating the Socket.IO server, inside the same async flow:


    app.prepare().then(async () => {
        const httpServer = createServer((req, res) => {
            const parsedUrl = parse(req.url!, true);
            handle(req, res, parsedUrl);
        });

        const pubClient = createClient({ url: process.env.REDIS_URL || "redis://localhost:6379" });
        const subClient = pubClient.duplicate();
        await Promise.all([pubClient.connect(), subClient.connect()]);

        const io = new Server<
            ClientToServerEvents,
            ServerToClientEvents,
            InterServerEvents,
            SocketData
        >(httpServer, {
            path: "/api/socket",
            adapter: createAdapter(pubClient, subClient),
        });

        // ... rest of connection handling stays the same
    });

What the Adapter Does Not Solve

This is worth being explicit about, since it's a common misconception. The Redis Adapter solves broadcasting across servers. It does not solve the sticky sessions requirement we covered in the last post, you still need your load balancer configured for session affinity, since a single client's long-polling requests still need to consistently reach the same server instance. The adapter and sticky sessions solve two separate parts of the same overall problem.

A Limitation Worth Knowing: Connection State Recovery

Recall the connection state recovery feature from Topic 17. As of the current Redis Adapter, this feature is not supported when using it. If you need both multi-server scaling and connection state recovery together, you would need to look at the newer Redis Streams Adapter instead, which handles temporary Redis disconnections differently and is designed with this compatibility in mind. For most courses and small-to-medium production apps, the standard Redis Pub/Sub adapter shown here is the right starting point.

A Security Note Worth Taking Seriously

Messages passed through the Redis Adapter's Pub/Sub channels are not encrypted, signed, or authenticated by the adapter itself. Redis is meant to be treated as trusted internal infrastructure, not exposed to public networks. In production, this means proper Redis authentication, firewall rules, and ideally keeping Redis on a private network that your application servers can reach, but the public internet cannot.

Testing This Locally

To actually see the adapter working, you would need to run two instances of your server on different ports, both connected to the same Redis instance, then connect different browser tabs to each port separately. If a message sent through the instance on one port appears in a tab connected to the other port, the adapter is working correctly. Setting up this kind of local multi-instance testing environment is optional for this course, since our focus is understanding the concept and the code correctly, but it's a valuable exercise if you want to see it in action yourself.

Summary

  • The Redis Adapter uses Redis Pub/Sub so that a broadcast on one server instance also reaches clients connected to other instances
  • It requires two separate Redis connections, one for publishing and one for subscribing, created with pubClient.duplicate()
  • Your existing io.emit(), io.to(), and socket.broadcast.emit() code does not need to change, the adapter works transparently underneath
  • The adapter solves cross-server broadcasting, but does not replace the need for sticky sessions at your load balancer
  • The standard Redis Pub/Sub adapter does not support connection state recovery; the newer Redis Streams Adapter exists for cases needing both
  • Redis should be treated as trusted internal infrastructure, properly secured and not exposed to public networks

In the next post, we cover deploying a Next.js + Socket.IO app to production, including hosting options and what to look for given everything we've covered in this phase.

No comments:

Post a Comment

PHASE 7 — Topic 25: Building One Complete Real-Time Project End-to-End

This is the final post of the course. We bring together everything from all seven phases into one complete, polished feature: a real-time ch...