Ahmed Abdelaziz
Back to projects
Custom Ecommerce Backend API for a Clothing Brand screenshot

Custom Ecommerce Backend API for a Clothing Brand

A TypeScript and Express REST API for a clothing commerce platform, covering customer accounts, product variants, inventory, carts, checkout, orders, reviews, administration, and signed product-image uploads.

typescriptexpressnode.jspostgresqlprismazodsession-authenticationimagekitresendpinovitest

July 2026

Overview

This project is the backend for a custom online clothing store. It provides the API that a web shop, mobile app, or other client can use to manage customers, clothing products, sizes, colors, stock, shopping carts, purchases, and reviews.

It is intentionally API-only. There is no product storefront in this repository, so the project focuses on the rules and data that a commerce client needs to rely on: secure accounts, accurate prices, stock protection, order history, and administrator workflows.

The Problem

A clothing store needs more than a product list. One product can have several purchasable combinations, such as a black medium shirt and a blue large shirt. Each combination can have its own SKU, price, images, and stock. The system must also keep an order's historical details stable after a product changes.

The backend therefore has to coordinate customer accounts, catalog management, stock, discounts, payments, and fulfillment without placing those rules in an unmaintainable collection of routes.

The Solution

The API separates each business area into a focused module and keeps HTTP handling, business rules, and database queries in different layers. Customers use public identifiers rather than internal database IDs. Commerce-critical work is performed transactionally so a checkout either completes as one consistent operation or leaves no partial order behind.

The model is especially appropriate for apparel because variants explicitly support attributes such as size and color, while also retaining SKU, pricing, dimensions, images, and inventory per variant.

Key Features

Customer Accounts and Sessions

Customers can:

  • register
  • log in
  • verify an email address
  • reset or change a password
  • update account details
  • manage addresses
  • view or revoke active sessions.

Why it matters: Customers can use the store across devices while the server retains control of session validity and revocation.

Product Catalog and Variants

Administrators can manage products, categories, product images, and variant images. Products support search, brand filtering, sorting, and pagination. Variants hold clothing-specific options such as size and color, along with SKU, price, discount, status, and dimensions.

Why it matters: A customer selects an exact purchasable item rather than an ambiguous parent product, so stock and pricing remain tied to the right SKU.

Stock-Aware Cart

Authenticated customers can create a cart, add variants, change quantities, remove lines, view the current cart, and clear it. Cart responses resolve the related product and image information and calculate the current line price.

Why it matters: The cart represents real variants and can reject invalid quantities before checkout.

Checkout and Orders

Checkout validates the cart and shipping address, calculates discounts and shipping, reserves and commits stock, records a payment result, creates a shipment, creates immutable order-item snapshots, and clears the cart.

Why it matters: A product edit made later does not rewrite what a customer bought, and concurrent checkouts cannot silently sell unavailable stock.

Coupons and Shipping Rules

Coupons support fixed-amount and percentage discounts, minimum order values, maximum discount caps, validity windows, global usage limits, and per-user usage limits. Shipping uses a flat fee below a configured threshold and free shipping at or above it.

Why it matters: Promotion rules are applied consistently by the API instead of being trusted to a client application.

Reviews and Moderation

Customers can create, update, and delete product reviews. Reviews include ratings, optional text, attached images, and approval state; administrators have moderation endpoints and aggregate rating data is available to clients.

Why it matters: The store can collect customer feedback while retaining an administrative review boundary.

Administration and Image Uploads

Role-protected endpoints support administration of users, products, categories, inventory, orders, and reviews. A separate admin:create CLI promotes an existing user to ADMIN or the first SUPER_ADMIN, rather than exposing privilege escalation through the public API.

Product and variant binaries are uploaded directly from an administrator's client to ImageKit. The API issues short-lived signed upload parameters and stores the resulting URL after the client registers it.

Why it matters: The API does not proxy large image files or expose the ImageKit private key.

User Experience

The API enables these primary flows:

  1. A customer registers and verifies the account through the email link served by the API.
  2. The customer logs in and receives an HTTP-only session cookie.
  3. The customer browses products, chooses a size or color variant, and adds it to a cart.
  4. The customer selects a saved address and optionally applies a coupon during checkout.
  5. The API creates the order, payment record, shipment, and stock changes as one transaction.
  6. The customer later lists or opens the order and can submit a review.
  7. An administrator manages catalog data, stock, order status, and review approval through protected endpoints.

How It Works

All versioned API routes are mounted below /api/v1. The Express application also exposes /health and three small static pages for email verification and the admin ImageKit upload demonstration.

Request Flow

Checkout Flow

Checkout runs inside a service-owned Prisma transaction. It locks the user's checkout scope, verifies the cart and address, conditionally reserves stock, calculates the total, applies and increments coupon usage when applicable, creates the order snapshots, records the mock payment, commits stock, confirms the order, and clears the cart. Any failure throws and rolls the transaction back.

Architecture

The project uses a layered architecture organized by business module. Dependencies move downward from HTTP routing toward persistence.

  • Application and middleware: src/app/index.ts assembles Express, Helmet, CORS, JSON parsing, cookies, rate limiting, request logging, static pages, and the global error handler.
  • Routes and controllers: Module routers define resource endpoints and controllers translate HTTP input and output. Controllers do not own database queries or checkout rules.
  • Services: Services implement account, catalog, cart, inventory, review, and order behavior. They coordinate repositories and own multi-write transactions.
  • Repositories: Repositories contain Prisma queries, ownership filters, pagination, joins, and selected raw SQL for derived inventory fields.
  • Shared modules: Typed errors, validation helpers, pagination, constants, logging, mail, SMS, ImageKit, and request types are reused across domains.
  • External adapters: Integrations are kept behind small boundaries so email, payment, SMS, and image behavior can be replaced or tested independently.

Technical Implementation

API Surface

The versioned API is grouped into these resources:

AreaExamples of responsibilities
AuthenticationRegistration, login, logout, verification, password reset, sessions
Users and addressesProfiles, account changes, saved shipping addresses
CatalogProducts, variants, categories, product and variant images
InventoryAdministrative stock adjustments and availability views
CartVariant line items and quantities
OrdersCustomer checkout and order history; administrative status transitions
ReviewsCustomer reviews, images, aggregates, and moderation
AdministrationRole-protected management endpoints across the domains

Successful responses use a { success: true, data } envelope. Errors use typed application errors and a consistent { success: false, message } shape. List operations expose pagination metadata where documented.

Authentication and Authorization

Authentication is session-based rather than JWT-based. A successful login stores a SHA-256 hash of the opaque session token in PostgreSQL and returns the raw token in the session cookie. The authentication middleware hashes the cookie on later requests, checks revocation, expiry, and active account status, then attaches the user to the request.

The database defines CUSTOMER, ADMIN, and SUPER_ADMIN roles. Authorization middleware checks the authenticated role, while services enforce ownership rules such as restricting carts, addresses, and orders to their owner.

Passwords are hashed with bcrypt. Verification and password-reset tokens are stored as hashes and have a lifecycle with expiry and one-time use. Email is sent through Resend; phone OTP delivery currently uses an SMS development stub that logs through the shared logger.

Validation and Error Handling

Zod schemas validate request bodies, query strings, and route parameters before controllers run. Business validation remains in services for rules such as ownership, purchasability, coupon applicability, and available stock.

Typed errors map expected failures to HTTP statuses including 400, 401, 403, 404, 409, and 410. Unexpected failures return a generic 500 response rather than internal implementation details.

Database and Data Model

PostgreSQL is accessed through Prisma ORM 7 and the @prisma/adapter-pg driver adapter. Money uses fixed-precision Decimal fields and timestamps use PostgreSQL timestamp types. The schema uses internal auto-incrementing IDs plus stable public IDs such as usr_..., prd_..., and ord_...; internal IDs are not serialized into API responses.

Important relationships are shown below. The complete schema is in prisma/schema.prisma.

Order items copy product name, slug, SKU, variant attributes, price, discount, and totals at checkout. Shipment rows also copy the selected address. These snapshots preserve historical order data when catalog or address records later change.

Commerce Consistency

  • Checkout uses a PostgreSQL advisory transaction lock for the user and conditional stock updates that require enough available quantity.
  • Inventory distinguishes on-hand and reserved quantities and provides reserve, commit, and release operations for order transitions.
  • Coupon usage is incremented with a conditional update so a usage limit cannot be exceeded by racing requests.
  • Repositories accept either the normal Prisma client or the transaction client, allowing service transactions to reuse the same query methods.
  • List endpoints commonly run count and page queries in parallel and use explicit sort tie-breakers for stable pagination.
  • Inventory availability and stock status that are not directly expressible through typed Prisma filters are derived with schema-aware raw SQL queries.

Media and Integrations

  • ImageKit: An authenticated administrator requests short-lived HMAC upload parameters from GET /api/v1/admin/products/uploads/imagekit-auth. The client uploads directly to ImageKit, then registers the returned HTTPS URL through an admin image endpoint. The API stores URLs, not binary files.
  • Resend: Registration and email-change flows send transactional messages asynchronously. Failures are logged without turning a successful account operation into a failed request.
  • SMS: The current adapter is a development stub, not a production SMS provider.
  • Payments: PaymentGateway is an interface with a MockPaymentGateway implementation. The current checkout records mock payment references; a real provider is not integrated.

Logging and Operations

Pino writes structured records to the terminal and to logs/log.json. Request logs include identifiers, method, URL, status, duration, and available user or client context. Development uses pretty terminal output; production uses structured terminal output. Log-file writes are serialized and logging failures do not crash the application.

Environment configuration is parsed with Zod at startup. Required configuration includes the database URL, session secret, CORS origin, Resend settings, ImageKit settings, port, environment, and log level. Secrets are not documented here.

Testing

Vitest is the single test runner, with Supertest for HTTP-level API tests. The repository contains:

  • Unit tests for validators, token helpers, OTP behavior, slugs, sorting, stock rules, and other pure logic.
  • Integration tests against PostgreSQL for repository and service behavior.
  • API/e2e tests through the exported Express app for authentication, authorization, response contracts, and resource endpoints.

External services are mocked or represented by development adapters. Test configuration uses a dedicated database environment and disables parallel file execution because database-backed tests share a schema.

Architecture Decision Records

ADR-001: Use Client-Side Signed ImageKit Uploads

Status: Accepted

Context: The API needs product and variant images but does not have a frontend-specific binary upload service. Uploading files through the API would add multipart parsing, temporary storage, bandwidth, and request-size concerns.

Decision: Authenticated administrators receive short-lived ImageKit upload parameters. Their client uploads directly to ImageKit, then submits the resulting URL to the API.

Why: This keeps the ImageKit private key on the server while preventing the API from proxying image bytes. The decision is documented in docs/adr/0001-imagekit-client-side-signed-upload.md.

Alternatives Considered: Server-side proxy uploads; self-hosted file storage; accepting client-generated URLs without an authenticated credential endpoint.

Trade-offs: The API has less visibility into the uploaded binary and URL registration still relies on administrator authorization plus URL-shape validation. ImageKit files also require separate cleanup if an image record is deleted.

ADR-002: Use Server-Side Sessions Instead of JWTs

Status: Accepted implementation

Context: The API needs revocable, device-aware authentication for customer and administrator clients.

Decision: Store session records server-side and send an opaque token in an HTTP-only cookie. Store only a hash of the token in PostgreSQL.

Why: This supports expiry, per-session revocation, revoking other sessions after a password change, and device metadata without exposing session state to clients.

Alternatives Considered: JWT access and refresh tokens; OAuth or social login.

Trade-offs: Each authenticated request performs session lookup work and the application must manage session storage and expiry. The implementation gains direct revocation and centralized control.

ADR-003: Use a Layered Module-Per-Feature Structure

Status: Accepted implementation

Context: Catalog, account, cart, inventory, order, and review rules need to evolve without turning route handlers into a single application-wide dependency cluster.

Decision: Organize each domain into routes, controllers, services, repositories, DTOs, and validators where needed. Keep the dependency direction Router → Controller → Service → Repository → Database.

Why: HTTP concerns remain thin, business rules are easier to test independently, and database queries have a clear ownership boundary.

Alternatives Considered: A flat route-and-query structure; a microservice split for each domain.

Trade-offs: There is some repeated module structure and more files to navigate, but the current monolith keeps transactions and domain changes straightforward without distributed-system overhead.

ADR-004: Isolate Payments Behind a Gateway Interface

Status: Accepted implementation

Context: Checkout needs to record payment results now, while a real payment provider is outside the current scope.

Decision: Define a PaymentGateway interface and use MockPaymentGateway in the current implementation.

Why: Order logic can exercise the payment step and persist a transaction reference without coupling checkout to a provider SDK.

Alternatives Considered: Calling a provider SDK directly from the order service; postponing payment modeling entirely.

Trade-offs: The current payment result is not real-world settlement, but replacing the adapter does not require rewriting order and inventory orchestration.

Challenges and Solutions

Challenge: Preventing Overselling During Concurrent Checkout

Problem: A simple read-then-write stock check can allow two requests to purchase the same last item.

Solution: Checkout uses an advisory transaction lock and conditional stock reservation updates. A failed reservation aborts the transaction.

Result: Stock reservation, order creation, payment recording, and cart clearing share one rollback boundary.

Challenge: Preserving Historical Order Information

Problem: Product names, variant attributes, prices, or saved addresses can change after a purchase.

Solution: The service copies relevant product, pricing, and address values into order-item and shipment snapshots.

Result: Order history remains readable and accurate independently of current catalog data.

Challenge: Keeping Coupon Limits Correct Under Concurrency

Problem: Checking coupon usage and incrementing it as separate unguarded operations could exceed global or per-user limits.

Solution: Usage is checked and incremented inside the checkout transaction, with conditional updates and a usage-history row.

Result: Coupon rules are applied by the same atomic operation that creates the order.

Challenge: Supporting Image Management Without an API Upload Proxy

Problem: The repository has no product frontend and the API should not receive large binary image bodies.

Solution: The ImageKit adapter generates short-lived signed credentials for authenticated administrators; the client uploads directly and submits only the resulting URL.

Result: The API remains JSON-oriented and the private ImageKit key never leaves server configuration.

Technology Stack

TechnologyPurpose
Node.js and Express 5HTTP server, routing, middleware, and REST API
TypeScript, strict modeStatic type checking and explicit contracts
PostgreSQLRelational persistence, constraints, transactions, and advisory locks
Prisma ORM 7 with @prisma/adapter-pgTyped database access using the PostgreSQL driver adapter
ZodRuntime validation for environment configuration and request input
bcrypt and nanoidPassword hashing, opaque tokens, and public identifiers
ImageKitCDN-backed product and variant image storage with signed uploads
ResendTransactional email delivery
PinoStructured request and application logging
Vitest and SupertestUnit, integration, and HTTP API testing
Helmet, CORS, cookies, and rate limitingHTTP hardening and request controls

Project Structure

project-root/
├── src/
│   ├── app/                         Express assembly, middleware pipeline, and static pages
│   ├── config/                      Environment validation and Prisma client setup
│   │
│   ├── modules/
│   │   └── products/
│   │       ├── controller/
│   │       ├── dto/
│   │       ├── repository/
│   │       ├── routes/
│   │       ├── services/
│   │       ├── validators/
│   │       └── index.ts             Module entry point
│   │
│   ├── middleware/                  Request ID, rate limiting, auth, validation, errors
│   ├── shared/                      Errors, logger, mailer, SMS, ImageKit, constants, utilities
│   └── routes/                      Versioned router mounting under /api/v1
│
├── prisma/
│   └── schema.prisma                PostgreSQL models, relations, enums, and indexes
│
├── tests/
│   ├── unit/                        Pure logic and validator tests
│   ├── integration/                 Service and repository tests with PostgreSQL
│   └── e2e/                         HTTP API tests through Supertest
│
├── scripts/
│   └── create-admin.ts              Operator CLI for initial admin promotion
│
├── public/                          Email verification and ImageKit demo pages
│
└── docs/                            Architecture, API, database, testing,
                                     operations, and ADRs

Deployment and Configuration

The repository includes local development and build/test commands, but no production hosting configuration or live demo URL. It does not contain a Dockerfile or provider-specific deployment files.

Typical local commands are:

  • npm run dev for the development server with tsx watch mode.
  • npm run build followed by npm start for the compiled server.
  • npm run db:generate and npm run db:migrate for Prisma client/schema workflows.
  • npm test for the full Vitest suite.

Environment variables are validated at startup. Database credentials, session secrets, email credentials, and ImageKit keys must be supplied through the environment and are intentionally not included in this document.

The repository includes a GitHub Actions CI workflow for pull requests to main. It installs dependencies, generates Prisma, typechecks, builds, prepares a dedicated PostgreSQL 16 test database, creates the test environment from repository secrets, and runs the full suite. This validates the project in CI but is not a production deployment pipeline.

What This Project Demonstrates

Product and Business Perspective

  • Translating a clothing store's needs into reusable products, purchasable size/color variants, stock, carts, orders, and reviews.
  • Designing customer and administrator flows around real commerce rules rather than only CRUD screens.
  • Protecting historical order information and making promotion, shipping, payment, and fulfillment states explicit.

Engineering Perspective

  • Layered, module-per-feature backend architecture with thin controllers and repository boundaries.
  • Session security with hashed opaque tokens, HTTP-only cookies, revocation, role authorization, validation, rate limiting, and bcrypt password hashing.
  • Transactional checkout with concurrency control, stock reservation, coupon usage guards, payment abstraction, and immutable snapshots.
  • Typed PostgreSQL access, public/private ID separation, structured logging, validated configuration, and external-service boundaries.
  • Unit, integration, and HTTP API testing against documented contracts, with third-party services isolated from the test suite.

Scope Notes

This repository is a backend foundation, not a complete hosted storefront. The current payment adapter is a mock, the SMS integration is a development stub, and there is no production deployment configuration or frontend application in the repository. Those boundaries are intentional and should be considered when evaluating the project.