All Projects
eCommerce Platform / Marketplace API
VAAD Media — eCommerce & Advertising Platform
A full-stack REST API for an outdoor and print advertising marketplace where clients can browse, book, and pay for billboard and print media ad spaces across Nigeria.
Role: Backend Lead / Sole Backend Developer
Problem Statement
Advertisers in Nigeria have no centralized platform to discover, compare, and book outdoor advertising spaces (static billboards, LED billboards, BRT buses, lampposts) and print media. The traditional process required cold calls, manual invoicing, and uncertain availability checks — all of which VAAD digitizes and automates.
Target users: Marketing managers, advertising agencies, and business owners in Nigeria looking to book outdoor ad spaces or print media products, plus VAAD platform admins who manage listings, invoices, and fulfillment.
Project Walkthrough
VAAD started with a clear market gap: there was no centralized platform in Nigeria where advertisers could discover, compare, and book outdoor advertising spaces — billboards, LED displays, BRT buses, and lampposts — without going through manual phone calls and paper invoices. I was brought in to design and build the complete backend for this advertising marketplace from the ground up, covering everything from user authentication through payment processing and order fulfillment.
The core data modeling challenge was designing a single order system that could cleanly handle two fundamentally different product types. Billboard orders involve duration windows (start date, end date, total days), geographic routes, and availability tracking; print orders involve dimensions, finishing options, delivery methods, and design file uploads. Rather than creating separate order flows for each, I used a discriminated orderItem array within a single Order document — each item carries an orderType field that gates which fields are required and how fulfillment progresses.
The checkout flow was designed to accommodate two real-world payment behaviors. Some advertisers want to pay immediately and get a Paystack redirect link in the same API response. Others prefer to reserve an ad space and handle payment within a window. On the Paystack side, I implemented HMAC-SHA512 webhook signature verification using Node's native crypto module, and added idempotency protection by checking whether a transaction record for that Paystack reference already exists before processing.
One aspect I'm particularly pleased with is the image pipeline for listing management. Rather than storing raw uploads, every image goes through Sharp server-side before reaching S3 — resized to a max of 1000x1000 pixels, which cuts storage and CDN costs without any quality loss for the display sizes used on the frontend.
Tech Stack
Languages
Frameworks
Databases
Cloud & Infrastructure
Dev Tools
Authentication
JWT (access token 24h for users, 1h for admins) + refresh token stored on User/Admin documents + bcrypt 10 rounds + email verification via UUID token with 10-min expiry + OTP-based password reset + Google OAuth 2.0
Third-Party APIs & Integrations
Paystack — order and invoice payment initiation + HMAC-SHA512 signed webhook verification for charge.success events
Mailgun — transactional email (10+ email templates)
Google OAuth 2.0 — Google sign-in/sign-up for users
AWS S3 — media image storage via @aws-sdk/client-s3
System Architecture
Architecture Pattern
Layered MVC monolith. Routes (thin HTTP binding) -> controllers (business logic + orchestration) -> services (shared infrastructure: email, file upload, payment) -> models (Mongoose schemas).
Request Data Flow
Client -> HTTPS -> Express.js -> CORS whitelist check -> express.json() -> Morgan logging -> /api/v1 router -> route file -> auth() middleware (JWT verification + RBAC check) -> Controller method -> Mongoose model query / Paystack axios call / Mailgun email send -> res.ok() / res.created() / res.error() response utility
Key Engineering Decisions
1. Express v5 (beta): native async/await error forwarding. 2. MongoDB + Mongoose: flexible schema design suited to two fundamentally different media types sharing one order document via a discriminated orderItem array. 3. Custom responseUtilities middleware: augments res with res.ok(), res.created(), res.error(), res.noContent() for consistent JSON envelopes. 4. Server-side image compression with Sharp: images resized to max 1000x1000 before S3 upload. 5. Dual checkout flow (Pay Now vs. Pay Later): Pay Later creates the order at Pending status; Pay Now creates the order and simultaneously generates a Paystack authorization_url.
Database Design
Key collections: User (userCustomId unique, authType {password/googleUuid}, emailConfirmation subdocument with token+expiry, passwordRecovery OTP subdocument, refreshToken field), Admin (adminType enum [Super-Admin/Sub-Admin]), billboardMediaApplication (mediaType enum [Static Billboard/Led Billboard/BRT Bus/Lampost], status enum [Available/Unavailable], pictures array, favoriteCount), Order (orderCustomId, amount {subTotal, vat, delivery, totalAmount}, paymentStatus enum [Pending/Failed/Success], paymentType enum [Pay Now/Pay Later]), Transaction (transactionCustomId = Paystack reference, card brand/channel/last4), Invoice (adminCustomId, dueDate, paymentStatus).
Module Structure
Key Features & Implementation
Dual Checkout Flow (Pay Now / Pay Later with Paystack)
Users can place an order and pay immediately via Paystack redirect, or defer payment. A separate endpoint regenerates payment links for unpaid orders.
How it was built
Both flows create an Order document first. payNow additionally calls PaystackService.payWithPaystack() returning authorization_url. Amount validation: server re-calculates subtotal from orderItems and validates totalAmount = subTotal + VAT + delivery — rejects on mismatch.
Billboard Media Listings with Full-Text Search
Admins create and manage outdoor ad listings across 4 media types with rich metadata and photos. Users browse, filter, and search listings.
How it was built
MongoDB compound wildcard text index ($**) enables full-text search across all string fields. $text: {$search: keyword} with textScore ranking. Filter by mediaType/state/cityLga/price/availability via dynamic query construction.
Media Image Management (S3 + Sharp)
Admins upload multiple images per listing. Images are server-side compressed and stored in AWS S3 by folder. Individual images can be deleted without removing the listing.
How it was built
Multer receives multipart uploads to local disk. Sharp resizes to max 1000x1000px. PutObjectCommand uploads to S3 with folder-namespaced UUID key. Delete: URL parsed to extract S3 object key, DeleteObjectCommand issued. Temp local files deleted after S3 upload.
Admin Invoice System
Admins generate invoices for offline clients. Invoice is automatically linked to a Paystack payment link and emailed to the customer.
How it was built
Server calculates: subtotal = unitPrice * quantity * period; taxAmount = subtotal * (tax/100); total = subtotal + taxAmount. Paystack payment link generated with paymentType: Invoice in metadata. Webhook differentiates Invoice from Order payments via metadata.paymentType.
Challenges & Engineering Decisions
Heterogeneous order items: billboard and print orders have fundamentally different data shapes stored in a single orderItem array via a discriminated orderType field — requires type-checking at every access point.
Client-provided unit prices: the server validates that subtotals match client-sent values but trusts client-provided unit prices. Ideally, prices would be fetched from DB server-side.
Google OAuth authorization code flow: requires careful redirect URI coordination between frontend and backend.
Refresh token stored in DB: simple but means every refresh and logout requires a DB write.
API Documentation
API Route Namespaces