typescript 33 lines · 1 tab

WebSocket server with topic subscriptions (ws)

Mateo Rodriguez Jan 2026
1 tab
import { WebSocketServer } from 'ws';
import type WebSocket from 'ws';

type ClientState = { topics: Set<string> };
const state = new WeakMap<WebSocket, ClientState>();

export function startWsServer(server: import('node:http').Server) {
  const wss = new WebSocketServer({ server, maxPayload: 1024 * 64 });

  wss.on('connection', (socket) => {
    state.set(socket, { topics: new Set() });

    socket.on('message', (buf) => {
      const msg = JSON.parse(buf.toString('utf8')) as { type: string; topic?: string };
      const s = state.get(socket)!;

      if (msg.type === 'subscribe' && msg.topic) s.topics.add(msg.topic);
      if (msg.type === 'unsubscribe' && msg.topic) s.topics.delete(msg.topic);
    });
  });

  return {
    publish(topic: string, data: unknown) {
      const payload = JSON.stringify({ type: 'event', topic, data });
      for (const client of wss.clients) {
        const s = state.get(client);
        if (s?.topics.has(topic) && client.readyState === client.OPEN) {
          client.send(payload);
        }
      }
    }
  };
}
1 file · typescript Explain with highlit

Raw WebSockets can turn into an unmaintainable mess unless you define a tiny protocol up front. I keep messages typed (even if it’s ‘JSON with a type field’) and I implement topic subscriptions so clients opt into exactly what they need. I track subscriptions per connection and clean them up on close so state doesn’t leak. Another practical detail is defensive limits: set maxPayload and validate incoming payloads so one bad client can’t chew CPU and memory. For many apps I’ll start with SSE, but when I truly need bidirectional real-time interactions, a topic-based approach keeps the system sane as features grow.


Related snips

typescript
export type Settled<R> =
  | { status: 'fulfilled'; value: R }
  | { status: 'rejected'; reason: unknown };

export interface ConcurrencyOptions {
  limit: number;

Simple concurrency limiter for batch operations

node concurrency async
by codesnips 2 tabs
typescript
import { randomBytes, createHash } from "crypto";
import jwt from "jsonwebtoken";
import { RefreshTokenStore } from "./store";

const ACCESS_SECRET = process.env.ACCESS_SECRET!;
const ACCESS_TTL = "15m";

JWT access + refresh token rotation (conceptual)

security node jwt
by codesnips 3 tabs
ruby
class Comment < ApplicationRecord
  belongs_to :article
  belongs_to :author, class_name: "User"

  validates :body, presence: true, length: { maximum: 2_000 }

Live comments with model broadcasts + turbo_stream_from

rails hotwire turbo
by codesnips 4 tabs
erb
<%# private stream: turbo signs the serialized record name %>
<%= turbo_stream_from current_user %>

<section class="notifications">
  <h1>Notifications</h1>

Turbo Streams + authorization: signed per-user stream name

rails turbo hotwire
by codesnips 3 tabs
ruby
# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
  def subscribed
    # Subscribe to a specific room
    room = Room.find(params[:room_id])

ActionCable for real-time WebSocket communication

ruby rails actioncable
by Sarah Mitchell 3 tabs
sql
CREATE TABLE outbox (
    id             BIGSERIAL PRIMARY KEY,
    aggregate_type TEXT        NOT NULL,
    aggregate_id   TEXT        NOT NULL,
    event_type     TEXT        NOT NULL,
    payload        JSONB       NOT NULL,

Transactional outbox in Node (DB write + event)

node postgres reliability
by codesnips 3 tabs

Share this code

Here's the card — post it anywhere.

WebSocket server with topic subscriptions (ws) — share card
Link copied