Skip to content

Latest commit

 

History

26 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

customized-digital-store

A customizable e-commerce platform with admin dashboard, built test-first from business requirements (BDD/ATDD).

Stack

  • Backend: Node.js, Express, Mongoose (MongoDB) — server/
  • Frontend: React, Vite, React Router — client/
  • Testing: Jest + Supertest + mongodb-memory-server (backend), Vitest + React Testing Library (frontend)

A public product listing, product detail page, a cart that follows a signed-in shopper across devices, email/password accounts, and admin pages to list/create/edit/delete products (gated to the admin role).

Screenshots

Product catalog Product detail
Product catalog Product detail
Cart Events calendar
Cart Events calendar
Contact
Contact

Getting started

npm install       # installs both server and client workspaces

Database

No real MongoDB is required to run the app or the tests. The backend test suite spins up an in-memory MongoDB instance automatically via mongodb-memory-server, and npm run dev:server does the same as a fallback whenever MONGODB_URI is unset or unreachable — it seeds itself with the mock products so the app works with zero setup. Data in that in-memory fallback does not persist across server restarts.

Once you have a real database, copy server/.env.example to server/.env and set MONGODB_URI to your own connection string (a local mongod, Atlas, etc.). Then optionally seed it with the mock products:

npm run seed --workspace server

Running the app

npm run dev:server   # http://localhost:5000
npm run dev:client   # http://localhost:5173 (proxies /api to the server)

Accounts

Signup/login/logout is email + password, hashed server-side, with the session kept in a JWT stored as an httpOnly cookie (not readable by JS, not stored in localStorage). Every new signup is created as a customer — there is no way for a client to request the admin role, by design.

Creating the first admin account: sign up normally, then manually set that user's role field to admin directly in MongoDB (Atlas UI, mongosh, etc.) — there is no promote-endpoint. The signed-in browser session won't see the new role until that user logs out and back in, since the role is baked into their existing JWT.

Optional env vars (see server/.env.example, both have safe dev fallbacks so nothing is required to run locally): JWT_SECRET (falls back to an insecure dev secret with a console warning) and CLIENT_ORIGIN (defaults to the Vite dev server, used for credentialed CORS).

Signup has a verifyCaptcha middleware mount point (server/src/middleware/verifyCaptcha.js) that's currently a no-op placeholder — no captcha provider is wired up yet.

Cart

Logged-out visitors get a plain localStorage-backed cart, same as before. Logging in fetches that account's server-saved cart (GET /api/cart) and merges it with whatever was in the guest cart — quantities are summed for a product present in both, guest-only products are added alongside the account's existing items. Nothing a shopper added while browsing anonymously is discarded. From that point on, every cart change is debounced and pushed to the server (PUT /api/cart, a full-array replace — the server never merges, only the client-side login step does); localStorage is left untouched while authenticated. Logging out resets the visible cart to empty and clears the local cart key, so the next guest session starts fresh — the account's cart itself stays saved server-side and reappears (merging again) on the next login, from any device.

Checkout

Checkout uses Stripe Checkout (hosted, redirect-based) — the client never touches a Stripe SDK; the server creates a Checkout Session and the browser is redirected straight to Stripe's payment page. On checkout, the current cart is snapshotted into an Order (user, items with name/price/quantity captured at purchase time, totalAmount, status), which doubles as the receipt shown on /checkout/success and the foundation for order history later.

No STRIPE_SECRET_KEY configured yet — unlike JWT_SECRET/MONGODB_URI, there is no safe fallback for a payment provider. POST /api/checkout and GET /api/checkout/:orderId return 503 until a real Stripe test-mode key is added to server/.env (see server/.env.example).

No webhook — payment is confirmed when the browser lands back on /checkout/success and the client asks the server to verify the session (stripe.checkout.sessions.retrieve), not via a checkout.session.completed webhook. This is deliberately simpler (no raw-body signature verification, no STRIPE_WEBHOOK_SECRET, testable without Stripe CLI), but has a real limitation: if a shopper closes the tab before that verification call completes, Stripe will have charged them but the Order stays pending forever with no automatic reconciliation. A second, narrower accepted gap: if Stripe session creation fails right after the Order is saved (no transaction wraps the two writes), the Order is left pending with no stripeSessionId — surfaced as a 409 if revisited, not silently retried.

Product images

Admins can upload one image per product (POST /api/admin/products/upload, multipart/form-data, field name image) from the product form — selecting a file uploads it immediately and shows a preview. Images are limited to 2MB, must be an image mimetype, and only one file per request is accepted. Uploaded files are stored on local disk under server/uploads/ (gitignored) and served back at /uploads/<filename>; this is fine for local development but won't survive a redeploy on hosts without persistent disk (e.g. most serverless platforms) — swapping in a real object store (S3, Cloudinary, etc.) behind the same endpoint is a natural later upgrade.

Running tests (TDD workflow)

npm test                  # runs both server and client suites once
npm run test:server       # backend only
npm run test:client       # frontend only
npm run test:server:watch # backend, watch mode — good for red/green/refactor
npm run test:client:watch # frontend, watch mode

Project layout

server/
  src/
    app.js               # Express app (exported factory, used directly in tests)
    server.js             # entry point: connects DB, starts the HTTP listener
    seed.js                # inserts mockProducts into the connected database
    config/db.js           # Mongoose connect/disconnect helpers
    config/auth.js         # JWT secret/expiry/cookie config (dev fallback + warning)
    models/Product.js      # Product schema/validation
    models/User.js         # User schema — email/password (hashed)/role
    models/Cart.js         # One cart per user (unique user ref), items ref Product
    models/Order.js        # Many per user; items snapshot name/price at purchase time
    data/mockProducts.js   # seed data
    config/stripe.js        # Stripe client — null (503 on checkout) if STRIPE_SECRET_KEY unset
    controllers/           # request handlers (products, auth, cart, checkout)
    routes/                 # /api/products (public), /api/admin/products (admin),
                             # /api/auth (signup/login/logout/me), /api/cart (requireAuth),
                             # /api/checkout (requireAuth)
    middleware/             # asyncHandler, errorHandler, requireAuth, requireAdmin,
                             # verifyCaptcha placeholder, uploadImage (multer)
  uploads/                  # uploaded product images (gitignored, local disk only)
  tests/
    unit/                   # model validation, controllers with mocked models
    integration/            # full HTTP requests against an in-memory MongoDB

client/
  src/
    pages/                  # Home, ProductDetail, Cart, CheckoutSuccess/Cancel, Login, Signup, admin/*
    components/             # ProductCard, Header, RequireAdmin (route guard)
    context/                # CartContext, AuthContext
    services/api.js         # fetch wrappers for the backend API
    test/setup.js            # jest-dom matchers for Vitest
  (co-located *.test.jsx files next to the components/pages they cover)

Suggested next steps for TDD

Each business requirement should start as a failing test in server/tests/ and/or client/src/**/*.test.jsx before implementation. Natural next slices for this store:

  • A real checkout.session.completed webhook, for reconciling payments when a shopper closes the tab before the success page's verification call completes
  • Stock decrement on purchase
  • An order-history list page (the Order model already exists from checkout)
  • Refunds and multi-currency support
  • A cleanup pass for stale pending orders
  • A real captcha provider behind the existing verifyCaptcha placeholder
  • Password reset and email verification
  • Profile editing, "remember me", rate limiting on login, refresh tokens
  • A role-promotion endpoint (currently a deliberate manual-DB-edit step)
  • Product search/filtering by category
  • Move product image storage off local disk to a real object store for production use

About

A customizable e-commerce platform with admin dashboard, built test-first from business requirements (BDD/ATDD).

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages