Skip to content

Implementing Real-Time Notifications in Node.js with Socket.io

· 22 min read

Implementing Real-Time Notifications in Node.js with Socket.io

Real-time notifications enhance user engagement by instantly delivering updates on key events, such as new messages, friend requests, or order updates. Socket.io makes it easy to implement real-time notifications in Node.js by establishing a WebSocket connection with the client. This guide walks through setting up a Socket.io server, handling events, and pushing notifications to clients in real-time.

Real-Time Notification Architecture

graph TB
    subgraph "Client Layer"
        WEB[Web Browser<br/>JavaScript Client]
        MOBILE[Mobile App<br/>React Native/Flutter]
        DESKTOP[Desktop App<br/>Electron]
    end
    
    subgraph "WebSocket Connection"
        SOCKETIO[Socket.io<br/>Real-time Communication]
        FALLBACK[Connection Fallbacks<br/>WebSocket → Polling → XHR]
        ROOMS[Room/Namespace Management<br/>User grouping & targeting]
    end
    
    subgraph "Node.js Server"
        EXPRESS[Express Server<br/>HTTP + WebSocket]
        MIDDLEWARE[Socket Middleware<br/>Auth, Rate Limiting]
        HANDLERS[Event Handlers<br/>Connection, Message, Disconnect]
    end
    
    subgraph "Notification Engine"
        TRIGGER[Event Triggers<br/>DB changes, User actions, Timers]
        QUEUE[Message Queue<br/>Redis Pub/Sub, RabbitMQ]
        BROADCAST[Broadcast Logic<br/>Target specific users/rooms]
    end
    
    subgraph "Data Sources"
        DATABASE[(Database<br/>User data, Notifications)]
        CACHE[(Cache<br/>Active connections, Sessions)]
        EXTERNAL[External APIs<br/>Third-party services]
    end
    
    subgraph "Notification Types"
        PERSONAL[Personal Notifications<br/>Private messages, Updates]
        GROUP[Group Notifications<br/>Team announcements]
        BROADCAST_ALL[Global Broadcasts<br/>System-wide alerts]
        TARGETED[Targeted Notifications<br/>Role/location based]
    end
    
    WEB --> SOCKETIO
    MOBILE --> SOCKETIO
    DESKTOP --> SOCKETIO
    
    SOCKETIO --> FALLBACK
    SOCKETIO --> ROOMS
    
    FALLBACK --> EXPRESS
    ROOMS --> MIDDLEWARE
    
    EXPRESS --> HANDLERS
    MIDDLEWARE --> HANDLERS
    
    HANDLERS --> TRIGGER
    TRIGGER --> QUEUE
    QUEUE --> BROADCAST
    
    BROADCAST --> PERSONAL
    BROADCAST --> GROUP
    BROADCAST --> BROADCAST_ALL
    BROADCAST --> TARGETED
    
    TRIGGER --> DATABASE
    QUEUE --> CACHE
    BROADCAST --> EXTERNAL
    
    PERSONAL --> WEB
    GROUP --> MOBILE
    BROADCAST_ALL --> DESKTOP
    
    style SOCKETIO fill:#e8f5e8
    style QUEUE fill:#e1f5fe
    style BROADCAST fill:#fff3e0
    style PERSONAL fill:#f3e5f5

Why Use Real-Time Notifications?

Real-time notifications are invaluable for applications where timely updates improve the user experience, such as:

  1. Messaging Apps: Instantly notify users of new messages or replies.
  2. E-commerce: Update users on order status changes or promotional offers.
  3. Social Media: Alert users to likes, comments, and friend requests as they happen.

With WebSockets, the server can push updates to the client without waiting for client-side requests, ensuring instant delivery.


Setting Up the Project

This guide assumes a basic Node.js and Express setup. You’ll use Socket.io to manage WebSocket connections and Express for setting up the server.

Step 1: Install Required Dependencies

Initialize a new project if you’re starting fresh, and install Express and Socket.io.

# @filename: script.sh
mkdir real-time-notifications
cd real-time-notifications
npm init -y
npm install express socket.io
  • express: To create the server.
  • socket.io: For managing WebSocket connections.

Setting Up the Socket.io Server

To manage notifications, configure a Socket.io server that will handle connections, broadcast messages, and push notifications to connected clients.

Step 1: Configuring Socket.io with Express

Create a server.js file to set up an Express server with Socket.io integrated.

server.js

// @filename: app.js
const express = require('express')
const http = require('http')
const { Server } = require('socket.io')

const app = express()
const server = http.createServer(app)
const io = new Server(server, {
  cors: {
    origin: '*', // Allow requests from any origin; restrict this in production
  },
})

const port = process.env.PORT || 5000

// Socket.io connection handler
io.on('connection', (socket) => {
  console.log('A user connected:', socket.id)

  // Handle custom notification event
  socket.on('sendNotification', (data) => {
    console.log('Notification received:', data)
    io.emit('receiveNotification', data) // Broadcast to all connected clients
  })

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

server.listen(port, () => {
  console.log(`Server running on port ${port}`)
})

In this code:

  • io.on("connection"): Listens for new connections. Each connected client is assigned a unique socket.id.
  • Custom Events: The server listens for sendNotification events and broadcasts receiveNotification events to all clients.
  • io.emit("receiveNotification"): Sends a notification to all connected clients, keeping everyone updated.

Socket.io Communication Flow

sequenceDiagram
    participant Client1 as Web Client 1
    participant Client2 as Mobile Client 2
    participant Server as Socket.io Server
    participant DB as Database
    participant Queue as Message Queue
    participant Service as External Service
    
    Note over Client1,Service: Initial Connection & Authentication
    Client1->>Server: connect()
    Server->>Server: Generate socket.id
    Server->>DB: Validate user session
    DB-->>Server: User authenticated
    Server-->>Client1: connection established + socket.id
    
    Client2->>Server: connect()
    Server->>Server: Generate socket.id
    Server-->>Client2: connection established + socket.id
    
    Note over Client1,Service: Room/Namespace Management
    Client1->>Server: join('user-123')
    Server->>Server: Add socket to room 'user-123'
    Server-->>Client1: joined room 'user-123'
    
    Note over Client1,Service: Real-time Notification Flow
    Service->>Queue: New order created for user-123
    Queue->>Server: notification event
    Server->>DB: Get user preferences
    DB-->>Server: notification settings
    Server->>Server: to('user-123').emit('orderUpdate', data)
    Server-->>Client1: orderUpdate: { orderId: 456, status: 'confirmed' }
    
    Note over Client1,Service: Client-to-Client Communication
    Client1->>Server: sendMessage({ to: 'user-456', message: 'Hello!' })
    Server->>Server: Validate permissions
    Server->>DB: Log message
    Server->>Server: to('user-456').emit('newMessage', data)
    Server-->>Client2: newMessage: { from: 'user-123', message: 'Hello!' }
    
    Note over Client1,Service: Broadcast Notifications
    Server->>Server: System maintenance alert
    Server->>Server: io.emit('systemAlert', data)
    Server-->>Client1: systemAlert: { type: 'maintenance', time: '2024-01-01T02:00:00Z' }
    Server-->>Client2: systemAlert: { type: 'maintenance', time: '2024-01-01T02:00:00Z' }
    
    Note over Client1,Service: Connection Management
    Client1->>Server: disconnect()
    Server->>Server: Remove from all rooms
    Server->>DB: Update user status to offline
    Server->>Server: socket.broadcast.emit('userOffline', userId)
    Server-->>Client2: userOffline: { userId: 'user-123' }

Sending Notifications from the Server

In addition to handling events from clients, you can send notifications from the server, such as alerts, updates, or reminders.

Step 1: Triggering Notifications from the Server

Create a new file, notifyService.js, to manage sending notifications from the server.

notifyService.js

// @filename: config.js
const sendNotification = (io, data) => {
  io.emit('receiveNotification', data)
}

module.exports = sendNotification

Step 2: Using the Notification Service in server.js

Import and use the sendNotification function to trigger notifications from server events or schedules.

server.js

// @filename: app.js
const express = require('express')
const http = require('http')
const { Server } = require('socket.io')
const sendNotification = require('./notifyService')

const app = express()
const server = http.createServer(app)
const io = new Server(server, {
  cors: {
    origin: '*',
  },
})

const port = process.env.PORT || 5000

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

  socket.on('sendNotification', (data) => {
    sendNotification(io, data)
  })

  socket.on('disconnect', () => {
    console.log('A user disconnected:', socket.id)
  })
})

// Trigger a test notification every 10 seconds
setInterval(() => {
  const testNotification = { message: 'This is a test notification!' }
  sendNotification(io, testNotification)
}, 10000)

server.listen(port, () => {
  console.log(`Server running on port ${port}`)
})

In this example:

  • A test notification is sent every 10 seconds to all clients to demonstrate server-driven notifications.
  • Notifications are triggered by calling sendNotification(io, data) with a notification message.

Setting Up the Client-Side to Receive Notifications

To handle notifications on the client side, you’ll set up Socket.io on a simple HTML/JavaScript client that connects to the server and listens for events.

Step 1: Creating the Client-Side Code

Create an index.html file with a simple setup to display notifications.

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Real-Time Notifications</title>
    <script src="https://cdn.socket.io/4.0.0/socket.io.min.js"></script>
  </head>
  <body>
    <h1>Real-Time Notifications</h1>
    <div id="notifications"></div>

    <script>
      const socket = io('http://localhost:5000')

      // Listen for notifications from the server
      socket.on('receiveNotification', (data) => {
        const notificationElement = document.createElement('div')
        notificationElement.textContent = `Notification: ${data.message}`
        document
          .getElementById('notifications')
          .appendChild(notificationElement)
      })

      // Emit a test notification from the client
      socket.emit('sendNotification', { message: 'Hello from the client!' })
    </script>
  </body>
</html>

In this example:

  • The client establishes a connection to the Socket.io server.
  • It listens for receiveNotification events and displays each notification message.
  • The client also emits a sendNotification event to test sending notifications from the client.

Step 2: Testing the Setup

  1. Start the Server: Run the server with node server.js.
  2. Open index.html: Open the index.html file in a browser.
  3. View Notifications: Check the notifications displayed in the browser, including the server’s scheduled messages.

Adding Real-Time Notifications to Specific Events

Real-time notifications are most useful when tied to specific actions, like sending a notification when a new message arrives or when an order status changes.

Example: Sending a Notification on a New Message Event

In a chat application, you might send a notification whenever a new message is sent.

server.js

// @filename: index.js
io.on('connection', (socket) => {
  console.log('A user connected:', socket.id)

  // Listen for a new message event
  socket.on('newMessage', (message) => {
    console.log('New message:', message)
    io.emit('receiveNotification', { message: `New message: ${message}` })
  })

  socket.on('disconnect', () => {
    console.log('A user disconnected:', socket.id)
  })
})

In this code:

  1. The server listens for a newMessage event.
  2. When a new message is received, the server broadcasts a receiveNotification event with the message content.

On the client side, you can trigger this event by emitting newMessage with a message body.


Socket.io Targeting and Broadcasting Strategies

graph TB
    subgraph "Broadcasting Strategies"
        GLOBAL[Global Broadcast<br/>io.emit('event', data)<br/>All connected clients]
        ROOM[Room Broadcast<br/>io.to('room-name').emit()<br/>Specific group/channel]
        NAMESPACE[Namespace Broadcast<br/>io.of('/admin').emit()<br/>Isolated context]
        PRIVATE[Private Message<br/>socket.to(socketId).emit()<br/>Individual user]
    end
    
    subgraph "Room Management"
        JOIN[Join Room<br/>socket.join('room-id')]
        LEAVE[Leave Room<br/>socket.leave('room-id')]
        AUTO_JOIN[Auto Join<br/>User ID, Role-based]
        DYNAMIC[Dynamic Rooms<br/>Chat groups, Projects]
    end
    
    subgraph "User Targeting"
        USER_ID[By User ID<br/>Authenticated users]
        ROLE_BASED[By Role<br/>Admin, Manager, Employee]
        LOCATION[By Location<br/>Geographic targeting]
        DEVICE[By Device Type<br/>Mobile, Desktop, Tablet]
    end
    
    subgraph "Notification Types"
        INSTANT[Instant Notifications<br/>Chat messages, Alerts]
        QUEUED[Queued Notifications<br/>Email digest, Batch updates]
        PERSISTENT[Persistent Notifications<br/>Unread counts, Status]
        EPHEMERAL[Ephemeral Notifications<br/>Typing indicators, Presence]
    end
    
    subgraph "Scaling Considerations"
        SINGLE[Single Server<br/>In-memory rooms]
        REDIS[Redis Adapter<br/>Multi-server coordination]
        CLUSTER[Cluster Mode<br/>Horizontal scaling]
        LOAD_BALANCE[Load Balancing<br/>Sticky sessions]
    end
    
    subgraph "Security & Performance"
        AUTH[Authentication<br/>JWT token validation]
        RATE_LIMIT[Rate Limiting<br/>Prevent spam/abuse]
        COMPRESSION[Message Compression<br/>Reduce bandwidth]
        HEARTBEAT[Connection Health<br/>Ping/pong monitoring]
    end
    
    GLOBAL --> JOIN
    ROOM --> AUTO_JOIN
    NAMESPACE --> DYNAMIC
    PRIVATE --> LEAVE
    
    JOIN --> USER_ID
    AUTO_JOIN --> ROLE_BASED
    DYNAMIC --> LOCATION
    LEAVE --> DEVICE
    
    USER_ID --> INSTANT
    ROLE_BASED --> QUEUED
    LOCATION --> PERSISTENT
    DEVICE --> EPHEMERAL
    
    INSTANT --> SINGLE
    QUEUED --> REDIS
    PERSISTENT --> CLUSTER
    EPHEMERAL --> LOAD_BALANCE
    
    SINGLE --> AUTH
    REDIS --> RATE_LIMIT
    CLUSTER --> COMPRESSION
    LOAD_BALANCE --> HEARTBEAT
    
    style GLOBAL fill:#e8f5e8
    style ROOM fill:#e1f5fe
    style PRIVATE fill:#fff3e0
    style REDIS fill:#f3e5f5
    style AUTH fill:#ffebee

Best Practices for Real-Time Notifications

  1. Manage Connection Lifetimes: Use disconnect and reconnect events to monitor user connections and clean up resources when necessary.
  2. Avoid Overloading Clients: Only send necessary notifications to avoid overwhelming clients with frequent updates.
  3. Namespace Connections: Use namespaces if you need to create multiple isolated channels for different types of notifications.
  4. Broadcast Selectively: Only broadcast to relevant users by using rooms or private messaging for targeted notifications.

Conclusion

Implementing real-time notifications in Node.js with Socket.io enables applications to deliver immediate updates to users, enhancing engagement and usability. By setting up a WebSocket connection and handling custom events, you can push notifications to clients as they happen, creating a dynamic and interactive experience.

This setup is ideal for applications that rely on timely updates, such as chat apps, social media, and real-time dashboards. Integrate these techniques to provide a smooth, real-time notification experience for your users.

Share: