Real-Time Auction Website Implementation
Online auction requires precise real-time bid synchronization: displaying current price to all participants, timer, bid signals, determining winner at end.
Server Logic
class AuctionService {
async placeBid(auctionId: string, userId: string, amount: number): Promise<BidResult> {
// Pessimistic locking via Redis SETNX
const lockKey = `lock:auction:${auctionId}`;
const lockAcquired = await redis.set(lockKey, userId, 'NX', 'PX', 5000);
if (!lockAcquired) {
throw new Error('Auction is processing another bid, try again');
}
try {
const auction = await auctionRepo.findById(auctionId);
// Validation
if (auction.status !== 'active') throw new BidError('Auction is not active');
if (new Date() > auction.endsAt) throw new BidError('Auction has ended');
if (amount <= auction.currentPrice) {
throw new BidError(`Bid must be higher than ${auction.currentPrice}`);
}
if (amount < auction.currentPrice + auction.minIncrement) {
throw new BidError(`Minimum increment is ${auction.minIncrement}`);
}
if (auction.currentLeaderId === userId) {
throw new BidError('You are already the highest bidder');
}
// Save bid
const bid = await bidRepo.create({ auctionId, userId, amount });
await auctionRepo.updateCurrentPrice(auctionId, amount, userId);
// Anti-sniping: if bid made in last 2 minutes — extend 2 minutes
const timeLeft = auction.endsAt.getTime() - Date.now();
if (timeLeft < 2 * 60 * 1000) {
const newEndTime = new Date(Date.now() + 2 * 60 * 1000);
await auctionRepo.extendTime(auctionId, newEndTime);
}
// Notify all participants
await this.broadcastBid(auctionId, bid, auction);
return { success: true, bid };
} finally {
await redis.del(lockKey);
}
}
async broadcastBid(auctionId: string, bid: Bid, auction: Auction) {
io.to(`auction:${auctionId}`).emit('bid:new', {
bidId: bid.id,
amount: bid.amount,
bidderId: bid.userId,
bidderName: anonymizeBidder(bid.userId),
timestamp: bid.createdAt,
totalBids: auction.bidCount + 1,
newEndTime: auction.endsAt
});
}
}
Server Timer
class AuctionTimer {
async startTimer(auctionId: string, endTime: Date): Promise<void> {
const msUntilEnd = endTime.getTime() - Date.now();
// Delayed task — finalize auction exactly at deadline
setTimeout(async () => {
await this.finalizeAuction(auctionId);
}, msUntilEnd);
// Broadcast every second for last 60 seconds
const broadcastInterval = setInterval(async () => {
const remaining = endTime.getTime() - Date.now();
if (remaining <= 0) {
clearInterval(broadcastInterval);
return;
}
if (remaining <= 60000) {
io.to(`auction:${auctionId}`).emit('timer:tick', {
remaining: Math.ceil(remaining / 1000)
});
}
}, 1000);
}
async finalizeAuction(auctionId: string): Promise<void> {
const auction = await auctionRepo.findById(auctionId);
if (auction.status !== 'active') return; // already finalized
await auctionRepo.finalize(auctionId);
io.to(`auction:${auctionId}`).emit('auction:ended', {
winnerId: auction.currentLeaderId,
winnerName: await getUserName(auction.currentLeaderId),
finalPrice: auction.currentPrice
});
// Notify winner and previous participants
await this.notifyParticipants(auction);
}
}
Client Component
function AuctionRoom({ auctionId }) {
const [auction, setAuction] = useState<AuctionState>();
const [bids, setBids] = useState<Bid[]>([]);
const [timeLeft, setTimeLeft] = useState<number>(0);
const socket = useSocket();
useEffect(() => {
if (!socket) return;
socket.emit('auction:join', { auctionId });
socket.on('auction:state', (state) => setAuction(state));
socket.on('bid:new', (bid) => {
setBids(prev => [bid, ...prev].slice(0, 50));
setAuction(prev => prev ? { ...prev, currentPrice: bid.amount } : prev);
});
socket.on('timer:tick', ({ remaining }) => setTimeLeft(remaining));
socket.on('auction:ended', ({ winnerId, finalPrice }) => {
setAuction(prev => prev ? { ...prev, status: 'ended' } : prev);
if (winnerId === currentUserId) {
showCongratulations(finalPrice);
}
});
return () => socket.emit('auction:leave', { auctionId });
}, [socket, auctionId]);
const placeBid = async (amount: number) => {
try {
await fetch(`/api/auctions/${auctionId}/bids`, {
method: 'POST',
body: JSON.stringify({ amount })
});
} catch (e) {
showError(e.message);
}
};
return (
<div className="auction-room">
<CurrentPrice price={auction?.currentPrice} />
<AuctionTimer seconds={timeLeft} critical={timeLeft < 30} />
<BidForm
minBid={(auction?.currentPrice ?? 0) + (auction?.minIncrement ?? 100)}
onBid={placeBid}
disabled={auction?.status !== 'active'}
/>
<BidHistory bids={bids} currentUserId={currentUserId} />
</div>
);
}
Implementation Timeline
- Basic auction with bids and timer: 2–3 weeks
- Anti-sniping, notifications, bid history: another 1 week
- Auction with multiple lots and payment: 4–6 weeks







