Ahmed Abdelaziz

Custom Ecommerce Backend API for a Clothing BrandDesign Docs

API Design

Seventy-seven endpoints under /api/v1 — envelope, validation, security and PDF reports per request.

Last updated September 2026

Overview

One versioned REST API — 77 paths under /api/v1 — speaking a single response dialect. Every endpoint validates input with zod, returns the same envelope, paginates the same way (or streams application/pdf), and fails with typed, predictable errors. The API contract lives twice: hand-maintained markdown in docs/api/** (authoritative) and a mirrored OpenAPI 3.1 spec (openapi/openapi.yaml) for Apidog/Postman import.


Response Envelope

Every response, success or failure:

{ "success": true, "data": { } }
{ "success": false, "message": "Validation error", "errors": { "email": "…" } }

Lists add pagination meta: { page, limit, total, totalPages, hasNext, hasPrev } — offset-based with page/limit, capped at 100 per page.


Endpoint Map

AreaEndpointsAuth
AuthPOST /auth/register, POST /auth/login (rate-limited + 10/15 min lockout), GET/DELETE /auth/session, GET /auth/sessions + per-device revoke + revoke-others, POST /auth/email-verification/*, POST /auth/password-reset + OTP verify + token verify, GET /auth/csrf-tokenPublic / customer
AccountGET/PATCH/DELETE /users/me, PATCH /users/me/password, POST /users/me/email + /verify, POST /users/me/phone-number + /verify, GET/POST/PATCH/DELETE /users/me/addresses (default-flag invariants, ownership-scoped)Customer
CatalogGET /products (search/brand/sort/paginated, customer visibility = has ACTIVE variant), GET /products/{id} (images ordered, is_primary, variants with final_price), GET /categories, GET /categories/{id}, GET /categories/{id}/productsPublic
CartGET /cart (404 if never created), POST /cart/items, PATCH /cart/items/{variant}, DELETE /cart/items/{variant}, DELETE /cart (all four mutations advisory-locked)Customer
OrdersPOST /orders (checkout: advisory-locked, coupon + shipping + mock payment + snapshots + cart clear), GET /orders + GET /orders/{id} (immutable snapshots, status-filtered)Customer
ReviewsGET /products/{id}/reviews (public, paginated), POST/PATCH/DELETE /reviews, GET /users/me/reviews, GET /reviews/{id} — purchase-gate flag off, ImageKit provenance validatedMixed
ReportsGET /admin/reports/pnl, /expenses, /revenue (+ .pdf aliases) by `monthquarter
UploadsGET /uploads/imagekit-auth and GET /admin/products/uploads/imagekit-auth (short-lived HMAC params)Customer/Admin

Admin surface (/admin/*)

Full catalog CRUD including nested variants and images, category management with product assignment, inventory adjustments + reservations, order status transitions, review moderation, coupon management with usage history, user management (role changes are SUPER_ADMIN only), admin account management, stats, audit log, analytics (overview / coupons / expenses — SUPER_ADMIN) and financial PDF reports (P&L / Expenses / Revenue by month|quarter|year|custom — SUPER_ADMIN, attachment|inline PDF or ?format=json).


Validation and Errors

  • One zod schema per endpoint validates body, query, and params together (including public_id prefix patterns usr_, prd_, var_, adr_, …); failures return 400 with per-field errors.
  • A typed error taxonomy (BadRequest 400, Unauthorized 401, Forbidden 403, NotFound 404, Conflict 409, Gone 410, TooManyRequests 429, …) flows to one central handler; prismaErrorMapper maps P2002 → 409, P2025 → 404, Postgres 22001 (string-too-long via cause chain) → 400 so internals never leak; stack is only included outside production.
  • Unknown /api/* paths return the JSON envelope too — never an HTML 404. Health probes /health//health/ready live outside the global rate limiter.

Security Per Request

MechanismDetail
Rate limitsGlobal default (RATE_LIMIT_MAX) plus stricter dedicated limiters on login, register, email verification/change, phone change, password reset; global limiter skipped in test, route limiters stay active
Login lockoutPer-email tracker: 10 failed attempts → locked for 15 minutes; failed admin logins audited, unknown-email probes logged
CSRFcsrf-csrf double-submit: x-csrf-token header + __Host-csrf cookie bound to the session cookie; anonymous (no session) skipped; fail-closed in production (ENABLE_CSRF boot guard)
Cookiessession cookie: httpOnly, Secure in production, SameSite=Lax; peppered SHA-256(session + SESSION_SECRET) hash stored server-side with 14-day idle timeout
Trust proxyTRUST_PROXY env drives app.set("trust proxy", …) — correct IP for rate limits/logs/session records behind Render/Neon

Contract Documentation

The API is documented twice by design:

  • Markdown contracts per domain under docs/api/ — the declared source of truth.
  • An OpenAPI YAML spec with shared envelope/pagination components, configured for Redocly linting.

Known gap

The OpenAPI spec is not yet linted in CI against the markdown contracts — drift between them is a tracked work item.


Design Rules in One Glance

  1. URLs use prefixed public IDs (ord_…, prd_…) — internal autoincrement IDs never leave the database.
  2. Money is Decimal(10,2) serialized as fixed strings — never floats.
  3. Every write that matters is validated, transactional, and audited.
  4. Pagination, sorting, and error shapes are identical across all fifteen modules.

Result

A client developer can guess any endpoint's behavior after learning three conventions: the envelope, the pagination meta, and the public-ID pattern.