SAF Demands — Automated Demand & Bidding Platform
A 3-role demand and bidding marketplace with automated bid management workflows, Airtable API synchronisation, cron-driven scheduling, and role-based access control — built on React.js, PHP, and Node.js, live at safdemand.com.
Project Overview
SAF Demands is a B2B procurement and bidding platform that connects customers who post service demands with providers who submit competitive bids. The Admin role oversees the entire marketplace — approving demands, managing providers, and monitoring bid activity.
The platform's standout technical requirement was Airtable as the operational database for the client's non-technical team: all demand and bid records needed to be reflected in Airtable in real time, so operations staff could manage data using a familiar spreadsheet interface without accessing the web application's admin panel.
How the Bidding Workflow Works
Customer Posts a Demand
A Customer creates a demand specifying requirements, budget range, and deadline. The record is created in MySQL and simultaneously synced to Airtable via the API.
Providers Receive Notifications and Submit Bids
Qualified Providers (filtered by category RBAC) receive an email alert. Each submits a bid with price and delivery terms. Bid records sync to Airtable in real time.
Automated Bid Evaluation
A cron job runs every 30 minutes to evaluate open demands: if the bid deadline has passed, the lowest qualifying bid is automatically surfaced for Admin review. Time-sensitive demands with no bids trigger an escalation notification.
Award and Fulfilment
Admin awards the demand to the selected Provider. Both parties receive confirmation emails. Status changes propagate to Airtable, keeping the operations team's view current.
Airtable API Integration
Integrating Airtable as a bidirectional sync target — rather than just a data export — was the platform's most technically interesting challenge. The operations team needed to see live demand and bid data in Airtable, and also be able to update certain fields (like internal notes or priority tags) in Airtable and have those changes reflected back in the web application.
Airtable Bidirectional Sync — Node.js Service
// services/airtableSync.js
const Airtable = require('airtable');
const base = new Airtable({ apiKey: process.env.AIRTABLE_API_KEY })
.base(process.env.AIRTABLE_BASE_ID);
// Push a new demand to Airtable when created in the PHP app
async function syncDemandToAirtable(demand) {
const record = await base('Demands').create({
'Demand ID': demand.id,
'Customer': demand.customer_name,
'Title': demand.title,
'Budget': demand.budget,
'Status': 'Open',
'Deadline': demand.deadline,
'Created At': demand.created_at,
});
// Store Airtable record ID back in MySQL for future updates
await db.query(
'UPDATE demands SET airtable_record_id = ? WHERE id = ?',
[record.id, demand.id]
);
}
// Pull admin-added notes from Airtable back to app (runs every 15 min via cron)
async function pullAirtableUpdates() {
const records = await base('Demands').select({
filterByFormula: "NOT({Admin Notes} = '')",
fields: ['Demand ID', 'Admin Notes', 'Priority Override']
}).all();
for (const record of records) {
await db.query(
'UPDATE demands SET admin_notes = ?, priority = ? WHERE id = ?',
[record.fields['Admin Notes'], record.fields['Priority Override'], record.fields['Demand ID']]
);
}
}
Key Challenges and Solutions
Challenge
Airtable's API has a rate limit of 5 requests per second per base. During high-demand periods, simultaneous bid submissions could create a queue of sync operations that breach this limit, causing sync failures and data inconsistencies between MySQL and Airtable.
Solution
All Airtable sync operations are dispatched to a Node.js queue with rate limiting (using the bottleneck library): maximum 4 requests/second with automatic retry on 429 responses. Sync operations are fire-and-forget from the user's perspective — the MySQL record is the source of truth, and Airtable sync runs asynchronously without blocking the API response.
What I Learned
SAF Demands taught me that the database the client uses is not always the database the application uses. No-code tools like Airtable are deeply embedded in how non-technical teams operate. Rather than forcing a migration to a "proper" admin panel, building a sync bridge let the operations team keep their existing workflow while the application gained a robust relational data layer.
Q: How is Airtable kept in sync with the application database?
Every create/update operation in the PHP application dispatches an async sync job to a Node.js queue service. The queue respects Airtable's 5 req/s rate limit using bottleneck, retries on failure, and stores the Airtable record ID in MySQL for subsequent updates.
Q: How does automated bid management work?
Cron jobs run every 30 minutes to evaluate open demands. When a bid deadline passes, the system surfaces the best bid for Admin review, notifies the relevant parties, and escalates demands with no bids received.
Project Info
Tech Stack
framework