Socket.IO
Socket.IO is a library that builds on WebSockets and adds everything you'd otherwise hand-roll: automatic reconnection, fallback transports, named events, rooms for grouping clients, broadcasting helpers, acknowledgements, and a Redis adapter for scaling across instances. It trades the rawness of ws for a rich, productive API — at the cost of a custom protocol (client and server must both be Socket.IO). This page covers the event-based model, rooms and namespaces, broadcasting, acknowledgements, scaling with the Redis adapter, and how it differs from plain WebSockets.
Socket.IO is not just WebSockets
Setup — server and client
npm install socket.io # server # client (browser): npm install socket.io-client
server
import { createServer } from 'node:http'
import { Server } from 'socket.io'
const httpServer = createServer()
const io = new Server(httpServer, {
cors: { origin: 'https://app.example.com' }, // restrict allowed origins
})
io.on('connection', (socket) => {
console.log('connected:', socket.id)
// Listen for a NAMED event from this client:
socket.on('chat:message', (payload) => {
io.emit('chat:message', { user: socket.id, text: payload.text }) // to everyone
})
socket.on('disconnect', (reason) => console.log('left:', socket.id, reason))
})
httpServer.listen(3000)client (browser)
import { io } from 'socket.io-client'
const socket = io('https://api.example.com')
socket.on('connect', () => console.log('connected as', socket.id))
socket.emit('chat:message', { text: 'hello' }) // send a named event
socket.on('chat:message', (msg) => render(msg)) // receive named events
// Reconnection, transport fallback, and buffering are handled automatically.Rooms — group clients for targeted broadcasts
io.on('connection', (socket) => {
// A socket can join any number of rooms (arbitrary string names):
socket.on('room:join', (roomId) => {
socket.join(roomId)
socket.to(roomId).emit('system', `${socket.id} joined`) // others in the room
})
socket.on('room:message', ({ roomId, text }) => {
// Broadcast to everyone in the room INCLUDING or EXCLUDING the sender:
io.to(roomId).emit('room:message', { from: socket.id, text }) // include sender
// socket.to(roomId).emit(...) → everyone in room EXCEPT the sender
})
})Acknowledgements — request/response over events
// Client emits with a callback — the server invokes it to acknowledge/reply:
socket.emit('order:create', { item: 'book' }, (response) => {
console.log('server acked:', response.orderId) // got the result back
})
// Server receives the ack callback as the last argument:
io.on('connection', (socket) => {
socket.on('order:create', async (data, ack) => {
const order = await createOrder(data)
ack({ ok: true, orderId: order.id }) // call it to "respond"
})
})Scaling with the Redis adapter
npm install @socket.io/redis-adapter redis
import { createAdapter } from '@socket.io/redis-adapter'
import { createClient } from 'redis'
const pubClient = createClient({ url: process.env.REDIS_URL })
const subClient = pubClient.duplicate()
await Promise.all([pubClient.connect(), subClient.connect()])
io.adapter(createAdapter(pubClient, subClient))
// Now io.to(room).emit(...) reaches clients on EVERY instance, not just this one.Socket.IO vs raw ws
Socket.IO | Raw | |
|---|---|---|
Reconnection | Automatic with backoff | You implement it |
Fallback transport | HTTP long-polling if WS fails | None — WS only |
Rooms / broadcasting | Built in | You maintain the maps |
Named events + acks | Built in | You design the protocol |
Multi-instance scaling | Redis adapter | You build the backplane |
Protocol / interop | Custom — both ends must be Socket.IO | Standard WebSocket, interoperable |
Overhead | Larger client, more abstraction | Minimal, closer to the metal |