Back to All Projects
Full Stack  ·  Marketplace  ·  Self-Hosted  ·  Ongoing 2026

CampusBay

A verified peer-to-peer student marketplace, built and self-hosted solo. Real-time chat, Stripe payments, escrow, flash sales, AI support, and a wallet system. Live at campusbay.store.

React 18 + Vite Node.js + Express PostgreSQL + Redis Socket.IO Stripe Docker Cloudflare Tunnel
Live & Actively Developing: campusbay.store · Self-hosted on Linux mini PC · Docker Compose · Shipped solo
Status
In Development
Year
2026
Role
Solo Founder & Dev
Platform
Web + Android TWA
01 Project Overview

CampusBay is a production-grade peer-to-peer student marketplace I'm building solo as a self-development project. Originally built for University of Malaya, now expanding Malaysia-wide, a trusted, verified space for students to buy and sell textbooks, electronics, hostel furniture, and more.

Registration is gated to institutional email addresses, verified via 6-digit OTP: every user is a real verified student, eliminating the scammers that are rampant on Carousell and Facebook Marketplace.

The honest answer: Building a full marketplace solo, auth, payments, real-time chat, escrow, notifications, admin panel, hosting, DevOps, while learning as I go. Every feature is production-deployed and live.
02 Interface

Screens from the running build. Every page here is live on campusbay.store, not a mockup: the same React frontend talking to the containerized backend described below.

HomeLanding page — the marketplace pitch and entry points into browsing
List an itemCreating a listing: photo upload, category, faculty, condition and price
MessagesBuyer–seller messaging, backed by the Socket.IO real-time layer
CartCart and checkout entry point into the escrow wallet flow
How it worksThe three-step explainer shown to first-time visitors
Design boardFigma working board — screens laid out before any code was written
DemoWalkthrough of the live marketplace end to end
Architecture report PDF
03 System Architecture
React 18 + Vite (Frontend)
SPA · Context API · TanStack Query
Node.js + Express (Backend API)
15+ route modules · JWT · Sequelize ORM
PostgreSQL 16 + Redis 7
Primary DB · OTP/session cache · wallet_transactions table
Socket.IO (Real-Time Layer)
Chat · order tracking · notifications · admin broadcasts
Stripe + Wallet System
Checkout · webhook fulfillment · in-app wallet balance · topup
Docker Compose + nginx + Cloudflare Tunnel
5 containers · self-hosted Linux mini PC · protocol: http2
04 Order Flow & Escrow Logic

The order lifecycle uses a state machine to move funds safely through an escrow-style flow:

Pending
Confirmed
Meetup Scheduled
Completed
Cancelled
Rejected (30-min)
01Wallet Payment Math
order.totalAmount = listingPrice × quantity
                     - discountAmount
                     + (SST if applicable)

buyer.wallet_balance  -= order.totalAmount   -- atomic SQL UPDATE
seller.wallet_balance += order.totalAmount   -- on order Completed

escrow: funds held in system until Completed
        released to seller only on meetup confirmation

Deduction runs as a single atomic SQL statement, UPDATE users SET wallet_balance = wallet_balance - $amount WHERE id = $id AND wallet_balance >= $amount, so two concurrent checkouts can never both succeed against a balance that covers only one. Buyer funds move to escrow immediately; the seller sees the credit only once both parties confirm the meetup — safe without a third-party payment processor.

# Buyer wallet: RM 120.00, buying item priced RM 45, qty 1
# Voucher applied: RM 5 off, SST not applicable (digital good)

totalAmount = 45 × 1 - 5 + 0 = RM 40.00

UPDATE users SET wallet_balance = wallet_balance - 40.00
WHERE id = 'buyer_id' AND wallet_balance >= 40.00
-- rows affected: 1  → success, new balance RM 80.00

-- funds sit in escrow until meetup confirmed by both sides
-- only then: seller.wallet_balance += 40.00
05 Key Features
Email OTP Verification
Institutional email gating via 6-digit OTP stored in Redis with TTL. Unverified accounts can't transact, eliminates fake accounts.
Real-Time Chat + AI Support
Socket.IO buyer-seller messaging with typing indicators and read receipts. AI-powered support chatbot at /support/ai-chat with 3× daily rate limit.
Stripe + In-App Wallet
Full Stripe Checkout (MYR) with webhook fulfillment. In-app wallet with topup, balance display, and atomic deduction at checkout. SST breakdown in seller earnings.
Flash Sales + Product Boost
Admin-triggered flash sales with realtime stock counter. Sellers can boost listings (1d RM2 / 3d RM5 / 7d RM10) paid from wallet. Featured products on homepage.
Admin Dashboard (7 Tabs)
Platform analytics, user management, maintenance mode (Redis setex + Socket.IO broadcast), announcement banners, support chat acceptance, flash sale control.
Android TWA App
Native Android TWA APK (package: store.campusbay.twa) with Digital Asset Links verified, downloadable from campusbay.store/download.html.
06 The Math Behind Payments & Security
02Voucher & Discount Application
basePrice   = listing.price × quantity
discount    = voucher.discountAmount  (flat or % calculated)
sst         = (basePrice - discount) × 0.08  (if SST enabled)
totalAmount = basePrice - discount + sst

-- Stored on Orders table:
orders.discountAmount = discount
orders.voucherCode    = voucher.code
orders.totalAmount    = totalAmount

Voucher definitions can change or expire. If the order stored only a voucher reference, a later edit would silently rewrite historical totals. Storing discountAmount directly keeps every past order honest. Discounts are resolved once, at checkout, and the resulting amount is frozen onto the order row rather than recomputed later.

# Listing: RM 80, qty 2 → basePrice = 160
# Voucher "WELCOME10": 10% off, SST enabled at 8%

discount = 160 × 0.10        = RM 16.00
sst      = (160 - 16) × 0.08 = RM 11.52
total    = 160 - 16 + 11.52  = RM 155.52

orders.discountAmount = 16.00
orders.voucherCode    = "WELCOME10"
orders.totalAmount    = 155.52
03Boost Visibility Score
boost.expiresAt = NOW() + INTERVAL '${days} days'
boost.active    = (expiresAt > NOW())

-- Featured query: boosted products rank first
SELECT * FROM products
WHERE boost_expires_at > NOW()
ORDER BY boost_expires_at DESC, created_at DESC

Boost is time-based, not score-based — no decaying relevance algorithm, just a hard expiry written once at purchase. That keeps /products/featured a single indexed range query rather than a recomputed ranking job, which matters on one mini-PC where cron jobs compete with the live API.

# Seller buys a 3-day boost on 2026-06-19 14:00:00
boost.expiresAt = '2026-06-19 14:00:00' + INTERVAL '3 days'
                = '2026-06-22 14:00:00'

# Featured query at 2026-06-20 09:00:00:
WHERE boost_expires_at > '2026-06-20 09:00:00'
  → '2026-06-22 14:00:00' > '2026-06-20 09:00:00'  → True
  → product appears in /products/featured
04JWT Auth & Rate Limiting
Access Token:  HS256, expires in 15min
Refresh Token: HS256, expires in 7d, stored httpOnly cookie

Rate limits (express-rate-limit):
  /api/auth/*    → 10 req / 15min
  /api/wallet/*  → 20 req / 15min
  /api/support/* → 3 requests / day  (AI chat)

Short-lived access tokens limit the damage window: 15 minutes is enough for normal browsing, short enough that a stolen token is near-worthless. The refresh token never touches client-side JavaScript — httpOnly cookies are invisible to XSS — which matters most on a self-hosted stack with no CDN-level WAF.

# Login flow:
POST /api/auth/login → 200 OK
  Set-Cookie: refreshToken=...; HttpOnly; Secure; SameSite=Strict
  Body: { accessToken: "eyJhbGci...", expiresIn: 900 }

# 16th request to /api/auth/* within 15 minutes:
→ 429 Too Many Requests
  { "error": "Too many auth attempts, try again in 4 minutes" }
07 Use Cases & Impact
Student Textbook Exchange
Buy and sell second-hand textbooks at fraction of bookstore price within campus community.
Hostel Furniture Handoffs
Students graduating sell hostel items to incoming students, meetup scheduling built in.
Electronics & Gadgets
Verified student-to-student device resale with escrow protection and meetup coordination.
Student Entrepreneurship
Students running small businesses (food, crafts, services) can list and reach peers directly.
Tech Stack
React 18 Vite Node.js Express PostgreSQL Redis Sequelize Socket.IO Stripe Docker nginx Cloudflare JWT Nodemailer
Technical Report
Full architecture writeup — system design, data models, real-time layer, payments, and security, as a PDF.
Download Report (PDF)
Built solo. Shipped to production.
Every line, Docker Compose, Stripe webhooks, Socket.IO, JWT, wallet escrow, written alone. It's messy sometimes, and I ship things that break, but it's the fastest way I've found to actually learn full-stack engineering.
Visit campusbay.store