Skip to Content
Jumentix DocsAdaptersRealtimeWebSocket Realtime API

WebSocket Realtime API

This guide is exclusively for the Socket.IO realtime interface.

Glossary

  • Inbound adapter — accepts external protocol calls and translates them into use-case calls.

Responsibility in context

  • Stack layer: adapter / realtime
  • Owns: framework-specific wiring for this technology
  • Used with: backend-template composition, persistence/SDK packages as needed, matching delivery guide
  • Not responsible for: domain rules, OpenAPI authoring, or browser offline storage

Why it exists

Framework choice should stay at the edge. This adapter keeps Express/Fastify/DB/realtime details replaceable.

What it is

Websocket Api adapter for Jumentix realtime interfaces — mounts application use-cases without leaking framework types into the domain.

Scope

  • Transport: WebSocket (Socket.IO protocol)
  • Server implementation: apps/backend-template/src/interface/WebSocket/WebSocketAPI.ts
  • SDK client: sdk-clients/websocket/WebSocketApiClient.ts
  • AsyncAPI source: spec/asyncapi/1.0.0.websocket.yml

Contract References

Endpoint and Channels

  • Base URL: ws://localhost:3001
  • Socket.IO path: /ws
  • Generic request channel: api:request
  • Generic response channel: api:response
  • Per-operation request: api:{operationId}:request
  • Per-operation response: api:{operationId}:response

Horizontal Scaling with Redis Streams Adapter

To run multiple Socket.IO servers with consistent cross-node delivery, enable Redis Streams adapter:

JUMENTIX_WEBSOCKET_SOCKETIO_ADAPTER=redis-streams JUMENTIX_WEBSOCKET_REDIS_URL=redis://127.0.0.1:6379/1

Fallback resolution order for Redis connection:

  1. JUMENTIX_WEBSOCKET_REDIS_URL
  2. JUMENTIX_REDIS_URL
  3. JUMENTIX_REDIS_HOST + JUMENTIX_REDIS_PORT + JUMENTIX_REDIS_DATABASE (+ JUMENTIX_REDIS_PASSWORD)

Implementation files:

  • apps/backend-template/src/interface/WebSocket/adapters/socket-io/redisStreamsAdapter.ts
  • apps/backend-template/src/interface/WebSocket/adapters/socket-io/socket-io.ts

Multi-thread Resilience with Socket.IO Cluster Adapter

To scale across CPU workers (multiple Node.js threads/processes in the same host), enable:

JUMENTIX_WEBSOCKET_SOCKETIO_ADAPTER=cluster JUMENTIX_WEBSOCKET_CLUSTER_WORKERS=4

Implementation files:

  • apps/backend-template/src/interface/WebSocket/adapters/socket-io/clusterAdapter.ts
  • apps/backend-template/src/interface/WebSocket/adapters/start-websocket-api.ts
  • apps/backend-template/src/interface/WebSocket/adapters/socket-io/socket-io.ts

Notes:

  1. Primary process forks workers and restarts dead workers automatically.
  2. Worker processes host Socket.IO and share events via @socket.io/cluster-adapter.
  3. For multi-host deployments, prefer Redis Streams adapter.

Multi-instance Validation Test

A dedicated integration test validates resilience with 2 Socket.IO servers + Redis:

  • apps/backend-template/test/integration/realtime/socketio.redis-streams.multi-instance.test.ts

Run it with Docker:

bun run smoke:realtime:redis-streams

Or run only the test (requires Redis running):

bun run test:integration:realtime:redis-streams

Runtime Flow

Deep Example: Generic Request + ACK correlation

import { io } from 'socket.io-client';
import { randomUUID } from 'crypto';

const socket = io('ws://localhost:3001', {
  path: '/ws',
  transports: ['websocket']
});

await new Promise<void>((resolve) => socket.on('connect', () => resolve()));

const requestId = randomUUID();
const channel = 'api:createOrganization:response';

socket.on(channel, (payload) => {
  if (payload?.metadata?.requestId !== requestId) return;
  console.log('Operation channel response:', payload);
});

socket.timeout(30000).emit(
  'api:request',
  {
    version: '1.0.0',
    operationId: 'createOrganization',
    authorization: 'Bearer <jwt>',
    input: {
      name: 'Acme Group',
      address: [],
      phone: [],
      email: []
    },
    metadata: { requestId }
  },
  (ackPayload) => {
    console.log('ACK response:', ackPayload);
  }
);

Deep Example: Per-operation Channel Request

import { io } from 'socket.io-client';

const socket = io('ws://localhost:3001', {
  path: '/ws',
  transports: ['websocket']
});

await new Promise<void>((resolve) => socket.on('connect', () => resolve()));

socket.emit(
  'api:getAllOrganizations:request',
  {
    version: '1.0.0',
    authorization: 'Bearer <jwt>',
    queryString: { page: 1, size: 20 },
    metadata: { requestId: 'req-001' }
  },
  (response) => {
    if (!response.ok) {
      console.error(response.error);
      return;
    }
    console.log(response.result);
  }
);

SDK Example

import { WebSocketApiClient } from '../sdk-clients/websocket/WebSocketApiClient';

const client = new WebSocketApiClient('ws://localhost:3001');
client.connect();

const response = await client.request({
  version: '1.0.0',
  operationId: 'getAllOrganizations',
  authorization: 'Bearer <jwt>',
  queryString: { page: 1, size: 10 },
  metadata: { requestId: 'req-ws-01' }
});

console.log(response.result);
client.disconnect();

Response/Error Handling Rules

  1. ok=true means result is the response payload for the operationId.
  2. ok=false means error contains normalized error data.
  3. metadata.requestId is the correlation key to match client requests.
  4. metadata.channel contains the response channel used by the server.

Operational Guidance

  1. Always send metadata.requestId from the client.
  2. Subscribe to both api:response and api:{operationId}:response when building generic clients.
  3. Keep a client-side timeout and retry strategy for transient network failures.

Junior checklist (“I can …”)

  • I know when to pick this adapter
  • I can start it from the documented script
  • I know the next guide/package to read

Next step

Return to Getting started or the matching delivery guide.