Skip to main content
Photo from unsplash: thesawraj/blogs/notification/ptivbotxdiuxnj3soqlg

Real-Time Notification System for an Auction-Bidding Platform

Written on October 15, 2023 by alpha.

Last updated October 15, 2023.

See changes
5 min read
––– views
Read in Hindi

Introduction

The Notification System is a critical component of any modern application, especially for an auction-bidding platform. It ensures users are promptly informed about important updates such as new item listings, live bidding activities, and auction results. Notifications can be delivered through mobile push notifications, SMS messages, or emails.

In this blog post, we’ll explore how we developed a real-time notification system for an auction-bidding platform, ensuring seamless user engagement and instant updates.

Live Demo


How Our Notification System Works

Our notification system is designed to keep users informed and engaged. Here’s how it works:

1️⃣ New Item Listings

When a seller lists a new item for auction, all users receive a notification. For example:

"[Seller Name] has listed a new item: [Item Name]."

This ensures potential buyers are instantly informed about newly available products.

New Item Notification

2️⃣ Live Bidding Notifications

During an ongoing auction, participants who place bids receive real-time updates. For example:

"User [Bidder Name] has placed a new bid on [Item Name]. Check the latest price now!"

This keeps the auction competitive and ensures transparency among bidders.

Live Bidding Notification

Notification Key

  1. New Item Added → Notify all users.
  2. Live Bidding → Notify participating bidders in an auction.

Notification Component

The notification system is built using the following technologies:

  • Backend: Node.js, Prisma (PostgreSQL), Redis, BullMQ, Socket.io
  • Frontend: React, React Query, WebSocket

Notification Use Cases & Flow

Trigger:

  • New item added or a new bid placed.

Backend Actions:

  1. Store the notification in PostgreSQL using Prisma.
  2. Add the notification to Redis cache for fast retrieval.
  3. Broadcast a Socket.io event (NEW_ITEM_ADDED) to all users.
  4. Add the event to RabbitMQ for email/SMS processing (optional).

Frontend Actions:

  1. Listen for NEW_ITEM_ADDED via WebSocket and show a real-time alert.
  2. Fetch new notifications via React Query.

User Experience:

  • The user sees a 🔔 notification:

    "Seller [name] added a new item: [item_name]."


Database

 
model Notification {
  id        String           @id @default(cuid())  // Unique identifier
  type      NotificationType                     // Type of notification (Enum)
  title     String                                // Short title for the notification
  message   String                                // Detailed message
 
  userId    String                                // User ID foreign key
  user      User             @relation(fields: [userId], references: [id], onDelete: Cascade)
 
  auctionId Int?                                  // Optional: Related auction ID
  auction   Auction?         @relation(fields: [auctionId], references: [id], onDelete: SetNull)
 
  read      Boolean          @default(false)      // Whether the user has read it
  createdAt DateTime         @default(now())      // Timestamp of creation
  expiresAt DateTime?                            // Optional expiration date
 
  // Indexes for optimized lookups
  @@index([userId]) 
  @@index([auctionId]) 
}
 
 

NotificationService.js

1. createNotification

async createNotification(data) {
  try {
    const notification = await prisma.notification.create({
      data: {
        ...data,
        expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), // 30 days
      },
    });
 
    await this.invalidateUserNotificationCache(data.userId);
    return notification;
  } catch (error) {
    logger.error('Error creating notification:', error);
    throw new ApiError(500, 'Failed to create notification');
  }
}
 

2. getUserNotifications

 async getUserNotifications(userId, options = {}) {
    const { page = 1, limit = 20, unreadOnly = false } = options;
    const cacheKey = `notifications:user:${userId}:${page}:${limit}:${unreadOnly}`;
 
    try {
      const cached = await redis.get(cacheKey);
      if (cached) return JSON.parse(cached);
 
      const where = {
        userId,
        expiresAt: { gt: new Date() },
      };
 
      if (unreadOnly) {
        where.read = false;
      }
           const [notifications, total] = await prisma.$transaction([
        prisma.notification.findMany({
          where,
          orderBy: { createdAt: 'desc' },
          skip: (page - 1) * limit,
          take: limit,
          include: {
            auction: {
              select: {
                id: true,
                title: true,
                currentPrice: true,
              },
            },
          },
        }),
        prisma.notification.count({ where }),
      ]);
 
      const result = {
        notifications,
        pagination: {
          total,
          pages: Math.ceil(total / limit),
          currentPage: page,
          perPage: limit,
        },
      };
 
      await redis.setex(cacheKey, 300, JSON.stringify(result)); // Cache for 5 minutes
      return result;
    } catch (error) {
      logger.error('Error fetching notifications:', error);
      throw new ApiError(500, 'Failed to fetch notifications');
    }
  }
 

3. markAsRead

 async markAsRead(id, userId) {
    try {
      const notification = await prisma.notification.update({
        where: { id, userId },
        data: { read: true },
      });
 
      await this.invalidateUserNotificationCache(userId);
      return notification;
    } catch (error) {
      logger.error('Error marking notification as read:', error);
      throw new ApiError(400, 'Failed to update notification');
    }
  }
 

scheduler

const { Queue } = require('bullmq');
const redis = require('../config/redis'); // Import the Redis client
 
// Initialize the queue
const auctionQueue = new Queue('auctionTransitions', {
  connection: redis, // Use the Redis client directly
});
 
module.exports = auctionQueue;

Setup Redis

const Redis = require('ioredis');
const logger = require('./logger');
 
// Configure Redis connection
const redis = new Redis({
  host: process.env.REDISHOST, // Default to 'localhost' if no REDIS_HOST is specified
  port: parseInt(process.env.REDISPORT), // Default to port 6379 if no REDIS_PORT is specified
  password: process.env.REDISPASSWORD || null, // Use the REDIS_PASSWORD if specified
  maxRetriesPerRequest: null, // Ensure this is set to null for BullMQ
  retryStrategy: (times) => {
    // Gradually increase the retry delay with each attempt, up to a max of 2 seconds
    const delay = Math.min(times * 50, 2000);
    logger.warn(`Redis reconnecting attempt #${times}, retrying in ${delay}ms`);
    return delay;
  },
});
 
// Log Redis connection events
redis.on('connect', () => {
  logger.info('Connected to Redis server');
});
 
redis.on('ready', () => {
  logger.info('Redis server is ready');
});
 
redis.on('error', (error) => {
  logger.error('Redis connection error:', error);
});
 
redis.on('close', () => {
  logger.warn('Redis connection closed');
});
 
redis.on('end', () => {
  logger.info('Redis connection ended');
});
 
module.exports = redis; // Export the Redis client directly
 

You can check out the live deployment on Auction-Bid

Thanks and Goodluck!

Share this article

Enjoying this post?

Don't miss out 😉. Get an email whenever I post, no spam.

Subscribe Now