Back to BlogReal-Time & WebSockets

Building Real-Time Apps with WebSockets: Chat, Notifications, and Live Collaboration

October 23, 2025
7 min read

WebSockets enable instant, bidirectional communication between browsers and servers. Learn how to build real-time chat, live notifications, and collaborative editing with WebSocket APIs and AWS infrastructure.

Why WebSockets Over HTTP Polling?

Traditional HTTP polling wastes bandwidth and adds latency:

  • HTTP Polling: Client requests updates every 1-5 seconds (even when no data changed)
  • Long Polling: Server holds request open until data available (complex, resource-intensive)
  • WebSockets: Persistent connection, instant bidirectional messages (under 50ms latency)

Performance Comparison

Scenario: 1,000 concurrent users, 1 update per minute

HTTP Polling (1s interval):
  Requests: 60,000/minute (1,000 users x 60 polls)
  Data transferred: ~12 MB/min (overhead)
  Server load: High (constant request processing)
  Latency: 0-1000ms (depends on poll timing)

WebSockets:
  Requests: 1,000 connections (persistent)
  Data transferred: ~50 KB/min (only actual updates)
  Server load: Low (idle connections)
  Latency: under 50ms (instant push)

Result: 99.6% reduction in server requests

WebSocket Architecture on AWS

1. AWS API Gateway WebSocket API

Managed WebSocket infrastructure with auto-scaling:

# Create WebSocket API
aws apigatewayv2 create-api \
  --name "SnapIT-Chat-WebSocket" \
  --protocol-type WEBSOCKET \
  --route-selection-expression '$request.body.action'

# Routes:
# $connect    -> onConnect Lambda (authenticate user)
# $disconnect -> onDisconnect Lambda (cleanup)
# sendMessage -> sendMessage Lambda (broadcast to users)
# $default    -> default Lambda (error handling)

2. Connection Management with DynamoDB

Store active connections for message broadcasting:

// DynamoDB Table: WebSocketConnections
{
  connectionId: "abc123def456",    // Primary Key
  userId: "user_789",
  roomId: "room_general",
  connectedAt: 1697654400000,
  ttl: 1697740800  // Auto-delete after 24 hours
}

// onConnect Lambda
exports.handler = async (event) => {
  const connectionId = event.requestContext.connectionId
  const userId = event.queryStringParameters.userId

  await dynamodb.put({
    TableName: 'WebSocketConnections',
    Item: {
      connectionId,
      userId,
      connectedAt: Date.now(),
      ttl: Math.floor(Date.now() / 1000) + 86400
    }
  }).promise()

  return { statusCode: 200, body: 'Connected' }
}

Building a Real-Time Chat App

Frontend: React WebSocket Client

import { useEffect, useState } from 'react'

const ChatApp = ({ userId, roomId }) => {
  const [ws, setWs] = useState(null)
  const [messages, setMessages] = useState([])
  const [input, setInput] = useState('')

  useEffect(() => {
    // Connect to WebSocket API
    const wsUrl = 'wss://your-api-id.execute-api.us-east-1.amazonaws.com/prod'
      + '?userId=' + userId + '&roomId=' + roomId
    const socket = new WebSocket(wsUrl)

    socket.onopen = () => console.log('Connected')
    socket.onmessage = (event) => {
      const message = JSON.parse(event.data)
      setMessages(prev => [...prev, message])
    }
    socket.onerror = (error) => console.error('WebSocket error:', error)
    socket.onclose = () => console.log('Disconnected')

    setWs(socket)
    return () => socket.close()
  }, [userId, roomId])

  const sendMessage = () => {
    ws.send(JSON.stringify({
      action: 'sendMessage',
      roomId,
      userId,
      message: input,
      timestamp: Date.now()
    }))
    setInput('')
  }

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

Backend: Message Broadcasting Lambda

const AWS = require('aws-sdk')
const dynamodb = new AWS.DynamoDB.DocumentClient()
const apiGateway = new AWS.ApiGatewayManagementApi({
  endpoint: process.env.WEBSOCKET_ENDPOINT
})

exports.handler = async (event) => {
  const { roomId, userId, message } = JSON.parse(event.body)

  // Get all connections in room
  const connections = await dynamodb.query({
    TableName: 'WebSocketConnections',
    IndexName: 'roomId-index',
    KeyConditionExpression: 'roomId = :roomId',
    ExpressionAttributeValues: { ':roomId': roomId }
  }).promise()

  // Broadcast message to all room members
  const postToConnection = async (connectionId) => {
    try {
      await apiGateway.postToConnection({
        ConnectionId: connectionId,
        Data: JSON.stringify({ userId, message, timestamp: Date.now() })
      }).promise()
    } catch (error) {
      if (error.statusCode === 410) {
        // Connection stale, remove from DB
        await dynamodb.delete({
          TableName: 'WebSocketConnections',
          Key: { connectionId }
        }).promise()
      }
    }
  }

  await Promise.all(
    connections.Items.map(conn => postToConnection(conn.connectionId))
  )

  return { statusCode: 200, body: 'Message sent' }
}

SnapIT Real-Time Infrastructure

Deploy production-ready WebSocket chat, notifications, and collaboration features in minutes. Pre-built AWS CDK stacks with API Gateway, Lambda, and DynamoDB.

Explore Real-Time Solutions

Advanced Use Cases

1. Live Notifications

Push alerts to users instantly:

  • New order notifications (e-commerce)
  • Friend requests (social networks)
  • System alerts (SaaS dashboards)
  • Price drop alerts (price tracking)

2. Collaborative Editing

Real-time document collaboration like Google Docs:

// Operational Transformation (OT) for conflict resolution
const applyOperation = (doc, operation) => {
  switch (operation.type) {
    case 'insert':
      return doc.slice(0, operation.position) +
             operation.text +
             doc.slice(operation.position)
    case 'delete':
      return doc.slice(0, operation.position) +
             doc.slice(operation.position + operation.length)
  }
}

// Broadcast cursor positions
ws.send(JSON.stringify({
  action: 'cursorMove',
  userId,
  position: { line: 5, column: 12 },
  color: '#db2777'
}))

3. Live Dashboards

Real-time metrics for analytics platforms:

  • Active users count (updates every second)
  • Revenue tracking (live sales notifications)
  • System health monitoring (server metrics)
  • Social media engagement (likes, shares in real-time)

Security & Best Practices

1. Authentication

// Validate JWT token in $connect route
const jwt = require('jsonwebtoken')

exports.handler = async (event) => {
  const token = event.queryStringParameters.token

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET)
    // Store userId from decoded token
    await storeConnection(event.requestContext.connectionId, decoded.userId)
    return { statusCode: 200 }
  } catch (error) {
    return { statusCode: 401, body: 'Unauthorized' }
  }
}

2. Rate Limiting

Prevent abuse with message rate limits:

  • Max 10 messages per second per user
  • Max 1,000 connections per IP
  • DynamoDB TTL for auto-cleanup of stale connections

3. Error Handling

  • Automatic reconnection with exponential backoff
  • Message queue for offline users (store in DynamoDB)
  • Dead letter queue (DLQ) for failed broadcasts

Cost Analysis

Scenario: Chat app with 10,000 concurrent users, 1 million messages per day

  • API Gateway WebSocket: $1.00/million messages + $0.25/million connection minutes = $3.47/day
  • Lambda executions: 1M invocations x 200ms x 512MB = $1.67/day
  • DynamoDB: 1M writes, 10M reads = $2.75/day
  • Total: $7.89/day ($236/month for 10K users = $0.024 per user/month)

Conclusion

WebSockets transform user experience with instant updates and real-time collaboration. AWS API Gateway + Lambda + DynamoDB provides a scalable, cost-effective infrastructure for chat apps, live notifications, and collaborative tools. With sub-50ms latency and 99.6% fewer server requests than HTTP polling, WebSockets are essential for modern interactive applications.