Skip to content

Feature Request: Optional Adapter Interface for Horizontal Scaling #133

@0-don

Description

@0-don

Problem

next-ws currently only supports single-instance deployments. When running multiple Next.js instances behind a load balancer (Docker Compose + Traefik, Kubernetes, etc.), WebSocket messages only reach clients connected to the same instance. There's no way to broadcast messages across instances.

Proposed Solution

Add an optional adapter interface that allows users to implement their own pub/sub mechanism (Redis, RabbitMQ, Kafka, etc.) for cross-instance communication.

Implementation Requirements

1. Add adapter interface (~15 lines):

// src/server/adapters/adapter.ts
export interface Adapter {
  broadcast(room: string, message: unknown): Promise<void>;
  onMessage(room: string, handler: (message: unknown) => void): void;
  close(): Promise<void>;
}

2. Modify setupWebSocketServer in src/server/setup.ts (~30 lines):

  • Add optional options?: { adapter?: Adapter } parameter
  • When adapter is provided:
    • Subscribe to adapter messages and forward to local clients
    • Forward local client messages to adapter for broadcast
  • When adapter is NOT provided, behavior is unchanged (backward compatible)

3. Export from src/server/index.ts:

export type { Adapter } from './adapters/adapter.js';

Benefits

  • Zero breaking changes: Existing code works identically without an adapter
  • No new dependencies: Users bring their own Redis/RabbitMQ client
  • Flexible: Users can implement encryption, compression, rate limiting in their adapter
  • Out of scope for next-ws: You don't maintain Redis connection logic or debug broker issues

Example User Implementation

import { setupWebSocketServer, type Adapter } from 'next-ws/server';
import Redis from 'ioredis';

class RedisAdapter implements Adapter {
  private pub = new Redis(process.env.REDIS_URL);
  private sub = new Redis(process.env.REDIS_URL);
  
  async broadcast(room: string, message: unknown) {
    await this.pub.publish(room, JSON.stringify(message));
  }
  
  onMessage(room: string, handler: (msg: unknown) => void) {
    this.sub.subscribe(room);
    this.sub.on('message', (channel, msg) => {
      if (channel === room) handler(JSON.parse(msg));
    });
  }
  
  async close() {
    await Promise.all([this.pub.quit(), this.sub.quit()]);
  }
}

setupWebSocketServer(nextServer, { adapter: new RedisAdapter() });

Related Issues

This approach keeps multi-instance complexity out of next-ws core while enabling users who need horizontal scaling.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions