Home Getting Started Server Client Shared Types GitHub ↗
Getting Started

Get up and running fast

Install Sockr, spin up a server, and send your first real-time message in minutes.

Getting Started

This guide walks you through installing Sockr, setting up a server, connecting a client, and sending your first real-time message.

Installation

Sockr is split into three focused packages. Install only what you need:

# All packages (recommended for monorepos)
npm install sockr-server sockr-client sockr-shared

# Server only
npm install sockr-server

# Client only (React)
npm install sockr-client sockr-shared
# All packages
pnpm add sockr-server sockr-client sockr-shared

# Server only
pnpm add sockr-server

# Client only (React)
pnpm add sockr-client sockr-shared
# All packages
yarn add sockr-server sockr-client sockr-shared

# Server only
yarn add sockr-server

# Client only (React)
yarn add sockr-client sockr-shared
TypeScript: All packages ship with full TypeScript declarations. No @types packages needed.

Server Setup

The SocketServer supports three initialization modes depending on your app's architecture.

Mode 1 — Standalone

Sockr creates and manages its own HTTP server. The simplest setup for dedicated real-time services.

import { SocketServer } from 'sockr-server';

const server = new SocketServer({ port: 3000 });

server
  .createStandalone()
  .useAuth(authHandler)
  .usePresence()
  .useMessaging()
  .useGroupMessaging();

await server.listen(); // listens on port 3000

Mode 2 — Attach to existing HTTP server

Attach Sockr to an existing http.Server or https.Server. Call initialize() instead of listen(), then start listening on your own server.

import { createServer } from 'http';
import { SocketServer } from 'sockr-server';

const httpServer = createServer(myRequestHandler);
const sockr = new SocketServer();

sockr
  .attach(httpServer)
  .useAuth(authHandler)
  .usePresence()
  .useMessaging();

await sockr.initialize(); // connects providers, inits plugins
httpServer.listen(3000);   // start your own server

Mode 3 — Attach to Express

Pass an Express app to attachToExpress(). Sockr wraps it in an HTTP server internally. Call listen() to start.

import express from 'express';
import { SocketServer } from 'sockr-server';

const app = express();
app.get('/health', (req, res) => res.json({ ok: true }));

const sockr = new SocketServer();

sockr
  .attachToExpress(app)
  .useAuth(authHandler)
  .usePresence()
  .useMessaging()
  .useGroupMessaging();

await sockr.listen(3000);

Authentication

The useAuth() plugin takes an AuthHandler — an async function that receives the client's token and returns { userId: string } on success or null to reject.

import { SocketServer } from 'sockr-server';

const server = new SocketServer({ port: 3000 });

server
  .createStandalone()
  .useAuth(async (token: string) => {
    // Verify with your own logic (JWT, database lookup, etc.)
    const user = await db.findUserByToken(token);
    if (!user) return null; // reject the connection

    return { userId: user.id }; // authenticate
  });

await server.listen();

When authentication succeeds, the server emits authenticated to the client with { userId, socketId }. On failure it emits auth_error with { message }.

Tip: Register usePresence(), useMessaging(), and useGroupMessaging() after useAuth(). On successful authentication, Sockr automatically flushes queued messages and restores group memberships for the connecting user.

Client — React

Wrap your app in SocketProvider. Provide a url and optionally a token — authentication is triggered automatically once connected.

import { SocketProvider } from 'sockr-client';

function App() {
  return (
    <SocketProvider
      config={{ url: "http://localhost:3000" }}
      token={authToken}
    >
      <YourApp />
    </SocketProvider>
  );
}

All 12 hooks are then available anywhere inside the provider:

import {
  useSocket,
  useMessages,
  useSendMessage,
  usePresence,
  useTypingIndicator,
} from 'sockr-client/react';

function Dashboard() {
  const { isAuthenticated, userId } = useSocket();
  const { messages } = useMessages();
  const { sendMessage, isSending } = useSendMessage();
  const { isUserOnline } = usePresence();
  const { usersTyping } = useTypingIndicator();

  return (
    <div>
      <p>Logged in as {userId}</p>
      {[...usersTyping].map(id => (
        <p key={id}>{id} is typing...</p>
      ))}
      {messages.map(msg => (
        <div key={msg.id}>
          <strong>{msg.from}</strong>
          {isUserOnline(msg.from) && <span> 🟢</span>}
          : {msg.content}
        </div>
      ))}
    </div>
  );
}
React Native: Use transports={['websocket']} on the provider — polling transport is not supported in React Native.

Client — Vanilla JS / TypeScript

Use SocketClient directly without any framework dependency.

import { SocketClient } from 'sockr-client';

const client = new SocketClient({
  url: 'http://localhost:3000',
  autoConnect: true,
  reconnection: true,
  reconnectionAttempts: 5,
  reconnectionDelay: 1000,   // delays multiply per attempt
  timeout: 20000,
  transports: ['websocket'],
});

// Connection lifecycle
client.on('connect', () => {
  client.authenticate('your-auth-token');
});

client.on('authenticated', ({ userId, socketId }) => {
  console.log('Authenticated as', userId);
});

client.on('auth_error', ({ message }) => {
  console.error('Auth failed:', message);
});

// Receive messages
client.on('message', ({ from, content, timestamp, messageId }) => {
  console.log(`[${from}]: ${content}`);
});

// Send a message
client.sendMessage('recipient-user-id', 'Hello!');

// Cleanup
// client.disconnect();

Sending Your First Message

A complete example using useSendMessage and useMessages:

import { useSendMessage, useMessages } from 'sockr-client/react';

interface Props {
  recipientId: string;
}

function DirectMessage({ recipientId }: Props) {
  const { messages } = useMessages();
  const { sendMessage, isSending, error } = useSendMessage();

  return (
    <div>
      <div className="message-list">
        {messages.map(msg => (
          <div key={msg.id} className="message">
            <strong>{msg.from}</strong>: {msg.content}
            <span>{new Date(msg.timestamp).toLocaleTimeString()}</span>
          </div>
        ))}
      </div>

      {error && <p style={{ color: 'red' }}>{error}</p>}

      <button
        onClick={() => sendMessage(recipientId, 'Hello!')}
        disabled={isSending}
      >
        {isSending ? 'Sending...' : 'Send'}
      </button>
    </div>
  );
}

Group Chat Example

Use useUserGroups, useGroupMessages, and useGroupTyping together for a complete group chat experience:

import {
  useUserGroups,
  useGroupMessages,
  useGroupTyping,
} from 'sockr-client/react';

// List groups and create new ones
function GroupList() {
  const { groups, isLoading, createGroup } = useUserGroups();

  return (
    <div>
      {isLoading && <p>Loading groups...</p>}
      {groups.map(g => (
        <div key={g.id}>{g.name}</div>
      ))}
      <button onClick={() => createGroup('My Group', ['user-2', 'user-3'])}>
        New Group
      </button>
    </div>
  );
}

// Group conversation
function GroupChat({ groupId }: { groupId: string }) {
  const { messages, sendMessage, isLoadingHistory } =
    useGroupMessages(groupId, { fetchHistory: true, limit: 50 });

  const { typingMembers, startTyping, stopTyping } =
    useGroupTyping(groupId, 2000);

  return (
    <div>
      {isLoadingHistory && <p>Loading history...</p>}
      {typingMembers.length > 0 && (
        <p>{typingMembers.join(', ')} typing...</p>
      )}
      {messages.map(msg => (
        <div key={msg.id}>
          <strong>{msg.from}</strong>: {msg.content}
        </div>
      ))}
      <input
        onFocus={startTyping}
        onBlur={stopTyping}
        onKeyDown={startTyping}
        placeholder="Type a message..."
      />
      <button onClick={() => sendMessage('Hello group!')}>Send</button>
    </div>
  );
}

Voice Calling Example

Enable P2P calling on the server with a STUN server, then use useVoiceCall and useSocketEvent on the client:

Server

import { SocketServer } from 'sockr-server';

const server = new SocketServer({
  port: 3000,
  voice: {
    iceServers: [{ urls: 'stun:stun.l.google.com:19302' }],
    // Optional: Coturn TURN server for clients behind strict NATs
    turn: {
      urls: 'turn:turn.example.com:3478',
      secret: process.env.TURN_SECRET!,
    },
  },
});

server
  .createStandalone()
  .useAuth(authHandler)
  .useVoice();  // registers the signaling plugin

await server.listen();

Client (React)

import { useVoiceCall } from 'sockr-client/react';

function CallUI({ remoteUserId }: { remoteUserId: string }) {
  const {
    callState,    // 'idle' | 'calling' | 'ringing' | 'active' | 'busy' | 'ended'
    incomingCall, // IncomingCall | null — set when another user calls you
    localStream,
    remoteStream,
    startCall,
    answerCall,   // () => Promise<void> — accepts incomingCall automatically
    rejectCall,   // () => void
    hangUp,
  } = useVoiceCall({ video: false });

  // Attach remote stream to an <audio> element
  const audioRef = useRef<HTMLAudioElement>(null);
  useEffect(() => {
    if (audioRef.current && remoteStream) {
      audioRef.current.srcObject = remoteStream;
    }
  }, [remoteStream]);

  return (
    <div>
      <p>Call state: {callState}</p>
      <audio ref={audioRef} autoPlay />
      {incomingCall && (
        <div>
          <p>{incomingCall.from} is calling...</p>
          <button onClick={answerCall}>Answer</button>
          <button onClick={rejectCall}>Reject</button>
        </div>
      )}
      {callState === 'idle' && !incomingCall && (
        <button onClick={() => startCall(remoteUserId)}>Call</button>
      )}
      {callState === 'active' && (
        <button onClick={hangUp}>Hang Up</button>
      )}
    </div>
  );
}

Conference Calling Example

Enable SFU group calling on the server with LiveKit, then use useConferenceCall on the client:

Server

import { SocketServer, LiveKitSFUProvider } from 'sockr-server';
// npm install livekit-server-sdk

const server = new SocketServer({ port: 3000 });

server
  .createStandalone()
  .useAuth(authHandler)
  .useGroupMessaging()
  .useConference(
    new LiveKitSFUProvider({
      apiKey: process.env.LIVEKIT_API_KEY!,
      apiSecret: process.env.LIVEKIT_API_SECRET!,
      host: 'https://your-livekit.example.com',
    }),
    'wss://your-livekit.example.com'
  );

await server.listen();

Client (React)

import { useConferenceCall, LiveKitClientAdapter } from 'sockr-client';
// npm install livekit-client

const adapter = new LiveKitClientAdapter();

function GroupCall({ groupId }: { groupId: string }) {
  const { conferenceState, participants, joinConference, leaveConference, onRemoteTrack } =
    useConferenceCall({ adapter, audio: true, video: true });

  // Attach incoming audio/video tracks
  onRemoteTrack((track, participantId) => {
    const el = document.getElementById(`media-${participantId}`) as HTMLVideoElement;
    if (el) el.srcObject = new MediaStream([track]);
  });

  return (
    <div>
      <p>Conference: {conferenceState}</p>
      <p>Participants: {participants.length}</p>
      {participants.map(p => (
        <video key={p.userId} id={`media-${p.userId}`} autoPlay playsInline />
      ))}
      {conferenceState === 'idle' ? (
        <button onClick={() => joinConference(groupId)}>Join Call</button>
      ) : (
        <button onClick={leaveConference}>Leave Call</button>
      )}
    </div>
  );
}

With Persistence

By default, Sockr uses in-memory providers (data is lost on restart). For production, supply real adapters via the providers config.

import { SocketServer } from 'sockr-server';
import { MongoMessageStore } from 'sockr-server/adapters/db/mongo';
import { RedisQueueProvider } from 'sockr-server/adapters/queue/redis';
import { RedisCacheProvider } from 'sockr-server/adapters/cache/redis';

const server = new SocketServer({
  port: 3000,
  cors: { origin: 'https://myapp.com', credentials: true },
  providers: {
    messageStore: new MongoMessageStore(
      'mongodb://localhost:27017/myapp'
    ),
    queue: new RedisQueueProvider('redis://localhost:6379'),
    cache: new RedisCacheProvider('redis://localhost:6379'),
  },
});

server
  .createStandalone()
  .useAuth(authHandler)
  .usePresence()
  .useMessaging()
  .useGroupMessaging();

await server.listen();
Offline queuing: With a queue provider configured, messages sent to offline users are stored and automatically flushed the next time they connect and authenticate.
Custom adapters: Implement the IMessageStore, IQueueProvider, or ICacheProvider interfaces from sockr-shared to integrate any database or caching system. See the Shared Types page for the full interface definitions.
Server Reference → Client Reference →