| name | websocket-realtime-builder |
| description | Implements real-time features using WebSockets with Socket.io, rooms, authentication, and reconnection handling. Use when users request "real-time updates", "WebSocket", "Socket.io", "live chat", or "push notifications". |
WebSocket Realtime Builder
Build real-time applications with WebSockets and Socket.io.
Core Workflow
- Choose library: Socket.io vs native WebSocket
- Setup server: Configure WebSocket server
- Add authentication: Validate connections
- Implement rooms: Group connections
- Handle events: Define event handlers
- Add reconnection: Handle disconnects gracefully
Installation
npm install socket.io
npm install socket.io-client
Server Setup
Basic Socket.io Server
import express from 'express';
import { createServer } from 'http';
import { Server, Socket } from 'socket.io';
import { verifyToken } from './auth';
const app = express();
const httpServer = createServer(app);
const io = new Server(httpServer, {
cors: {
origin: process.env.CLIENT_URL,
credentials: true,
},
pingInterval: 25000,
pingTimeout: 60000,
});
io.use(async (socket, next) => {
const token = socket.handshake.auth.token;
if (!token) {
return next(new Error('Authentication required'));
}
try {
const user = await verifyToken(token);
socket.data.user = user;
next();
} catch (err) {
next(new Error('Invalid token'));
}
});
io.on('connection', (socket: Socket) => {
const user = socket.data.user;
console.log(`User connected: ${user.id}`);
socket.join(`user:${user.id}`);
socket.on('disconnect', () => {
console.log(`User disconnected: ${user.id}`);
});
});
httpServer.listen(3001, () => {
console.log('Socket.io server running on port 3001');
});
export { io };
Namespaces and Rooms
import { Server, Socket } from 'socket.io';
export function setupChatNamespace(io: Server) {
const chatNamespace = io.of('/chat');
chatNamespace.on('connection', (socket: Socket) => {
const user = socket.data.user;
socket.on('join-room', async (roomId: string) => {
const canAccess = await canAccessRoom(user.id, roomId);
if (!canAccess) {
socket.emit('error', { message: 'Access denied' });
return;
}
socket.join(`room:${roomId}`);
socket.to(`room:${roomId}`).emit('user-joined', {
userId: user.id,
: user.,
});
});
socket.(, {
socket.();
socket.().(, {
: user.,
});
});
socket.(, (: { : ; : }) => {
{ roomId, content } = data;
message = db..({
: {
roomId,
: user.,
content,
},
: { : },
});
chatNamespace.().(, {
: message.,
: message.,
: {
: user.,
: user.,
},
: message.,
});
});
socket.(, {
socket.().(, {
: user.,
: user.,
});
});
socket.(, {
socket.().(, {
: user.,
});
});
});
chatNamespace;
}
Event Emitters
import { io } from '../server';
export class NotificationService {
static sendToUser(userId: string, event: string, data: any) {
io.to(`user:${userId}`).emit(event, data);
}
static sendToUsers(userIds: string[], event: string, data: any) {
userIds.forEach((userId) => {
io.to(`user:${userId}`).emit(event, data);
});
}
static broadcast(event: string, data: any) {
io.emit(event, data);
}
static sendToRoom(roomId: string, event: , : ) {
io.().(event, data);
}
() {
.(order., , {
: order.,
: order.,
});
io.().(, {
: order.,
: order.,
: order.,
});
}
}
Client Setup
React Client Hook
import { useEffect, useRef, useState, useCallback } from 'react';
import { io, Socket } from 'socket.io-client';
import { useAuth } from './useAuth';
interface UseSocketOptions {
namespace?: string;
autoConnect?: boolean;
}
export function useSocket(options: UseSocketOptions = {}) {
const { namespace = '/', autoConnect = true } = options;
const { token } = useAuth();
const socketRef = useRef<Socket | null>(null);
const [isConnected, setIsConnected] = useState(false);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
if (!token || !autoConnect) return;
const socket = io(`${process.env.NEXT_PUBLIC_WS_URL}${namespace}`, {
auth: { token },
reconnection: ,
: ,
: ,
: ,
});
socket.(, {
();
();
});
socket.(, {
();
});
socket.(, {
(err.);
();
});
socketRef. = socket;
{
socket.();
};
}, [token, namespace, autoConnect]);
emit = ( {
socketRef.?.(event, data);
}, []);
on = ( {
socketRef.?.(event, handler);
{
socketRef.?.(event, handler);
};
}, []);
off = ( {
socketRef.?.(event, handler);
}, []);
{
: socketRef.,
isConnected,
error,
emit,
on,
off,
};
}
Chat Hook
import { useEffect, useState, useCallback } from 'react';
import { useSocket } from './useSocket';
interface Message {
id: string;
content: string;
author: { id: string; name: string };
createdAt: string;
}
interface TypingUser {
userId: string;
name: string;
}
export function useChat(roomId: string) {
const { socket, isConnected, emit, on } = useSocket({ namespace: '/chat' });
const [messages, setMessages] = useState<Message[]>([]);
const [typingUsers, setTypingUsers] = useState<TypingUser[]>([]);
useEffect(() => {
if (isConnected && roomId) {
emit('join-room', roomId);
return () => {
emit('leave-room', roomId);
};
}
}, [isConnected, roomId, emit]);
( {
unsubMessage = (, {
( [...prev, message]);
});
unsubTyping = (, {
( {
(prev.( u. === user.)) prev;
[...prev, user];
});
});
unsubStopTyping = (, {
( prev.( u. !== userId));
});
{
();
();
();
};
}, [on]);
sendMessage = ( {
(, { roomId, content });
}, [emit, roomId]);
startTyping = ( {
(, roomId);
}, [emit, roomId]);
stopTyping = ( {
(, roomId);
}, [emit, roomId]);
{
messages,
typingUsers,
sendMessage,
startTyping,
stopTyping,
isConnected,
};
}
Chat Component
'use client';
import { useState, useRef, useEffect } from 'react';
import { useChat } from '@/hooks/useChat';
export function ChatRoom({ roomId }: { roomId: string }) {
const {
messages,
typingUsers,
sendMessage,
startTyping,
stopTyping,
isConnected,
} = useChat(roomId);
const [input, setInput] = useState('');
const messagesEndRef = useRef<HTMLDivElement>(null);
const typingTimeoutRef = useRef<NodeJS.Timeout>();
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [messages]);
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setInput(e.target.value);
startTyping();
clearTimeout(typingTimeoutRef.current);
typingTimeoutRef.current = setTimeout(stopTyping, );
};
= () => {
e.();
(!input.()) ;
(input);
();
();
};
(!isConnected) {
;
}
(
);
}
Real-time Notifications
import { useEffect, useState } from 'react';
import { useSocket } from './useSocket';
import { toast } from 'sonner';
interface Notification {
id: string;
type: string;
title: string;
message: string;
createdAt: string;
read: boolean;
}
export function useNotifications() {
const { on, isConnected } = useSocket();
const [notifications, setNotifications] = useState<Notification[]>([]);
const [unreadCount, setUnreadCount] = useState(0);
useEffect(() => {
const unsubscribe = on('notification', (notification: Notification) => {
setNotifications((prev) => [notification, ...prev]);
setUnreadCount((prev) => prev + 1);
(notification., {
: notification.,
});
});
unsubscribe;
}, [on]);
= () => {
(
prev.( (n. === id ? { ...n, : } : n))
);
( .(, prev - ));
};
= () => {
( prev.( ({ ...n, : })));
();
};
{
notifications,
unreadCount,
markAsRead,
markAllAsRead,
isConnected,
};
}
Presence System
import { Server, Socket } from 'socket.io';
interface UserPresence {
odlineserId: string;
name: string;
status: 'online' | 'away' | 'busy';
lastSeen: Date;
}
const onlineUsers = new Map<string, UserPresence>();
export function setupPresence(io: Server) {
io.on('connection', (socket: Socket) => {
const user = socket.data.user;
onlineUsers.set(user.id, {
odlineserId: user.id,
name: user.name,
status: 'online',
lastSeen: new Date(),
});
io.emit('presence:update', {
: user.,
: ,
});
socket.(, .(onlineUsers.()));
socket.(, {
presence = onlineUsers.(user.);
(presence) {
presence. = status;
io.(, { : user., status });
}
});
socket.(, {
onlineUsers.(user.);
io.(, {
: user.,
: ,
});
});
});
}
Error Handling & Reconnection
const socket = io(WS_URL, {
auth: { token },
reconnection: true,
reconnectionAttempts: 10,
reconnectionDelay: 1000,
reconnectionDelayMax: 10000,
timeout: 20000,
});
socket.on('connect_error', (error) => {
if (error.message === 'Authentication required') {
router.push('/login');
} else {
console.error('Connection error:', error);
}
});
socket.on('reconnect', (attemptNumber) => {
console.log(`Reconnected after ${attemptNumber} attempts`);
socket.emit('sync-state');
});
socket.on('reconnect_failed', () => {
console.error('Failed to reconnect');
toast.error('Connection lost. Please refresh the page.');
});
Best Practices
- Authenticate connections: Validate tokens on connect
- Use rooms: Group users logically
- Handle reconnection: Re-sync state after reconnect
- Debounce events: Prevent flooding (e.g., typing indicator)
- Clean up listeners: Remove on component unmount
- Acknowledge important events: Confirm critical messages
- Use namespaces: Separate concerns (chat, notifications)
- Scale with Redis: Use Redis adapter for multiple servers
Output Checklist
Every WebSocket implementation should include: