08 / 11
E-commerce
ArcadeVault
A three-app gaming storefront built to learn Stripe and file uploads the hard way.
- ROLE
- Full-stack developer, solo project
- BUILT FOR
- Personal learning project / portfolio piece
- STACK
- React + Vite (storefront and admin), Tailwind CSS (storefront), MUI + MUI Data Grid (admin), Node.js/Express backend, MongoDB/Mongoose, JWT auth, bcrypt, Multer (file uploads), Stripe Checkout
- PLATFORM
- Web app (three separate Vite/Node processes: customer frontend, admin frontend, backend API)

THE PROBLEM
Most "build a store" tutorials stop at a product grid. The two pieces that actually make an e-commerce app real — taking a payment and letting a non-developer upload product images — are usually glossed over or faked. This project was explicitly built to not skip those parts: it started out as an exploration of Stripe Payments that later expanded to include Multer for file uploads.
There's also a second, more mundane problem any store needs solved: someone has to manage the catalog. A JSON seed file doesn't cut it once you want to add a new console or retire a product, so the project splits into a dedicated admin app rather than bolting CRUD forms onto the customer site.
THE IDEA
Keep the three concerns — browsing/buying, catalog management, and the API/payment/data layer — as three separate Vite/Node apps sharing one Express backend and one MongoDB database.
The backend exposes a plain REST API that both frontends consume independently, with image uploads served statically from /images.
WHAT IT DOES
01
Storefront catalog
Home page with an explore-by-category menu and a product grid fed by the items and categories endpoints. Cart is backed by a React context calling the cart add/remove/get routes, all behind JWT auth.

02
Cart, checkout, and Stripe
Checkout posts to the order API, which builds Stripe line items (per-item price/quantity plus a flat delivery charge) and redirects to a hosted Stripe Checkout session. An order document is created before the redirect, and only confirmed — or deleted — when Stripe redirects back to a verify endpoint.

HOW IT WORKS
┌──────────────────────┐
│ MongoDB, via │
│ Mongoose models │
└──────────▲───────────┘
│
┌──────────┴───────────┐
│ backend/ (Express) │
│ /api/items │
│ /api/categories │
│ /api/user (JWT) │
│ /api/cart (JWT) │
│ /api/order (Stripe) │
│ /images/* (static) │
└───▲───────────────▲──┘
│ │
┌──────────────┴───┐ ┌───────┴────────────┐
│ frontend/ (React,│ │ admin/ (React, │
│ Vite, Tailwind) │ │ Vite, MUI) │
│ storefront, │ │ catalog CRUD, │
│ cart, checkout │ │ order list │
└──────────────────┘ └─────────────────────┘Stripe Checkout, not a custom payment form
The order controller delegates all card handling to Stripe's hosted Checkout session, sidestepping PCI scope entirely — the backend never touches raw card data.
Order-then-verify flow
An order document is created before redirecting to Stripe, and only confirmed (or deleted) when Stripe redirects back to /verify. This avoids needing Stripe webhooks for a small project, at the cost of relying on the client actually hitting the verify endpoint.
Generic "entity" model
Naming the product schema entity rather than product suggests an intent to keep it extensible to other sellable types, though nothing in the codebase currently uses that genericity beyond products.
No ORM-level cart model
Cart contents live as a loosely-typed Object field directly on the user document rather than a separate collection — a simple, denormalized approach that avoids a join at the cost of scalability if carts grow large.
CHALLENGES AND WHAT I LEARNED
Payment integration without webhooks
Confirming payment purely through the client-side redirect is simpler to build than a webhook listener, but it means a user closing the tab mid-redirect could leave an order in limbo — a real tradeoff of the exploration scope this project set out with.
Two frontends, one API
Keeping the admin app (MUI, router v7) and storefront (Tailwind, router v6) on different library versions worked fine in practice but shows the natural dependency drift of maintaining two separate Vite apps instead of a shared package.
File uploads outside the database
Multer writes images to a local uploads/ folder tracked in the repo rather than to cloud storage — fine for a local/demo project, but it would need to move to something like S3 before any real deployment.
RESULTS
Three independently runnable apps (backend, admin, frontend) sharing one REST API and one MongoDB database.
Full purchase loop implemented end-to-end: browse → add to cart → checkout via Stripe → payment verification → order history.
JWT-authenticated user accounts with bcrypt password hashing and server-side input validation.
Admin-managed catalog with image upload (Multer) for both categories and items, reflected live in the storefront via shared API endpoints.
AND ACROSS THE APP




STACK
LINKS

NEXT PROJECT
Prompt-O-Phobia
05 / CONTACT