Ahmed Abdelaziz

Custom Ecommerce Backend API for a Clothing BrandDesign Docs

Data Model

Twenty-four tables, variant modeling, immutable order snapshots, audit trail, and public IDs.

Last updated August 2026

Overview

Twenty-four PostgreSQL tables via Prisma 7 (@prisma/adapter-pg), modeling one idea carefully: what the customer bought must never change when the catalog does. Variants, snapshots, soft deletes, Decimal money, and an append-only audit trail do the heavy lifting.


The Variant Model

Apparel needs combinations, not just products:

  • products own identity: name, slug, brand, description, images.
  • product_variants own everything purchasable: SKU (unique), barcode, price, color, size, dimensions, status.
  • inventory is 1:1 per variant with quantity_on_hand, quantity_reserved, and reorder_level.

A black medium shirt and a blue large shirt are two variants of one product — each with its own price, stock, and photos.


Orders Are Immutable Snapshots

order_items copies at purchase time: unit price, discount percentage, product name/slug/SKU, and full variant attributes including dimensions. shipments copies the shipping address. Renaming a product or changing a price never rewrites history.

Why it matters

Invoices, returns, and audits all read order data as it was at purchase — not as the catalog looks today.


Identity: Two IDs Per Entity

Every major entity carries both:

IDPurpose
Internal autoincrement IntJoins and foreign keys
public_id — prefixed nanoid (usr_, prd_, var_, ord_, pimg_, vimg_, cat_, adr_, ses_)URLs and API responses

Internal IDs never appear in the API. Enumeration by guessing sequential IDs is structurally prevented.


Conventions That Hold Everywhere

  • Money: Decimal(10,2) (12,2 for operating_expenses) computed with decimal math, serialized as fixed strings. No floating-point anywhere.
  • Soft deletes: deleted_at on ten tables (users, user_addresses, products, product_variants, categories, coupons, reviews, order_items, payments, shipments); repositories always filter them out — nothing commerce-relevant is ever hard-deleted. audit_logs and coupon_usages are never deleted.
  • Audit trail: audit_logs is append-only by contract ("rows are never updated or deleted") — actor, action, entity type/id, method/path/status, request body (sensitive keys redacted), previous values/diffs, IP, user agent.
  • P&L: operating_expenses (rent/salaries/marketing/… with spent_at date) alongside COGS to compute true net profit.
  • Indexes with intent: composite indexes for real query patterns ([users_id, placed_at] on orders), dedicated index migrations added after query analysis; every FK column carries an index.

Auth & Security Tables

TableKeeps
sessionsSHA-256(token + SESSION_SECRET) hashes per device, IP/user-agent/device_name/country/city, last_activity_at, 14-day idle timeout, revocation
verification_tokensHashed email/email-change/phone-change tokens (24h for email, 10 min for phone OTP) — purpose + channel allow OTP and link rows to coexist
password_reset_tokensLink tokens + 6-digit OTPs (15 min TTL, 5-attempt lock, link invalidates prior tokens)
coupon_usagesPer-order redemption records (orders_id unique) backing global/per-user limits + hard 410/409 guards
audit_logsAppend-only trail: actor, action, entity, request body (redacted), prev/diff, status, IP/UA
operating_expensesP&L ledger: category enum, spent_at date, created_by_users_id (super_admin)

Result

A schema where catalog can change freely, orders cannot, and every security-sensitive action leaves an immutable trace.