Skip to main content

Overview

Homarr provides a WebSocket server for real-time updates and subscriptions. The WebSocket API uses tRPC over WebSocket, providing the same type-safe interface as the HTTP API.

Connection Details

WebSocket Server: ws://localhost:3001 (default) Protocol: tRPC WebSocket adapter with SuperJSON serialization Authentication: Session-based (via cookies) or API key

Architecture

The WebSocket server is a separate process that runs alongside the main Homarr application:
Homarr Web (Port 3000) ←→ WebSocket Server (Port 3001)
                ↓                    ↓
           Database ←→ Shared State ←→ Database
Key Features:
  • Persistent connections for real-time updates
  • Automatic reconnection on disconnect
  • Heartbeat/keepalive messages
  • Same authentication as HTTP API
  • Full tRPC query/mutation/subscription support

Connecting to WebSocket

Browser Client

import { createWSClient, wsLink } from '@trpc/client';
import { createTRPCProxyClient } from '@trpc/client';
import type { AppRouter } from '@homarr/api/websocket';
import superjson from 'superjson';

// Create WebSocket client
const wsClient = createWSClient({
  url: 'ws://localhost:3001',
  // Authentication via cookie is automatic in browsers
});

// Create tRPC client
const client = createTRPCProxyClient<AppRouter>({
  transformer: superjson,
  links: [
    wsLink({
      client: wsClient,
    }),
  ],
});

// Use the client
const subscription = await client.widget.weather.subscribeAtLocation.subscribe(
  { latitude: 40.7128, longitude: -74.0060 },
  {
    onData: (data) => {
      console.log('Weather update:', data);
    },
  },
);

Node.js Client

import { WebSocket } from 'ws';
import { createWSClient, wsLink } from '@trpc/client';
import { createTRPCProxyClient } from '@trpc/client';
import superjson from 'superjson';

// Create WebSocket client with authentication
const wsClient = createWSClient({
  url: 'ws://localhost:3001',
  WebSocket: WebSocket,
  connectionParams: async () => {
    return {
      // Include session token in connection
      sessionToken: 'your-session-token',
    };
  },
});

const client = createTRPCProxyClient({
  transformer: superjson,
  links: [wsLink({ client: wsClient })],
});

Connection Lifecycle

Connection Events

const wsClient = createWSClient({
  url: 'ws://localhost:3001',
  onOpen: () => {
    console.log('✅ WebSocket connected');
  },
  onClose: () => {
    console.log('❌ WebSocket disconnected');
  },
});

Heartbeat

The server sends ping messages every 30 seconds to keep the connection alive:
  • Ping interval: 30,000ms
  • Pong timeout: 5,000ms
  • Connection terminated if pong not received

Automatic Reconnection

The tRPC WebSocket client automatically attempts to reconnect:
const wsClient = createWSClient({
  url: 'ws://localhost:3001',
  retryDelayMs: (attemptCount) => {
    // Exponential backoff
    return Math.min(1000 * Math.pow(2, attemptCount), 30000);
  },
});

Authentication

In browsers, session cookies are automatically sent:
// No special authentication needed - cookies sent automatically
const wsClient = createWSClient({
  url: 'ws://localhost:3001',
});

Manual Session Token (Node.js)

For server-side connections, include the session token:
import { parseCookies } from '@homarr/common';

const cookies = parseCookies(cookieHeader);
const sessionToken = cookies['next-auth.session-token'];

const ws = new WebSocket('ws://localhost:3001', {
  headers: {
    Cookie: `next-auth.session-token=${sessionToken}`,
  },
});

API Key Authentication

API keys are not directly supported over WebSocket. Use session-based authentication instead.

Error Handling

Connection Errors

const wsClient = createWSClient({
  url: 'ws://localhost:3001',
  onClose: (cause) => {
    console.error('Connection closed:', cause);
    // Handle reconnection or notify user
  },
  onError: (error) => {
    console.error('WebSocket error:', error);
  },
});

Subscription Errors

const subscription = client.widget.weather.subscribeAtLocation.subscribe(
  { latitude: 40.7128, longitude: -74.0060 },
  {
    onData: (data) => {
      console.log('Data:', data);
    },
    onError: (error) => {
      console.error('Subscription error:', error);
      if (error.code === 'UNAUTHORIZED') {
        // Redirect to login
      }
    },
  },
);

Performance Considerations

Connection Pooling

Reuse WebSocket connections across your application:
// Create a single WebSocket client
export const wsClient = createWSClient({
  url: WS_URL,
});

// Create a single tRPC client
export const wsApi = createTRPCProxyClient({
  transformer: superjson,
  links: [wsLink({ client: wsClient })],
});

// Use throughout your app
import { wsApi } from './api';

Subscription Management

Unsubscribe when components unmount:
useEffect(() => {
  const subscription = wsApi.widget.weather.subscribeAtLocation.subscribe(
    { latitude, longitude },
    {
      onData: setWeather,
    },
  );

  return () => {
    subscription.unsubscribe();
  };
}, [latitude, longitude]);

Message Batching

tRPC automatically batches messages when possible to reduce overhead.

Debugging

Enable Debug Logging

const wsClient = createWSClient({
  url: 'ws://localhost:3001',
  onOpen: () => console.log('Connection opened'),
  onClose: (cause) => console.log('Connection closed:', cause),
});

Monitor Connection State

const [connectionState, setConnectionState] = useState<'connecting' | 'connected' | 'disconnected'>('connecting');

const wsClient = createWSClient({
  url: 'ws://localhost:3001',
  onOpen: () => setConnectionState('connected'),
  onClose: () => setConnectionState('disconnected'),
});

Server Logs

The WebSocket server logs connection events:
✅ WebSocket Server listening on ws://localhost:3001
➕ Connection (1) GET /
➖ Connection (0) 1000 Normal Closure

React Hooks Integration

Use tRPC React Query hooks with WebSocket subscriptions:
import { api } from '~/trpc/react';

function WeatherWidget() {
  const [weather, setWeather] = useState(null);

  // Subscribe to weather updates
  api.widget.weather.subscribeAtLocation.useSubscription(
    { latitude: 40.7128, longitude: -74.0060 },
    {
      onData: (data) => {
        setWeather(data);
      },
    },
  );

  return <div>{weather?.temperature</div>;
}

Next Steps

WebSocket Events

Learn about available WebSocket events and subscriptions

Widget Subscriptions

Real-time widget data subscriptions

Build docs developers (and LLMs) love