WebSocket on TypeScript: Building Advanced Real-Time Applications
\n\nReal-time applications have become an integral part of the modern web. From chats and notifications to online games and trading platforms, WebSocket technology provides a persistent two-way connection between client and server. At an advanced level, developing such systems requires not only understanding the protocol but also the ability to build fault-tolerant, scalable architectures. In this article, we will explore how to create production-grade WebSocket applications in TypeScript using best practices and patterns.
\n\n1. Architecture and Clustering of a WebSocket Server
\n\nA single WebSocket server cannot handle the load of a real project. The key challenge is to organize clustering and message exchange between instances. Let's consider an architecture using the ws library and a Redis event bus.
Install the dependencies:
\nnpm install ws redis ioredis @types/ws @types/ioredis\n\nCreate a basic clustered server:
\n\nimport { WebSocketServer, WebSocket } from 'ws';\nimport Redis from 'ioredis';\n\nconst redisPub = new Redis();\nconst redisSub = new Redis();\n\nconst wss = new WebSocketServer({ port: 8080 });\n\n// Channel for inter-server communication\nconst CHANNEL = 'ws:messages';\n\nredisSub.subscribe(CHANNEL, (err, count) => {\n if (err) console.error('Subscription error:', err);\n console.log(`Subscribed to ${count} channels`);\n});\n\n// Receiving messages from other instances\nredisSub.on('message', (channel, message) => {\n const parsed = JSON.parse(message);\n wss.clients.forEach((client) => {\n if (client.readyState === WebSocket.OPEN) {\n client.send(parsed.data);\n }\n });\n});\n\nwss.on('connection', (ws) => {\n console.log('New connection');\n\n ws.on('message', (data) => {\n const message = data.toString();\n // Publish to Redis for all instances\n redisPub.publish(CHANNEL, JSON.stringify({ data: message }));\n });\n});\n\nAdvantages:
\n- \n
- Horizontal scaling — add as many servers as needed \n
- Fault tolerance — if one instance fails, others continue working \n
- Unified message bus via Redis Pub/Sub \n
2. Authentication and Session Management
\n\nIn production applications, unauthenticated connections cannot be trusted. Let's implement token verification via JWT when establishing a connection.
\n\nInstall jsonwebtoken:
npm install jsonwebtoken @types/jsonwebtoken\n\nCreate a middleware for WebSocket authentication:
\n\nimport { IncomingMessage } from 'http';\nimport jwt from 'jsonwebtoken';\n\nconst JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';\n\ninterface AuthenticatedSocket extends WebSocket {\n userId?: string;\n}\n\nfunction authenticate(ws: AuthenticatedSocket, req: IncomingMessage): boolean {\n const url = new URL(req.url || '', 'http://localhost');\n const token = url.searchParams.get('token');\n\n if (!token) {\n ws.close(4001, 'Token not provided');\n return false;\n }\n\n try {\n const decoded = jwt.verify(token, JWT_SECRET) as { userId: string };\n ws.userId = decoded.userId;\n console.log(`User ${decoded.userId} authenticated`);\n return true;\n } catch (err) {\n ws.close(4002, 'Invalid token');\n return false;\n }\n}\n\n// Usage\nwss.on('connection', (ws: AuthenticatedSocket, req) => {\n if (!authenticate(ws, req)) return;\n\n ws.on('message', (data) => {\n // Now we know the userId\n console.log(`Message from ${ws.userId}: ${data}`);\n });\n});\n\nRecommendations:
\n- \n
- Pass the token via a query parameter or headers when establishing the connection \n
- Use short-lived access tokens and refresh tokens \n
- Store sessions in Redi