Overview
Real-time collaborative applications are becoming the standard for modern web experiences. In this comprehensive guide, we'll build a production-ready collaborative dashboard that allows multiple users to view and interact with data simultaneously.
This tutorial assumes you have intermediate knowledge of JavaScript, React, and Node.js. All code examples are production-ready and follow industry best practices.
Table of Contents
Architecture Overview
WebSocket Server Setup
Frontend Implementation
Authentication & Authorization
State Synchronization
Database Design
Testing & Monitoring
Deployment Strategy
Architecture Overview
Our collaborative dashboard will use the following technology stack:
Separating the WebSocket server from the API server allows for better scalability. You can scale each independently based on load patterns.
System Architecture Diagram
āāāāāāāāāāāāāāā āāāāāāāāāāāāāāāā āāāāāāāāāāāāāāā
ā Clients āāāāāāāŗā Load Bal. āāāāāāāŗā WS Srv ā
āāāāāāāāāāāāāāā āāāāāāāāāāāāāāāā āāāāāāāāāāāāāāā
ā ā
ā ā¼
ā āāāāāāāāāāāāāāā
ā ā Redis ā
ā āāāāāāāāāāāāāāā
ā¼ ā
āāāāāāāāāāāāāāāā ā
ā API Server āāāāāāāāāāāāāā
āāāāāāāāāāāāāāāā
... See all 5 lines
WebSocket Server Setup
Basic Socket.io Server
Let's start by creating a robust WebSocket server with connection handling and error recovery.
// server/websocket.js
const socketIO = require ( 'socket.io' ) ;
const jwt = require ( 'jsonwebtoken' ) ;
const Redis = require ( 'ioredis' ) ;
class WebSocketServer {
constructor ( httpServer ) {
this . io = socketIO ( httpServer , {
cors : { origin : process . env . CLIENT_URL , credentials : true } ,
pingTimeout : 60000 ,
pingInterval : 25000
} ) ;
... See all 23 lines
Always implement authentication middleware before allowing socket connections. This prevents unauthorized access to your real-time features.
Connection Handling
// server/handlers/connection.js
handleConnection ( socket ) {
console . log ( ` User connected: ${ socket . username } ( ${ socket . id } ) ` ) ;
// Join user-specific room
socket . join ( ` user: ${ socket . userId } ` ) ;
// Broadcast user presence
socket . broadcast . emit ( 'user:online' , {
userId : socket . userId ,
username : socket . username ,
timestamp : new Date ( )
... See all 17 lines
Frontend Implementation
WebSocket Hook
Create a reusable React hook for managing WebSocket connections.
// hooks/useWebSocket.ts
import { useEffect , useRef , useState } from 'react' ;
import { io , Socket } from 'socket.io-client' ;
interface UseWebSocketOptions {
autoConnect ? : boolean ;
reconnection ? : boolean ;
}
export const useWebSocket = ( options : UseWebSocketOptions = { } ) => {
const [ isConnected , setIsConnected ] = useState ( false ) ;
const [ lastMessage , setLastMessage ] = useState < any > ( null ) ;
... See all 22 lines
Dashboard Component
// components/Dashboard.tsx
import React , { useEffect , useState } from 'react' ;
import { useWebSocket } from '../hooks/useWebSocket' ;
interface Widget {
id : string ;
type : 'chart' | 'table' | 'metric' ;
data : any ;
position : { x : number ; y : number } ;
size : { width : number ; height : number } ;
}
... See all 24 lines
Always clean up event listeners in React components to prevent memory leaks. Use the return function inuseEffect for cleanup.
Authentication & Authorization
JWT Token Generation
// auth/tokenService.js
const jwt = require ( 'jsonwebtoken' ) ;
const crypto = require ( 'crypto' ) ;
class TokenService {
generateAccessToken ( user ) {
return jwt . sign (
{
userId : user . id ,
username : user . username ,
email : user . email ,
role : user . role
... See all 23 lines
Role-Based Access Control
// middleware/authorization.js
const checkPermission = ( requiredRole ) => {
return async ( req , res , next ) => {
const userRole = req . user . role ;
const roleHierarchy = {
'admin' : 3 ,
'editor' : 2 ,
'viewer' : 1
} ;
if ( roleHierarchy [ userRole ] >= roleHierarchy [ requiredRole ] ) {
next ( ) ;
... See all 16 lines
Never trust client-side role checks alone. Always validate permissions on the server side before performing sensitive operations.
State Synchronization
Implementing a simple operational transformation system for conflict resolution.
// sync/operationalTransform.ts
interface Operation {
type : 'insert' | 'delete' | 'update' ;
path : string [ ] ;
value ? : any ;
timestamp : number ;
userId : string ;
}
class OperationalTransform {
private operationQueue : Operation [ ] = [ ] ;
... See all 21 lines
CRDT Implementation
// sync/crdt.js
class CRDT {
constructor ( ) {
this . state = new Map ( ) ;
this . vectorClock = new Map ( ) ;
}
set ( key , value , siteId ) {
const timestamp = this . incrementClock ( siteId ) ;
const entry = { value , timestamp , siteId } ;
if ( ! this . state . has ( key ) ) {
... See all 25 lines
Database Design
PostgreSQL Schema
-- schema.sql
CREATE TABLE users (
id SERIAL PRIMARY KEY ,
username VARCHAR ( 50 ) UNIQUE NOT NULL ,
email VARCHAR ( 255 ) UNIQUE NOT NULL ,
password_hash VARCHAR ( 255 ) NOT NULL ,
role VARCHAR ( 20 ) DEFAULT 'viewer' ,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ,
last_seen TIMESTAMP
) ;
CREATE TABLE dashboards (
... See all 21 lines
Using JSONB for flexible configuration allows you to store widget-specific settings without schema migrations for each widget type.
Database Access Layer
// db/queries.js
const { Pool } = require ( 'pg' ) ;
class DashboardRepository {
constructor ( pool ) {
this . pool = pool ;
}
async getDashboard ( id , userId ) {
const result = await this . pool . query (
` SELECT d . * ,
json_agg ( w . * ) as widgets
... See all 23 lines
Testing & Monitoring
Unit Testing with Jest
// tests/crdt.test.js
const { CRDT } = require ( '../sync/crdt' ) ;
describe ( 'CRDT' , ( ) => {
let crdt ;
beforeEach ( ( ) => {
crdt = new CRDT ( ) ;
} ) ;
test ( 'should set and get values' , ( ) => {
crdt . set ( 'counter' , 5 , 'site1' ) ;
... See all 21 lines
Integration Testing
// tests/websocket.integration.test.js
const io = require ( 'socket.io-client' ) ;
const { createServer } = require ( '../server' ) ;
describe ( 'WebSocket Integration' , ( ) => {
let server , clientSocket ;
beforeAll ( ( done ) => {
server = createServer ( ) ;
server . listen ( 3001 , done ) ;
} ) ;
... See all 24 lines
Use separate test databases and mock external services to ensure tests are isolated and repeatable.
Monitoring Setup
// monitoring/metrics.js
const prometheus = require ( 'prom-client' ) ;
const register = new prometheus . Registry ( ) ;
const wsConnections = new prometheus . Gauge ( {
name : 'ws_active_connections' ,
help : 'Number of active WebSocket connections' ,
registers : [ register ]
} ) ;
const messageRate = new prometheus . Counter ( {
... See all 14 lines
Deployment Strategy
Docker Composition
# docker-compose.yml
version : '3.8'
services :
postgres :
image : postgres : 15 - alpine
environment :
POSTGRES_DB : dashboard_db
POSTGRES_USER : admin
POSTGRES_PASSWORD : $ { DB_PASSWORD }
volumes :
- postgres_data : /var/lib/postgresql/data
... See all 34 lines
Always use environment variables for sensitive configuration. Never commit credentials to version control.
Kubernetes Deployment
# k8s/deployment.yaml
apiVersion : apps/v1
kind : Deployment
metadata :
name : websocket - server
spec :
replicas : 3
selector :
matchLabels :
app : websocket
template :
metadata :
... See all 26 lines
Load Balancing Configuration
# nginx.conf
upstream websocket_backend {
ip_hash ;
server websocket1:4001 ;
server websocket2:4001 ;
server websocket3:4001 ;
}
upstream api_backend {
least_conn ;
server api1:4000 ;
server api2:4000 ;
... See all 21 lines
Redis Caching Strategy
// cache/redisCache.js
const Redis = require ( 'ioredis' ) ;
class CacheManager {
constructor ( ) {
this . redis = new Redis ( process . env . REDIS_URL ) ;
}
async get ( key ) {
const value = await this . redis . get ( key ) ;
return value ? JSON . parse ( value ) : null ;
}
... See all 21 lines
Message Queue Processing
# workers/queue_processor.py
import redis
import json
from typing import Dict , Any
class MessageProcessor :
def __init__ ( self , redis_url : str ) :
self . redis = redis . from_url ( redis_url )
self . pubsub = self . redis . pubsub ( )
def process_message ( self , message : Dict [ str , Any ] ) - > None :
msg_type = message . get ( 'type' )
... See all 22 lines
Security Considerations
// security/sanitizer.js
const validator = require ( 'validator' ) ;
const DOMPurify = require ( 'isomorphic-dompurify' ) ;
class InputSanitizer {
static sanitizeHTML ( input ) {
return DOMPurify . sanitize ( input , {
ALLOWED_TAGS : [ 'b' , 'i' , 'em' , 'strong' , 'a' , 'p' ] ,
ALLOWED_ATTR : [ 'href' ]
} ) ;
}
... See all 22 lines
Always sanitize user input before storing or displaying it. XSS attacks can compromise your entire application.
Rate Limiting
// middleware/rateLimiter.js
const rateLimit = require ( 'express-rate-limit' ) ;
const RedisStore = require ( 'rate-limit-redis' ) ;
const Redis = require ( 'ioredis' ) ;
const createRateLimiter = ( options = { } ) => {
return rateLimit ( {
store : new RedisStore ( {
client : new Redis ( process . env . REDIS_URL ) ,
prefix : 'rl:'
} ) ,
windowMs : options . windowMs || 15 * 60 * 1000 ,
... See all 14 lines
Error Handling
Global Error Handler
// middleware/errorHandler.ts
import { Request , Response , NextFunction } from 'express' ;
class AppError extends Error {
statusCode : number ;
isOperational : boolean ;
constructor ( message : string , statusCode : number ) {
super ( message ) ;
this . statusCode = statusCode ;
this . isOperational = true ;
Error . captureStackTrace ( this , this . constructor ) ;
... See all 20 lines
Conclusion
Building a real-time collaborative dashboard requires careful consideration of many factors:
ā
Key Achievements
Real-time synchronization across multiple clients
Scalable architecture using WebSockets and Redis
Robust authentication with JWT tokens
Conflict resolution using CRDT and OT
Production-ready deployment configurations
Start small and scale gradually. Monitor your metrics closely and optimize bottlenecks as they appear.
š Next Steps
Add offline support using service workers
Implement undo/redo functionality
Add real-time analytics tracking
Build mobile applications using React Native
Enhance security with 2FA and audit logs
š Further Reading
FAQ
Q: How do I handle connection drops?
A: Implement exponential backoff for reconnection attempts and queue messages during disconnection.
Q: Can this scale to millions of users?
A: Yes, but you'll need to implement additional infrastructure like message brokers and database sharding.
Q: What about mobile support?
A: Socket.io works great on mobile. Consider battery optimization and use background services wisely.
GitHub Repository : github.com/example/realtime-dashboard
Live Demo : dashboard.example.com
Documentation : docs.example.com
Built with ā¤ļø by the engineering team
Last Updated : January 4, 2026
Version : 2.1.0
License : MIT
This guide is maintained regularly. Star the repository to get notified of updates!