Skip to content
All work

STDNT GEAR

A quote-to-invoice B2B commerce platform for South African institutions ordering custom branded apparel — architected and built solo, end to end.

Role
Founding Engineer (solo)
Timeframe
Apr 2024 — Present
Live
View site
ReactNode.jsExpressPostgreSQLRedisSystem Design
STDNT GEAR storefront homepage showing the hero section and category navigation
The customer storefront — production, live at studentgear.onrender.com.
01

The problem

Schools, sports clubs, and corporate teams that want branded apparel — varsity jackets, team kits, event T-shirts — typically get a price the slow way: email a supplier, wait for a quote, go back and forth on quantities and print positions, wait again. There is no self-service way to browse a real catalogue, place a design on a product, and see a defensible price before committing.

STDNT GEAR replaces that loop with a platform where a customer picks a product, customizes it, and generates a priced quote themselves — while staff retain manual control over final pricing and approval, since custom print jobs still need a human sanity check before an invoice goes out.

02

Role & scope

I was the only engineer on this product. That meant every layer — schema design, API contracts, third-party integration, frontend architecture, deployment — was one continuous decision space rather than a handoff between teams. Concretely, I:

  • Designed the PostgreSQL schema (27 tables) from scratch.
  • Built the Express REST API (~70 endpoints) and all business logic.
  • Built the customer storefront (React) and a separate staff admin portal (React).
  • Engineered a data-ingestion pipeline syncing the full product catalogue — 3,000+ products, prices, stock, and colour swatches — from a third-party vendor API into PostgreSQL.
  • Deployed and operate the platform in production.
03

Architecture

Client layer

Customer storefront (React)
Staff admin portal (React)

API layer

Express REST API — ~70 endpoints, JWT auth

Data & external

PostgreSQL — 27 tables
In-memory + Redis cache
Vendor catalogue API

Both frontends talk to the same API. The storefront handles discovery, customization, and quoting; the admin portal handles pricing review, invoicing, and catalogue oversight. A scheduled ingestion job keeps the PostgreSQL catalogue in sync with the vendor's product API, since STDNT GEAR doesn't manufacture or warehouse stock itself.

04

Key engineering decisions

Vendor catalogue ingestion pipeline

Problem

The vendor API returns thousands of products with nested variants, prices, stock, and colour swatches. Naively inserting everything concurrently caused PostgreSQL deadlocks under load, and a single failed record could kill the whole sync.

Options considered
  • Single-threaded sequential inserts — safe but far too slow for a full catalogue refresh.
  • High concurrency (10+ parallel batches) — fast, but frequent deadlocks on shared indexes.
  • Smart batching with capped concurrency and per-operation retry.
Decision

Grouped products into batches of 50 (partitioned to reduce key overlap), capped concurrency at 2 in-flight batches, and wrapped every write in deadlock-aware retry with exponential backoff.

Trade-off

A full catalogue sync is slower than it could theoretically be, but it runs unattended and self-heals from transient PostgreSQL deadlocks instead of failing the whole job.

Server-authoritative pricing

Problem

Quotes and invoices involve price adjustments (staff discounts, corrections). If a client ever computed or held the authoritative total, a tampered request could change what a customer owes.

Options considered
  • Trust the price the client submits with the quote.
  • Recompute and lock the total server-side whenever a quote is priced or converted to an invoice.
Decision

Every quote total is calculated and locked server-side. Staff-applied adjustments are stored as discrete line items, not baked into a single mutable total, so invoice history stays auditable.

Trade-off

More server-side computation and stricter API contracts, in exchange for pricing integrity that can't be bypassed from the client.

Caching strategy for the product catalogue

Problem

Category trees and multi-facet product filters (brand, gender, material, fit, price) get slow at scale if every filter click hits PostgreSQL with fresh joins.

Options considered
  • No caching — simplest, but filter interactions get sluggish as the catalogue grows.
  • Cache everything in Redis from day one.
  • Layer caches by volatility: in-memory for near-static data, React Query for per-user UI state, Redis reserved for cross-instance needs.
Decision

Cache the category hierarchy in server memory with TTL + explicit invalidation on writes, run facet-count queries in parallel rather than sequentially, and let React Query handle client-side staleness.

Trade-off

In-memory cache doesn't survive a restart or share state across multiple server instances — acceptable for current scale, with Redis already integrated as the upgrade path.

05

The quoting flow, in practice

The catalogue view below is the direct product of the caching decision above — multi-facet filters (category, price, stock) running against 879 products in this category alone, with facet counts computed in parallel rather than serially.

Product catalogue page with category filters, price range facets, and a grid of apparel products with live stock counts
Multi-facet filtering over the synced vendor catalogue — categories, price bands, and per-product colour variants.
Quote builder quantities step showing per-size stock levels, unit price, and a live-calculated subtotal
Step 1 — per-size quantities against live stock, with the running total calculated server-side.
Quote builder design step showing print position selection (front/back) and an artwork upload panel
Step 2 — print positions and artwork, captured per line item before staff review.
06

Security & auth

Authentication is JWT-based with short-lived access tokens and HttpOnly refresh tokens, plus optional Google OAuth. Beyond the basics, I added account lockout after repeated failed logins, audit logging of authentication events, and rate limiting on auth endpoints — the kind of hardening that matters once a platform holds real customer accounts and payment-adjacent data, even without a payment gateway yet.

07

Challenges

The hardest problems weren't in the UI — they were in keeping a large, frequently-changing external catalogue consistent with an internal schema that also has to support fast filtering. Deadlocks under concurrent writes (solved above), keeping category and price caches from going stale after a sync, and designing a quote data model flexible enough for arbitrary print positions and personalization without becoming an unstructured blob were the recurring hard problems.

08

Scope & honesty

Deliberate scopeThere is no payment gateway or production-tracking system yet. Customers receive static EFT banking details by email, and fulfillment happens outside the platform. This was a conscious sequencing decision — quoting and invoicing were the highest-leverage problems to solve first, and I'd rather ship an honest, working slice of the workflow than a half-built payment integration.
09

Scale

27
PostgreSQL tables
~70
REST API endpoints
3,000+
products synced from vendor
2
frontends, 1 shared API