A real-time British Auction system with intelligent bid-time extensions and transparent supplier competition
Features โข Architecture โข Installation โข API Docs โข Demo
- Overview
- Key Features
- Live Demo
- High-Level Design (HLD)
- Database Schema Design
- Tech Stack
- Design Tradeoffs
- Installation & Setup
- API Documentation
- Advanced Implementation Details
- Business Logic & Auction Mechanics
- Demo Credentials
- Project Structure
TrustBid is a sophisticated RFQ (Request for Quotation) management system implementing British Auction mechanics to ensure fair and transparent supplier competition. The system prevents last-second bid sniping through intelligent automatic time extensions and provides real-time updates to all participants.
A British Auction in the RFQ context is a competitive bidding process where:
- โ Suppliers submit bids openly and can continuously lower prices
- โ Bidding activity near auction close time triggers automatic extensions
- โ A forced close time ensures auctions conclude within reasonable timeframes
- โ Multiple extension triggers ensure fair competition
- Role-Based Access Control: Separate buyer and supplier workflows
- Real-Time Bidding: Live bid updates using Socket.IO
- Intelligent Time Extensions: Automatic auction extensions based on configurable triggers
- Force Close Mechanism: Guaranteed auction conclusion time
- Live Rankings: Real-time L1, L2, L3 supplier rankings
- Activity Logging: Complete audit trail of all bids and extensions
- Race Condition Prevention: MongoDB transactions for concurrent bid handling
- Optimistic Updates: Real-time UI updates with server validation
- Time Synchronization: Client-server time offset handling
- Responsive Design: Mobile-first UI with dark/light themes
- Form Validation: Comprehensive client and server-side validation
- JWT-based authentication
- Role-based authorization middleware
- Password hashing with bcrypt
- Protected API endpoints
- Input sanitization and validation
๐ https://trust-bid-gamma.vercel.app
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ CLIENT TIER โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ React SPA (Vite) โ โ
โ โ โโโโโโโโโโโโโโ โโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโ โ โ
โ โ โ Auth โ โ Auction โ โ Real-time Sync โ โ โ
โ โ โ Views โ โ Views โ โ (Socket.IO) โ โ โ
โ โ โโโโโโโโโโโโโโ โโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโ โ โ
โ โ โ โ โ โ โ
โ โ โโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโ โ โ
โ โ โ โ โ
โ โ useAuction Hook โ โ
โ โ โ โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
HTTPS/WSS โ
โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ API GATEWAY โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Express.js Server โ โ
โ โ โ โ
โ โ โโโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโโโโ โ โ
โ โ โ Auth โ โ Auction โ โ Bid โ โ Activity โ โ โ
โ โ โ Routes โ โ Routes โ โ Routes โ โ Routes โ โ โ
โ โ โโโโโโฌโโโโโโ โโโโโโฌโโโโโโ โโโโโโฌโโโโโโ โโโโโโโโฌโโโโโโโ โ โ
โ โ โ โ โ โ โ โ
โ โ โ โ โ โ โ โ
โ โ โโโโโโดโโโโโโ โโโโโโดโโโโโโ โโโโโโดโโโโโโ โโโโโโโโดโโโโโโโ โ โ
โ โ โ Auth โ โ Auction โ โ Bid โ โ Activity โ โ โ
โ โ โControllerโ โControllerโ โControllerโ โ Controller โ โ โ
โ โ โโโโโโฌโโโโโโ โโโโโโฌโโโโโโ โโโโโโฌโโโโโโ โโโโโโโโฌโโโโโโโ โ โ
โ โ โ โ โ โ โ โ
โ โโโโโโโโโผโโโโโโโโโโโโโโผโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโผโโโโโโโโโโ โ
โ โ โ โ โ โ
โ โโโโโโโโโดโโโโโโโโโโโโโโดโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโดโโโโโโโโโโ โ
โ โ Business Logic Layer โ โ
โ โ โข Extension Logic โข Validation Rules โ โ
โ โ โข Ranking System โข Time Calculations โ โ
โ โ โข Status Management โข Transaction Handling โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Middleware Layer โ โ
โ โ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโ โ โ
โ โ โ JWT โ โ CORS โ โ Rate Limiting โ โ โ
โ โ โ Auth โ โ Handler โ โ (Future) โ โ โ
โ โ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโ โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ DATA TIER โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ MongoDB Atlas โ โ
โ โ โ โ
โ โ โโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโ โโโโโโโโโโโโโโโโโโโโ โ โ
โ โ โ Users โ โ Auctions โ โ Bids โ โ ActivityLogs โ โ โ
โ โ โ (Auth) โ โ (RFQs) โ โ โ โ (Audit Trail) โ โ โ
โ โ โโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโ โโโโโโโโโโโโโโโโโโโโ โ โ
โ โ โ โ
โ โ โข Indexes for Performance โ โ
โ โ โข Transaction Support (ACID) โ โ
โ โ โข Aggregation Pipelines โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ REAL-TIME COMMUNICATION โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Socket.IO Server โ โ
โ โ โ โ
โ โ Events: โ โ
โ โ โข joinAuction โ Client joins auction room โ โ
โ โ โข newBid โ Broadcast new bid to all clients โ โ
โ โ โข auctionExtended โ Broadcast time extension โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โโโโโโโโโโโโ โโโโโโโโโโโโ
โ Buyer โ โ Supplier โ
โโโโโโฌโโโโโโ โโโโโโฌโโโโโโ
โ โ
โ 1. Create RFQ โ
โ POST /api/auctions โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โโโโโโผโโโโโ โ
โ โ Express โ โ
โ โ Server โ โ
โ โโโโโโฌโโโโโ โ
โ โ โ
โ 2. Validate โ
โ โข Time constraints โ
โ โข Auth check โ
โ โข Field validation โ
โ โ โ
โ โโโโโโผโโโโโ โ
โ โ MongoDB โ โ
โ โ Auction โ โ
โ โ Created โ โ
โ โโโโโโฌโโโโโ โ
โ โ โ
โ 3. RFQ Created โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค โ
โ โ โ
โ โ โ
โ โ 4. View Auction โ
โ โ GET /api/auctions/:id โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ โ
โ โ 5. Auction Details โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโบโ
โ โ โ
โ โ 6. Join Socket Room โ
โ โ socket.emit('joinAuction')
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ โ
โ โ 7. Place Bid โ
โ โ POST /api/bids โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ โ
โ 8. Start Transaction โ
โ โโโโโโผโโโโโ โ
โ โ MongoDB โ โ
โ โ Session โ โ
โ โโโโโโฌโโโโโ โ
โ โ โ
โ 9. Validate Bid โ
โ โข Amount < current lowest โ
โ โข Auction still active โ
โ โข No duplicate amounts โ
โ โ โ
โ 10. Check Extension โ
โ โข Within trigger window? โ
โ โข Trigger type met? โ
โ โ โ
โ 11. Save Bid โ
โ 12. Update Rankings โ
โ 13. Create Activity Log โ
โ โ โ
โ 14. Commit Transaction โ
โ โโโโโโผโโโโโ โ
โ โ Socket โ โ
โ โ IO โ โ
โ โโโโโโฌโโโโโ โ
โ โ โ
โ 15. Broadcast newBid โ 15. Receive newBid โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโบโ
โ โ โ
โ 16. Broadcast auctionExtendedโ 16. Receive auctionExtended โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโบโ
โ โ โ
โ 17. UI Auto-Updates โ 17. UI Auto-Updates โ
โ โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ MongoDB Collections โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ USERS COLLECTION โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ _id : ObjectId (PK) โ
โ name : String (required) โ
โ email : String (required, unique, indexed) โ
โ password : String (hashed with bcrypt) โ
โ role : String (enum: 'buyer', 'supplier') โ
โ createdAt : Date (auto) โ
โ updatedAt : Date (auto) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ AUCTIONS COLLECTION โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ _id : ObjectId (PK) โ
โ name : String (required) โ
โ createdBy : ObjectId (FK โ Users) โ
โ bidStartTime : Date (required) โ
โ bidCloseTime : Date (required, mutable - extends) โ
โ forcedCloseTime : Date (required, immutable) โ
โ triggerWindow : Number (minutes, required) โ
โ extensionDuration : Number (minutes, required) โ
โ triggerType : String (enum: 'ANY_BID', 'RANK_CHANGE', โ
โ 'L1_CHANGE') โ
โ status : String (computed: 'UPCOMING', 'ACTIVE', โ
โ 'CLOSED', 'FORCE_CLOSED') โ
โ currentLowestBid : Number (updated on each valid bid) โ
โ pickupDate : Date (optional) โ
โ createdAt : Date (auto) โ
โ updatedAt : Date (auto) โ
โ โ
โ Indexes: โ
โ โข bidStartTime, bidCloseTime (range queries) โ
โ โข status (filtering) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ BIDS COLLECTION โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ _id : ObjectId (PK) โ
โ auctionId : ObjectId (FK โ Auctions, indexed) โ
โ supplierId : ObjectId (FK โ Users, indexed) โ
โ freightCharges : Number (required) โ
โ originCharges : Number (required) โ
โ destinationCharges : Number (required) โ
โ amount : Number (required, sum of above) โ
โ transitTime : String (e.g., "14 days") โ
โ quoteValidity : String (e.g., "30 days") โ
โ rank : Number (1=L1, 2=L2, 3=L3...) โ
โ createdAt : Date (auto) โ
โ updatedAt : Date (auto) โ
โ โ
โ Indexes: โ
โ โข Compound: { auctionId: 1, amount: 1 } (sorting & queries) โ
โ โข { supplierId: 1, auctionId: 1 } (supplier bid history) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ACTIVITY_LOGS COLLECTION โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ _id : ObjectId (PK) โ
โ auctionId : ObjectId (FK โ Auctions, indexed) โ
โ type : String (enum: 'BID', 'EXTENSION') โ
โ message : String (human-readable log) โ
โ metadata : Object (additional context) โ
โ { โ
โ supplierId: ObjectId (for BID type) โ
โ amount: Number (for BID type) โ
โ reason: String (for EXTENSION type) โ
โ newCloseTime: Date (for EXTENSION type) โ
โ } โ
โ createdAt : Date (auto, indexed for sorting) โ
โ โ
โ Indexes: โ
โ โข { auctionId: 1, createdAt: -1 } (activity timeline) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โโโโโโโโโโโโ โโโโโโโโโโโโโ โโโโโโโโ
โ Users โ โ Auctions โ โ Bids โ
โ (Auth) โ โ (RFQs) โ โ โ
โโโโโโฌโโโโโโ โโโโโโโฌโโโโโโ โโโโโฌโโโ
โ โ โ
โ createdBy โ auctionId โ
โ 1:N โ 1:N โ
โโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ supplierId
โ N:1
โ
โโโโโโผโโโโโโ
โ Users โ
โ(Supplier)โ
โโโโโโโโโโโโ
Constraints:
1. Forced Close Time > Bid Close Time > Bid Start Time
2. Bid amount must be < current lowest bid (enforced via transaction)
3. Only buyers can create auctions
4. Only suppliers can place bids
5. No duplicate bid amounts allowed per auction
6. Auction extensions cannot exceed forced close time
- Runtime: Node.js (v18+)
- Framework: Express.js (v5.2+)
- Database: MongoDB (Atlas Cloud)
- ODM: Mongoose (v9.5+)
- Authentication: JWT + bcrypt
- Real-time: Socket.IO (v4.8+)
- Environment: dotenv
- Framework: React (v18.3+)
- Build Tool: Vite
- State Management: React Hooks (useState, useEffect, useCallback)
- Routing: Client-side navigation
- Real-time: Socket.IO Client
- Icons: Lucide React
- Styling: Custom CSS with CSS Variables (Dark/Light theme)
- Hosting:
- Backend: Render
- Frontend: Vercel
- Database: MongoDB Atlas
- Version Control: Git
- Used MongoDB instead of SQL for flexible schema and faster iteration
- Used Socket.IO instead of polling for real-time efficiency
- Used computed auction status instead of storing to avoid stale data
- Chose transactions to handle race conditions instead of locking mechanisms
- Prioritized real-time UX over backend simplicity
- Node.js >= 18.0.0
- MongoDB Atlas account (or local MongoDB)
- npm or yarn
-
Clone the repository
git clone <repository-url> cd backend
-
Install dependencies
npm install
-
Configure environment variables
Create
.envfile in the backend root:# MongoDB MONGO_URI=mongodb+srv://<username>:<password>@cluster.mongodb.net/trustbid?retryWrites=true&w=majority # JWT Secret (use a strong random string) JWT_SECRET=your_super_secret_jwt_key_here_min_32_chars # Server Port PORT=5000 # Node Environment NODE_ENV=development
-
Start the development server
npm run dev
The backend will run on
http://localhost:5000
-
Navigate to frontend directory
cd frontend -
Install dependencies
npm install
-
Configure environment variables
Create
.envfile in the frontend root:VITE_API_URL=http://localhost:5000/api
For production:
VITE_API_URL=https://your-backend-domain.com/api
-
Start the development server
npm run dev
The frontend will run on
http://localhost:5173
Backend:
npm startFrontend:
npm run build
npm run previewPOST /api/auth/register
Content-Type: application/json
{
"name": "John Doe",
"email": "john@example.com",
"password": "securepass123",
"role": "buyer" // or "supplier"
}
Response 201:
{
"message": "User registered",
"userId": "6512abc..."
}POST /api/auth/login
Content-Type: application/json
{
"email": "john@example.com",
"password": "securepass123"
}
Response 200:
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"id": "6512abc...",
"name": "John Doe",
"role": "buyer"
}
}POST /api/auctions
Authorization: Bearer <token>
Content-Type: application/json
{
"name": "Q4 Freight Tender",
"bidStartTime": "2024-12-01T10:00:00Z",
"bidCloseTime": "2024-12-01T18:00:00Z",
"forcedCloseTime": "2024-12-01T20:00:00Z",
"triggerWindow": 10,
"extensionDuration": 5,
"triggerType": "ANY_BID", // or "RANK_CHANGE", "L1_CHANGE"
"pickupDate": "2024-12-15T00:00:00Z"
}
Response 201:
{
"_id": "6512abc...",
"name": "Q4 Freight Tender",
"status": "UPCOMING",
"currentLowestBid": null,
...
}GET /api/auctions
Authorization: Bearer <token>
Response 200:
[
{
"_id": "6512abc...",
"name": "Q4 Freight Tender",
"status": "ACTIVE",
"currentLowestBid": 12500.50,
"bidStartTime": "2024-12-01T10:00:00Z",
"bidCloseTime": "2024-12-01T18:15:00Z", // Extended
"forcedCloseTime": "2024-12-01T20:00:00Z",
...
}
]GET /api/auctions/:id
Authorization: Bearer <token>
Response 200:
{
"auction": {
"_id": "6512abc...",
"name": "Q4 Freight Tender",
"status": "ACTIVE",
...
},
"bids": [
{
"_id": "bid123",
"supplierId": {
"name": "Maersk",
"email": "maersk@example.com"
},
"amount": 12500.50,
"rank": 1,
"freightCharges": 10000,
"originCharges": 1500,
"destinationCharges": 1000.50,
"transitTime": "14 days",
"quoteValidity": "30 days",
"createdAt": "2024-12-01T15:30:00Z"
}
],
"logs": [...]
}DELETE /api/auctions/:id
Authorization: Bearer <token>
Response 200:
{
"message": "Auction deleted"
}POST /api/bids
Authorization: Bearer <token>
Content-Type: application/json
{
"auctionId": "6512abc...",
"freight": 10000,
"origin": 1500,
"dest": 1000,
"transitTime": "14 days",
"quoteValidity": "30 days"
}
Response 201:
{
"message": "Bid placed successfully",
"bid": {
"_id": "bid123",
"amount": 12500,
"rank": 1,
...
}
}
// If auction extended:
{
"message": "Bid placed & Auction extended!",
"bid": {...}
}GET /api/activity/:auctionId
Authorization: Bearer <token>
Response 200:
[
{
"_id": "log123",
"auctionId": "6512abc...",
"type": "BID",
"message": "Supplier Maersk placed bid โน12500",
"metadata": {
"supplierId": "supplier123",
"amount": 12500
},
"createdAt": "2024-12-01T15:30:00Z"
},
{
"type": "EXTENSION",
"message": "Auction extended due to ANY_BID",
"metadata": {
"reason": "ANY_BID",
"newCloseTime": "2024-12-01T18:15:00Z"
},
...
}
]// Join auction room for real-time updates
socket.emit('joinAuction', auctionId);// New bid placed
socket.on('newBid', (data) => {
// data: { auctionId, amount, supplierId }
});
// Auction time extended
socket.on('auctionExtended', (data) => {
// data: { auctionId, newCloseTime, message }
});Problem: Multiple suppliers might submit bids simultaneously, potentially creating invalid states (e.g., two suppliers getting L1 rank).
Solution: MongoDB ACID transactions ensure atomic bid validation and submission.
// bidController.js - Transaction Implementation
const session = await mongoose.startSession();
session.startTransaction();
try {
// ๐ฅ Re-validate inside transaction with session lock
const lowestBid = await Bid.findOne({ auctionId })
.sort({ amount: 1 })
.session(session); // Locks the document
// Validate bid is still valid
if (lowestBid && amount >= lowestBid.amount) {
await session.abortTransaction();
session.endSession();
return res.status(400).json({
message: "Bid must be lower than current lowest bid"
});
}
// Create bid within transaction
const created = await Bid.create([{
auctionId,
supplierId: req.user.id,
amount,
...otherFields
}], { session });
// Update auction's current lowest bid
if (auction.currentLowestBid === null || amount < auction.currentLowestBid) {
auction.currentLowestBid = amount;
await auction.save({ session });
}
// Commit all changes atomically
await session.commitTransaction();
session.endSession();
} catch (err) {
await session.abortTransaction();
session.endSession();
throw err;
}Key Benefits:
- โ Atomicity: All operations succeed or all fail
- โ Consistency: No invalid intermediate states
- โ Isolation: Concurrent transactions don't interfere
- โ Durability: Committed changes are permanent
Extension Trigger Types:
Extends whenever any bid is received during trigger window.
if (auction.triggerType === "ANY_BID") {
shouldExtend = true;
}Extends only when the lowest bidder (L1) changes.
if (auction.triggerType === "L1_CHANGE") {
const oldLowest = auction.currentLowestBid;
if (oldLowest === null || amount < oldLowest) {
shouldExtend = true;
}
}Extends when any supplier's rank changes (most complex).
if (auction.triggerType === "RANK_CHANGE") {
// Get best bid from each supplier (excluding current supplier)
const otherSuppliersBest = await Bid.aggregate([
{
$match: {
auctionId: auction._id,
supplierId: { $ne: req.user.id }
}
},
{
$group: {
_id: "$supplierId",
minAmount: { $min: "$amount" }
}
}
]);
// Check if new bid beats any existing supplier's best
let didRankChange = false;
for (let supplier of otherSuppliersBest) {
if (amount < supplier.minAmount) {
didRankChange = true;
break;
}
}
if (didRankChange) {
shouldExtend = true;
}
}Extension Application:
if (shouldExtend) {
let newCloseTime = new Date(
bidCloseTime.getTime() + auction.extensionDuration * 60 * 1000
);
// Never exceed forced close time
if (newCloseTime > new Date(auction.forcedCloseTime)) {
newCloseTime = new Date(auction.forcedCloseTime);
}
auction.bidCloseTime = newCloseTime;
// Log extension event
await ActivityLog.create({
auctionId,
type: "EXTENSION",
message: `Auction extended due to ${auction.triggerType}`,
metadata: { reason: auction.triggerType, newCloseTime }
});
}Socket.IO Implementation:
// Server-side broadcast
const io = req.app.get("io");
// Broadcast to all clients in auction room
io.to(auctionId).emit("newBid", {
auctionId,
amount,
supplierId: req.user.id
});
if (shouldExtend) {
io.to(auctionId).emit("auctionExtended", {
auctionId,
newCloseTime: auction.bidCloseTime,
message: "Auction time extended due to activity!"
});
}// Client-side listeners
useEffect(() => {
const handleNewBid = ({ auctionId }) => {
fetchDetails(auctionId); // Refresh bid list
fetchAuctions(); // Update auction status
};
const handleExtension = ({ auctionId }) => {
fetchDetails(auctionId);
fetchAuctions();
showToast("Auction extended!");
};
socket.on("newBid", handleNewBid);
socket.on("auctionExtended", handleExtension);
return () => {
socket.off("newBid", handleNewBid);
socket.off("auctionExtended", handleExtension);
};
}, []);Bulk Update for Performance:
// After successful bid placement
const allBids = await Bid.find({ auctionId }).sort({ amount: 1 });
// Prepare bulk operations
const bulkOps = allBids.map((b, i) => ({
updateOne: {
filter: { _id: b._id },
update: { $set: { rank: i + 1 } }
}
}));
// Execute all rank updates in single operation
if (bulkOps.length > 0) {
await Bid.bulkWrite(bulkOps);
}Time Validation (Server-side):
const now = new Date();
const start = new Date(bidStartTime);
const close = new Date(bidCloseTime);
const forced = new Date(forcedCloseTime);
// Start must be future
if (start < now) {
return res.status(400).json({
message: "Bid start cannot be in the past"
});
}
// Close must be after start
if (close <= start) {
return res.status(400).json({
message: "Bid close must be after bid start"
});
}
// Forced must be after close
if (forced <= close) {
return res.status(400).json({
message: "Forced close must be after bid close"
});
}Bid Amount Validation:
// Must be lower than supplier's previous bid
const lastBid = await Bid.findOne({
auctionId,
supplierId: req.user.id
}).sort({ createdAt: -1 });
if (lastBid && amount >= lastBid.amount) {
return res.status(400).json({
message: "New bid must be lower than your previous bid"
});
}
// No duplicate amounts allowed
const tieBid = await Bid.findOne({ auctionId, amount });
if (tieBid) {
return res.status(400).json({
message: "A bid with this amount already exists. Please bid lower."
});
}Computed Status (never stored, always calculated):
function getAuctionStatus(auction) {
const now = new Date();
const start = new Date(auction.bidStartTime);
const close = new Date(auction.bidCloseTime);
const forced = new Date(auction.forcedCloseTime);
if (now < start) return "UPCOMING";
if (now >= forced) return "FORCE_CLOSED";
if (now >= close) return "CLOSED";
return "ACTIVE";
}Benefits:
- โ Always accurate (no stale data)
- โ No background jobs needed
- โ Works across time zones
- โ Handles server/client time drift
โโโโโโโโโโโโโโโ
โ UPCOMING โ โ Auction created, waiting for bidStartTime
โโโโโโโโฌโโโโโโโ
โ bidStartTime reached
โผ
โโโโโโโโโโโโโโโ
โ ACTIVE โ โ Suppliers can place bids
โโโโโโโโฌโโโโโโโ Extensions possible within trigger window
โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ โ Extension Triggers (if applicable): โ
โ โ โข ANY_BID: Any bid in last X minutes โ
โ โ โข RANK_CHANGE: Any rank shift in last X โ
โ โ โข L1_CHANGE: L1 position change in last Xโ
โ โ โ
โ โ Extension: bidCloseTime += Y minutes โ
โ โ (max: forcedCloseTime) โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โ bidCloseTime reached (no more extensions)
โผ
โโโโโโโโโโโโโโโ
โ CLOSED โ โ No more bids accepted
โโโโโโโโฌโโโโโโโ Winner determined (L1 supplier)
โ
โ forcedCloseTime reached
โผ
โโโโโโโโโโโโโโโ
โFORCE_CLOSED โ โ Auction definitively ended
โโโโโโโโโโโโโโโ
Configuration:
- Bid Close: 6:00 PM
- Forced Close: 8:00 PM
- Trigger Window (X): 10 minutes
- Extension Duration (Y): 5 minutes
- Trigger Type: L1_CHANGE
Timeline:
5:50 PM โ [Trigger Window Begins]
โ
5:52 PM โ Supplier A bids $12,000 โ Becomes L1
โ โ Extension triggered (L1 changed)
โ New close: 6:05 PM
โ
5:58 PM โ Supplier B bids $11,800 โ Becomes L1
โ โ Extension triggered (L1 changed)
โ New close: 6:10 PM
โ
6:02 PM โ [New Trigger Window: 6:00 PM - 6:10 PM]
โ
6:08 PM โ Supplier C bids $11,500 โ Becomes L1
โ โ Extension triggered (L1 changed)
โ New close: 6:15 PM
โ
6:11 PM โ Supplier A bids $11,200 โ Becomes L1
โ โ Extension triggered (L1 changed)
โ New close: 6:20 PM
โ
6:15 PM โ [Trigger Window: 6:10 PM - 6:20 PM]
โ No more bids in trigger window
โ
6:20 PM โ โน Auction CLOSED
โ Winner: Supplier A at $11,200
โ
8:00 PM โ โน Forced Close Time (not reached because auction
โ naturally closed at 6:20 PM)
L1, L2, L3 Badge Logic:
// Rankings are calculated after each bid
// Sorted by amount (lowest = L1)
Supplier A: $12,000 โ Rank 2 (L2)
Supplier B: $11,500 โ Rank 1 (L1) โญ
Supplier C: $13,200 โ Rank 3 (L3)
Supplier D: $15,000 โ Rank 4 (L4+)Visual Representation:
- L1 (Gold): Lowest bid, winning position
- L2 (Silver): Second-lowest bid
- L3 (Bronze): Third-lowest bid
- L4+ (Gray): Other positions
Email: buyer@test.com
Password: buyer123
Capabilities:
- โ Create new RFQ auctions
- โ View all auctions and bids
- โ Delete own auctions
- โ Monitor real-time bidding activity
- โ Cannot place bids
Email: supplier1@test.com
Password: supplier123
Email: supplier2@test.com
Password: supplier123
Capabilities:
- โ View active auctions
- โ Place competitive bids
- โ See live rankings (L1, L2, L3)
- โ Receive real-time extension notifications
- โ Cannot create or delete auctions
trustbid/
โ
โโโ backend/
โ โโโ src/
โ โ โโโ config/
โ โ โ โโโ db.js # MongoDB connection
โ โ โ
โ โ โโโ models/
โ โ โ โโโ User.js # User schema (buyer/supplier)
โ โ โ โโโ Auction.js # Auction/RFQ schema
โ โ โ โโโ Bid.js # Bid schema with ranking
โ โ โ โโโ ActivityLog.js # Activity log schema
โ โ โ
โ โ โโโ controllers/
โ โ โ โโโ authController.js # Register, login
โ โ โ โโโ auctionController.js # CRUD auctions, get details
โ โ โ โโโ bidController.js # Place bid (with extensions)
โ โ โ โโโ activityController.js # Get activity logs
โ โ โ
โ โ โโโ middlewares/
โ โ โ โโโ authMiddleware.js # JWT auth & role authorization
โ โ โ
โ โ โโโ routes/
โ โ โ โโโ authRoutes.js # /api/auth/*
โ โ โ โโโ auctionRoutes.js # /api/auctions/*
โ โ โ โโโ bidRoutes.js # /api/bids/*
โ โ โ โโโ activityRoutes.js # /api/activity/*
โ โ โ
โ โ โโโ utils/
โ โ โโโ getAuctionStatus.js # Status calculation helper
โ โ
โ โโโ server.js # Express + Socket.IO setup
โ โโโ package.json
โ โโโ .env # Environment variables
โ
โโโ frontend/
โ โโโ src/
โ โ โโโ components/
โ โ โ โโโ layout/
โ โ โ โ โโโ Navbar.jsx # Navigation bar
โ โ โ โ
โ โ โ โโโ ui/
โ โ โ โ โโโ Badge.jsx # Status badges
โ โ โ โ โโโ Toast.jsx # Toast notifications
โ โ โ โ
โ โ โ โโโ modals/
โ โ โ โ โโโ BidModal.jsx # Bid submission form
โ โ โ โ โโโ DeleteModal.jsx # Delete confirmation
โ โ โ โ
โ โ โ โโโ views/
โ โ โ โโโ AuthView.jsx # Login/Register
โ โ โ โโโ HeroView.jsx # Landing page
โ โ โ โโโ CreateView.jsx # Create RFQ form
โ โ โ โโโ ListingView.jsx # Auction list
โ โ โ โโโ DetailView.jsx # Auction details + live bids
โ โ โ
โ โ โโโ hooks/
โ โ โ โโโ useAuction.js # Main data hook (API + Socket)
โ โ โ
โ โ โโโ utils/
โ โ โ โโโ helpers.js # Format functions, status calc
โ โ โ โโโ socket.js # Socket.IO client setup
โ โ โ
โ โ โโโ App.jsx # Main app component
โ โ โโโ main.jsx # React entry point
โ โ โโโ index.css # Global styles + CSS variables
โ โ
โ โโโ package.json
โ โโโ .env # VITE_API_URL
โ
โโโ README.md # This file
-
Full-Stack Development
- RESTful API design with Express.js
- React hooks and modern state management
- Real-time bidirectional communication
-
Database Design
- Schema modeling for complex relationships
- Transaction handling for data integrity
- Index optimization for query performance
-
Real-Time Systems
- WebSocket integration with Socket.IO
- Event-driven architecture
- Optimistic UI updates
-
Security Best Practices
- JWT authentication flow
- Role-based access control (RBAC)
- Password hashing with bcrypt
- Input validation and sanitization
-
Business Logic Implementation
- Complex time-based calculations
- Dynamic auction extensions
- Concurrent request handling
- Ranking algorithms
-
UI/UX Design
- Responsive design principles
- Component-based architecture
- Theme system with CSS variables
- Accessibility considerations
This project is licensed under the MIT License.
Anurag Dubey
- ๐ LinkedIn: [https://www.linkedin.com/in/anuragdubey7/]
- ๐ GitHub: [https://github.com/AnuragDubey007]
- ๐ง Email: anuragdubey0245@gmail.com
- Go Comet for the assignment opportunity
- MongoDB for the excellent database documentation
- Socket.IO for real-time communication capabilities
- React Team for the amazing frontend framework
- Lucide Icons for the beautiful icon set