← Back to articles

How to Implement WebSockets in Your App (2026)

WebSockets enable real-time bidirectional communication between client and server. Here's how to implement them for chat, collaboration, and live updates.

WebSockets vs Alternatives

TechnologyBest ForLatency
WebSocketsChat, collaborative editing~5ms
Server-Sent Events (SSE)Live dashboards, one-way updates~10ms
Long pollingLegacy browser support~500ms
Supabase RealtimeDatabase subscriptions~50ms

Option 1: Socket.IO (Easiest)

Socket.IO handles reconnection, room management, and fallbacks automatically.

Server Setup (Node.js/Express)

import { createServer } from 'http'
import { Server } from 'socket.io'
import express from 'express'

const app = express()
const httpServer = createServer(app)
const io = new Server(httpServer, {
  cors: { origin: process.env.CLIENT_URL },
})

io.on('connection', (socket) => {
  console.log('Client connected:', socket.id)

  // Join a room
  socket.on('join', (roomId) => {
    socket.join(roomId)
    io.to(roomId).emit('user-joined', { userId: socket.id })
  })

  // Broadcast message to room
  socket.on('message', ({ roomId, message }) => {
    io.to(roomId).emit('message', { userId: socket.id, message, timestamp: Date.now() })
  })

  // Handle disconnect
  socket.on('disconnect', () => {
    console.log('Client disconnected:', socket.id)
  })
})

httpServer.listen(3001)

Client Setup (React)

'use client'
import { useEffect, useState } from 'react'
import { io, Socket } from 'socket.io-client'

function ChatRoom({ roomId }: { roomId: string }) {
  const [socket, setSocket] = useState<Socket | null>(null)
  const [messages, setMessages] = useState<Message[]>([])
  const [input, setInput] = useState('')

  useEffect(() => {
    const newSocket = io('http://localhost:3001')
    setSocket(newSocket)

    newSocket.emit('join', roomId)

    newSocket.on('message', (msg) => {
      setMessages(prev => [...prev, msg])
    })

    return () => {
      newSocket.close()
    }
  }, [roomId])

  const sendMessage = () => {
    if (!socket || !input) return
    socket.emit('message', { roomId, message: input })
    setInput('')
  }

  return (
    <div>
      <div className="messages">
        {messages.map((msg, i) => (
          <div key={i}>
            <strong>{msg.userId}:</strong> {msg.message}
          </div>
        ))}
      </div>
      <input value={input} onChange={e => setInput(e.target.value)} />
      <button onClick={sendMessage}>Send</button>
    </div>
  )
}

Option 2: Native WebSockets (More Control)

Server (Node.js with ws)

import { WebSocketServer } from 'ws'

const wss = new WebSocketServer({ port: 3001 })

const rooms = new Map<string, Set<WebSocket>>()

wss.on('connection', (ws) => {
  ws.on('message', (data) => {
    const msg = JSON.parse(data.toString())

    if (msg.type === 'join') {
      if (!rooms.has(msg.roomId)) rooms.set(msg.roomId, new Set())
      rooms.get(msg.roomId)!.add(ws)
    }

    if (msg.type === 'message') {
      const room = rooms.get(msg.roomId)
      if (room) {
        room.forEach(client => {
          if (client.readyState === WebSocket.OPEN) {
            client.send(JSON.stringify({ type: 'message', ...msg }))
          }
        })
      }
    }
  })

  ws.on('close', () => {
    rooms.forEach(room => room.delete(ws))
  })
})

Client

const ws = new WebSocket('ws://localhost:3001')

ws.onopen = () => {
  ws.send(JSON.stringify({ type: 'join', roomId: '123' }))
}

ws.onmessage = (event) => {
  const msg = JSON.parse(event.data)
  console.log('Received:', msg)
}

ws.send(JSON.stringify({ type: 'message', roomId: '123', text: 'Hello!' }))

Option 3: Supabase Realtime (Database Subscriptions)

Perfect for syncing database changes in real-time:

import { createClient } from '@supabase/supabase-js'

const supabase = createClient(url, key)

// Subscribe to table changes
const channel = supabase
  .channel('posts')
  .on('postgres_changes', {
    event: '*',
    schema: 'public',
    table: 'posts',
  }, (payload) => {
    console.log('Change:', payload)
  })
  .subscribe()

Scaling WebSockets

Single server works for small apps. For scale:

1. Redis Adapter (Socket.IO)

import { createAdapter } from '@socket.io/redis-adapter'
import { createClient } from 'redis'

const pubClient = createClient({ url: 'redis://localhost:6379' })
const subClient = pubClient.duplicate()

await Promise.all([pubClient.connect(), subClient.connect()])

io.adapter(createAdapter(pubClient, subClient))

Now multiple Socket.IO servers share connection state via Redis.

2. Serverless WebSockets (AWS/Cloudflare)

Use managed WebSocket services:

  • AWS API Gateway WebSockets — serverless, scales automatically
  • Cloudflare Durable Objects — global, low-latency WebSockets

Authentication

// Server
io.use(async (socket, next) => {
  const token = socket.handshake.auth.token
  
  try {
    const user = await verifyToken(token)
    socket.data.userId = user.id
    next()
  } catch (err) {
    next(new Error('Authentication failed'))
  }
})

// Client
const socket = io('http://localhost:3001', {
  auth: { token: userToken },
})

FAQ

Socket.IO vs native WebSockets?

Socket.IO for ease of use (reconnection, rooms, broadcasting). Native WebSockets for maximum control and performance.

How do I deploy WebSockets on Vercel?

You can't — Vercel doesn't support long-lived WebSocket connections. Use a separate server (Railway, Fly.io) or Supabase Realtime.

Can I use WebSockets with Next.js?

Yes, but run the WebSocket server separately. Next.js serverless functions time out after 10 seconds.

Bottom Line

Use Socket.IO for chat and collaborative features. Use Supabase Realtime for database subscriptions. Deploy WebSocket servers to Railway or Fly.io (not Vercel). Add authentication and scaling (Redis adapter) as you grow.

Get AI tool guides in your inbox

Weekly deep-dives on the best AI coding tools, automation platforms, and productivity software.