Production-oriented Express + TypeScript REST API for PopBox Studio, an anime merchandise and Ichiban Kuji storefront. The backend owns catalog data, inventory, checkout, Stripe payment finalization, order access, Kuji tickets, admin operations, transactional email, and operational safety rules.
This repo is backend-only. A separate Next.js storefront/admin client consumes the API but does not duplicate commerce, payment, inventory, or Kuji business logic.
| Area | Stack |
|---|---|
| Runtime | Node.js 22, Express 5, TypeScript |
| Database | PostgreSQL, Supabase, Drizzle ORM |
| Payments | Stripe Checkout, Stripe webhooks, refunds |
| Auth and storage | Supabase Auth JWT verification, Supabase Storage |
| Email and observability | Resend, Pino, Sentry |
| API quality | Zod validation, OpenAPI 3.1, centralized response and error handling |
| Operations | Docker, node-cron jobs, Postgres advisory locks |
| Testing | Vitest, supertest, TypeScript, ESLint |
- Versioned REST API under
/api/v1, with admin routes under/api/v1/adminand Stripe webhooks under/api/v1/webhooks/stripe. - Catalog APIs for products, collections, tags, search, recommendations, product images, legal content, FAQ, and store settings.
- Standard product checkout with server-side price calculation, Canada-only shipping, Stripe Checkout Session creation, and required
Idempotency-Key. - Inventory reservations with a fixed 10-minute TTL to prevent overselling while customers complete Stripe Checkout.
- Stripe webhook-driven payment finalization with raw-body signature verification, persisted webhook event dedupe, and retry-safe processing.
- Ichiban Kuji support: Kuji prize inventory, paid-order ticket allocation, unrevealed prize masking, idempotent reveal endpoints, and Last One prize handling where modeled.
- Guest order access through signed bootstrap links and an HttpOnly order session cookie.
- Admin workflows for catalog management, product images, inventory, Kuji prizes, orders, shipments, refunds, customers, legal pages, FAQ, and store settings.
- Sentry request sanitization for sensitive query values and headers, plus structured logging for checkout, webhook, email, and background job failures.
Routes stay thin and delegate to service modules. Validation happens at the HTTP boundary, consistency-sensitive operations use Drizzle transactions and Postgres row locks, and external providers are isolated behind integration modules.
flowchart LR
Client[Client and admin apps] --> API[Express API]
API --> Middleware[Middleware]
Middleware --> Routes[Routes]
Routes --> Services[Services]
Services --> DB[PostgreSQL]
Services --> Stripe[Stripe]
Services --> Supabase[Supabase]
Services --> Resend[Resend]
Stripe --> Webhook[Webhook route]
Jobs[Background jobs] --> DB
API --> Sentry[Sentry]
Main code areas:
src/routes: public, admin, checkout, order, legal, settings, and webhook routers.src/middleware: auth, validation, rate limiting, response envelopes, and exception handling.src/services: catalog, checkout, orders, webhooks, admin, notifications, settings, and legal logic.src/db/schema.ts: Drizzle schema for catalog, inventory, orders, payments, refunds, Kuji, webhooks, and content.src/jobs: reservation and pending-order cleanup with advisory locks.
Checkout session creation
- Client posts cart items to
POST /api/v1/checkout/sessionwithIdempotency-Key. - Server normalizes cart lines, locks product and inventory rows, validates active products and available stock, calculates subtotal and shipping, creates a pending order, inserts order items, writes inventory reservations, and creates a pending payment row.
- Server creates a Stripe Checkout Session with the same idempotency key and stores the Stripe session/payment identifiers.
- If Stripe session creation fails, reservations are released and the order is cancelled.
Stripe payment finalization
- Stripe calls
POST /api/v1/webhooks/stripe. - The raw request body is signature-verified before parsing.
- The Stripe event id is persisted in
stripe_webhook_eventsfor dedupe. - Paid checkout sessions finalize the order inside a transaction: customer/address snapshots are reconciled, reservations are converted, standard inventory is decremented, Kuji tickets are allocated, payment is marked paid, and order emails are attempted.
- Late or inconsistent paid sessions are moved to
paid_needs_attentioninstead of silently finalizing unsafe inventory.
Kuji draw and reveal
- Kuji product inventory is derived from non-Last-One prize remaining quantities.
- Prize rows are locked during paid-order ticket allocation.
- Tickets are assigned using weighted random selection over remaining prize quantities.
- Unrevealed ticket responses hide prize details.
- Reveal endpoints are idempotent and protected by an order-level advisory lock.
- Admin APIs verify Supabase JWTs server-side and check
public.users.role; client-side role flags are not trusted. - Guest order links use signed tokens, then exchange into an HttpOnly cookie session.
- Plaintext guest access secrets are not stored in the database.
- Zod validates params, query strings, and request bodies before service logic runs.
- Stripe webhooks use raw-body signature verification.
- Checkout, webhook handling, order actions, emails, refunds, and cleanup jobs use idempotency markers, uniqueness constraints, transactions, row locks, or advisory locks where appropriate.
- Sentry sanitizes
token,session_id,checkout_session_id,authorization,cookie,set-cookie, andx-order-token. GET /healthreports process and database health.
Prerequisites: Node.js 22+, pnpm, PostgreSQL or Supabase, Stripe test keys, Supabase project credentials, and Resend credentials.
pnpm install
pnpm devUseful commands:
pnpm build # TypeScript build to dist/
pnpm start # Run dist/index.js
pnpm test # Vitest launch/safety suite
pnpm check # ESLint --fix, TypeScript noEmit, then testsThe main environment variables are validated at startup in src/config/env.ts: database URL, Supabase URL/keys/storage bucket, Stripe secret and webhook secret, Stripe success/cancel URLs, CORS/client/admin URLs, Resend settings, order notification email, contact email, and ORDER_TOKEN_PEPPER.
The reservation TTL is intentionally fixed at 10 minutes. STRIPE_CHECK_SESSION_RESERVATION_TTL may be omitted or set to 600000.
openapi.ymldocuments the public/admin HTTP contract.pnpm testruns the Vitest launch suite covering checkout, webhooks, guest order access, Kuji tickets, refunds, settings, Sentry, catalog behavior, and cleanup jobs.- CI runs
pnpm install --frozen-lockfileandpnpm checkon Node.js 22.
The included multi-stage Dockerfile builds the TypeScript app and runs node dist/index.js as the non-root node user on port 3000. It is suitable for container platforms such as Northflank when the required environment variables are provided.
- Queue-backed workers for webhook follow-up work and cleanup tasks if traffic grows beyond in-process cron.
- Stronger admin audit logging for inventory, refund, and order-status changes.
- More integration coverage around live Stripe test-mode flows and refund reconciliation.
- Inventory reconciliation reports and operational dashboards.
- Targeted caching for hot read paths after profiling.
ISC