How I Built a B2B Marketplace in 8 Weeks
Full case study: how IndianTradeMART — a verified B2B marketplace — was built with React, Supabase, and Node.js in 8 weeks. Architecture, decisions, results.
On this page
Article
Clean hierarchy, tighter spacing, and readable markdown blocks across desktop and mobile.
TL;DR: IndianTradeMART is a verified B2B marketplace connecting Indian suppliers and buyers. Built with React, Node.js, Supabase (PostgreSQL + Auth + RLS), Razorpay, and deployed on Vercel. Full project delivered in 8 weeks. This post walks through every technical decision, the architecture, what worked, and what I'd do differently.
Building a marketplace is one of the hardest categories of software product.
You have two user types (buyers and suppliers), a complex data model, payment flows, trust/verification mechanisms, admin moderation, and — on top of all that — you need it fast enough and structured enough to actually rank on Google.
This is the story of how I built IndianTradeMART from scratch in 8 weeks.
The Brief
IndianTradeMART is a B2B marketplace designed to connect verified suppliers and buyers across India — similar in spirit to IndiaMart or TradeIndia, but with a focus on verified, moderated listings rather than raw volume.
The client needed:
- Supplier registration with profile and product listing management
- Buyer enquiry system (contact supplier, get a quote)
- Admin dashboard to approve/reject suppliers and listings
- Payment integration for supplier premium plans (Razorpay)
- SEO-optimised supplier and product pages (Google-indexable)
- Mobile-responsive design
Timeline: 8 weeks. Budget: fixed-price contract.
Week 1: Architecture Decisions
Before writing a single line of code, I spent the first 3 days making architecture decisions. In a project like this, a wrong early decision costs weeks later.
Why Supabase Over a Custom Backend
The classic approach would be: Node.js + Express + PostgreSQL, all self-managed on a VPS or AWS EC2. That would have taken 3-4 weeks just to set up auth, database schema, role management, and API routes.
Instead, I chose Supabase — an open-source Firebase alternative built on PostgreSQL.
Here's what Supabase replaced:
| Traditional Setup | Supabase Equivalent |
|---|---|
| Custom auth server | Supabase Auth (email, OTP, OAuth) |
| REST API for data | Supabase auto-generated REST + PostgREST |
| S3 for file storage | Supabase Storage |
| Manual role checks | Row Level Security (RLS) policies in PostgreSQL |
| Realtime WebSocket server | Supabase Realtime |
In a marketplace, row-level security is critical. A supplier should only be able to edit their own listings. A buyer should only see approved listings. Admins should see everything.
Supabase RLS lets you write this logic directly in PostgreSQL as policies — and it enforces at the database level, not just in application code. This is far more secure than checking permissions in your API routes.
Why Next.js Over Plain React
For a marketplace, SEO is not optional. Every supplier page (/suppliers/ahmedabad-steel-traders) and every category page (/category/industrial-equipment) needs to be server-rendered and indexable by Google.
Plain Create React App renders everything client-side — Google can technically index it, but crawl budget is wasted and Time to First Byte (TTFB) is poor.
Next.js with SSG (Static Site Generation) and ISR (Incremental Static Regeneration) gave me:
- Pre-rendered HTML for every supplier and category page at build time
- ISR to regenerate pages every 24 hours as supplier data changes
- Fast TTFB because the page is already rendered on the server
- Next.js API routes for server-side operations (webhooks, admin actions)
Week 2-3: Database Schema Design
The schema was the most critical design work of the project. A poorly designed schema in a marketplace means painful migrations later.
Core Tables
users (managed by Supabase Auth)
├── id (uuid, FK to auth.users)
├── role: 'supplier' | 'buyer' | 'admin'
├── full_name
├── phone
└── created_at
suppliers
├── id (uuid)
├── user_id (FK to users)
├── company_name
├── gst_number
├── verified: boolean
├── plan: 'free' | 'premium'
├── location (city, state)
├── description
└── logo_url
products
├── id (uuid)
├── supplier_id (FK to suppliers)
├── name
├── category_id
├── description
├── min_order_quantity
├── unit
├── status: 'pending' | 'approved' | 'rejected'
└── images: text[] (array of storage URLs)
enquiries
├── id (uuid)
├── buyer_id (FK to users)
├── supplier_id (FK to suppliers)
├── product_id (FK to products, nullable)
├── message
├── status: 'new' | 'responded' | 'closed'
└── created_at
RLS Policies (Selected Examples)
Suppliers can only edit their own listings:
CREATE POLICY "suppliers_update_own"
ON products FOR UPDATE
USING (supplier_id = (SELECT id FROM suppliers WHERE user_id = auth.uid()));
Buyers can only see approved products:
CREATE POLICY "buyers_see_approved"
ON products FOR SELECT
USING (status = 'approved');
Admins bypass all restrictions:
CREATE POLICY "admin_full_access"
ON products FOR ALL
USING ((SELECT role FROM users WHERE id = auth.uid()) = 'admin');
These three policy patterns — own-resource access, status-filtered public access, admin bypass — covered 80% of all permission requirements.
Week 3-4: Authentication and Onboarding
Supabase Auth handled email/password registration and OTP-based phone login. But the marketplace needed one extra layer: role assignment during registration.
When a new user signs up, they choose "I'm a Supplier" or "I'm a Buyer." A Supabase Edge Function (a serverless function running on Deno) inserts the correct role into the users table immediately after the auth signup event fires.
// supabase/functions/on-signup/index.ts
Deno.serve(async (req) => {
const { record } = await req.json()
const role = record.raw_user_meta_data?.role ?? 'buyer'
await supabaseAdmin.from('users').insert({
id: record.id,
role,
full_name: record.raw_user_meta_data?.full_name,
})
return new Response('ok')
})
This Edge Function is triggered by Supabase's database webhook on auth.users INSERT — no API route needed, no polling, no race condition.
Supplier Verification Flow
Suppliers can't list products until verified. The flow:
- Supplier submits profile with GST number and business documents (uploaded to Supabase Storage)
- Admin receives a notification (via Supabase Realtime + a simple admin dashboard)
- Admin reviews and clicks Approve or Reject
- Supplier's
verifiedcolumn is updated totrue - Supplier can now create product listings (which also go through approval)
Two-tier moderation — supplier level AND product level — kept the marketplace quality high.
Week 4-5: The Supplier and Product Listing UI
The supplier-facing dashboard was built in React with:
- React Hook Form + Zod for form validation (type-safe, fast)
- Supabase Storage for image uploads (drag-and-drop, multiple images per product)
- TanStack Query for server state management (caching, background refetching)
One interesting challenge: image upload with progress feedback.
Supabase Storage's JavaScript client doesn't natively expose upload progress events. I worked around this by using the native XMLHttpRequest API for uploads and wiring progress callbacks manually — giving suppliers a real-time progress bar during uploads.
Category and Search Architecture
Rather than building a full-text search engine (Elasticsearch, Algolia — both expensive and complex), I used PostgreSQL's built-in full-text search via Supabase.
-- Add a full-text search column
ALTER TABLE products ADD COLUMN fts tsvector
GENERATED ALWAYS AS (
to_tsvector('english', coalesce(name, '') || ' ' || coalesce(description, ''))
) STORED;
CREATE INDEX products_fts_idx ON products USING GIN(fts);
Then from the frontend:
const { data } = await supabase
.from('products')
.select('*')
.textSearch('fts', query)
.eq('status', 'approved')
.limit(20)
This handled 95% of search use cases without any additional infrastructure cost.
Week 5-6: Payments with Razorpay
Suppliers on the free plan could list up to 5 products. Premium plan (₹2,999/month) unlocked unlimited listings, featured placement, and priority support.
Why Razorpay over Stripe:
- Razorpay supports UPI, net banking, and Indian cards natively
- INR settlements without conversion fees
- Better support for Indian GST invoicing
The payment flow:
- Supplier clicks "Upgrade to Premium"
- Next.js API route creates a Razorpay order (
orders.create) - Frontend opens the Razorpay checkout modal
- On success, Razorpay sends a webhook to the Next.js API route
- API route verifies the webhook signature (HMAC SHA256) and updates the supplier's plan
Webhook verification is non-negotiable. Never trust a success callback from the frontend alone — always verify server-side.
// pages/api/razorpay-webhook.ts
import crypto from 'crypto'
const expectedSignature = crypto
.createHmac('sha256', process.env.RAZORPAY_WEBHOOK_SECRET!)
.update(rawBody)
.digest('hex')
if (expectedSignature !== req.headers['x-razorpay-signature']) {
return res.status(400).json({ error: 'Invalid signature' })
}
// Only update DB after signature verified
Week 6-7: Admin Panel
The admin panel was a separate Next.js route group (/admin/*) protected by middleware that checks the user's role before rendering any page.
Key admin features:
- Supplier queue — pending verifications with document preview
- Product moderation — approve/reject listings with optional rejection notes
- Enquiry overview — all buyer-supplier conversations
- Analytics dashboard — total suppliers, products, enquiries (using Supabase's
count()and date-bucketed queries)
The admin panel was intentionally simple — no complex charting libraries, just tables and action buttons. Admins needed speed and clarity, not beauty.
Week 7-8: SEO Architecture and Deployment
This is where many marketplace projects fail. Dynamic content with no SEO structure means zero organic traffic.
What I implemented:
Static Generation for Category and Supplier Pages
// pages/suppliers/[slug].tsx
export async function getStaticPaths() {
const { data } = await supabase
.from('suppliers')
.select('slug')
.eq('verified', true)
return {
paths: data.map(s => ({ params: { slug: s.slug } })),
fallback: 'blocking' // new suppliers get SSR on first visit, then cached
}
}
fallback: 'blocking' is ideal for marketplaces — new suppliers get a server-rendered page on first visit, which Google can index immediately. Subsequent visits are served from the cache.
Structured Data for Supplier Pages
Every supplier page included LocalBusiness schema markup:
{
"@context": "https://schema.org",
"@type": "LocalBusiness",
"name": "Ahmedabad Steel Traders",
"description": "...",
"address": { "@type": "PostalAddress", "addressLocality": "Ahmedabad" },
"telephone": "..."
}
This improves rich snippet eligibility in Google Search results.
Deployment on Vercel
Vercel was the natural choice for Next.js deployment:
- Zero-config CI/CD from GitHub
- Automatic preview deployments for every PR
- Edge network CDN globally
- ISR support out of the box
Total deployment time from first commit to live URL: under 10 minutes.
Results and What I'd Do Differently
The project was delivered on time in 8 weeks. Post-launch metrics after 60 days:
- 200+ supplier registrations (organic, no paid acquisition)
- 500+ product listings (post-moderation)
- First-page Google rankings for 12 category + location keyword combinations (e.g., "industrial equipment suppliers Ahmedabad")
What I'd do differently:
- Build the admin panel first. Content moderation is the critical path — without it, nothing else works. I built it in week 6-7; it should have been week 2.
- Add Redis caching earlier. Supabase is fast, but some aggregated queries (total counts, featured listings) would benefit from a cache layer at scale.
- Invest more in the onboarding UX. Supplier drop-off during the registration/verification flow was higher than expected. Cleaner step-by-step onboarding would have improved completion rates.
The Stack Summary
| Layer | Technology | Why |
|---|---|---|
| Frontend | Next.js 14 (React) | SSR/SSG for SEO, fast routing |
| Database | Supabase (PostgreSQL) | RLS, auth, storage — all in one |
| Auth | Supabase Auth | Email, OTP, role management |
| File Storage | Supabase Storage | Images, documents |
| Search | PostgreSQL FTS | No extra infrastructure |
| Payments | Razorpay | UPI + Indian cards + webhooks |
| Deployment | Vercel | Zero-config, edge CDN |
| Forms | React Hook Form + Zod | Type-safe validation |
| Data fetching | TanStack Query | Caching and background sync |
If you're building a marketplace, a SaaS product, or a B2B platform and want the same approach — fast delivery, clean architecture, and SEO built in from the start — visit dipanshudev.com/contact or explore more projects at dipanshudev.com/projects.
Article snapshot
Published
07 Mar 2026
Read time
9 min
Category
case study
Media
0 visuals
Internal links
Need this done properly
Build, performance, SEO, and content can be handled in one delivery flow.
If you are planning a business site, technical blog, or product build that needs to look sharp and rank cleanly, the same approach can be applied to your stack.
Keep reading
Related articles
More posts connected to the same delivery, SEO, or product engineering themes.
case study
Dipanshu Kumar Pandey: Your Trusted Freelancer and Java Developer in India, Sonbhadra, Noida, Delhi, Varanasi, Pune, Chandigarh
Looking for a skilled Java Developer/Full stack Developer or Freelancer in Sonbhadra, Noida, Delhi, Varanasi, Pune, or Chandigarh? Dipanshu Kumar Pandey offers full-stack development and software solutions across these cities and India.
case study
How I Build Production-Ready Full Stack Websites for Growing Businesses
A practical framework I use to ship secure, fast, and SEO-ready websites for founders, local businesses, and startup teams.
guides
How to Hire a Full Stack Developer from India
Thinking of hiring a full stack developer from India? Here is exactly what to look for, what to pay, and how to avoid the most common hiring mistakes.
