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
@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 }.
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>
);
}
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();
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.