Skip to content

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

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.

Node.js JavaScript Backend Real-time
Share:

Continue Reading

Implementing Email Notifications in Node.js with Nodemailer and Cron Jobs

Email notifications keep users informed and engaged by providing timely updates on important events. By combining Nodemailer with cron jobs, you can automate email notifications in a Node.js application, whether it’s for daily reports, reminders, or promotional updates. This guide walks through setting up scheduled email notifications, covering the basics of Nodemailer and the use of cron jobs to handle recurring tasks.

Read article
Node.jsJavaScriptBackend

Implementing Request Throttling in Node.js with node-cache

Request throttling is essential for controlling the rate of incoming requests, especially in high-traffic applications where excessive requests can strain servers, increase costs, and negatively impact user experience. By using node-cache, we can create a simple, in-memory solution for throttling in Node.js. node-cache allows us to set request limits, track request counts, and enforce time-based restrictions without the need for a distributed caching system.

Read article
Node.jsJavaScriptBackend