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.
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.
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.
Notification Key
- New Item Added → Notify all users.
- 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:
- Store the notification in PostgreSQL using Prisma.
- Add the notification to Redis cache for fast retrieval.
- Broadcast a Socket.io event (
NEW_ITEM_ADDED) to all users. - Add the event to RabbitMQ for email/SMS processing (optional).
Frontend Actions:
- Listen for
NEW_ITEM_ADDEDvia WebSocket and show a real-time alert. - 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