AS.
← Back to work

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)
Arcade Vault — sign in

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.

arcade-vault.app
Arcade Vault — Storefront catalog

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.

arcade-vault.app
Arcade Vault — Cart, checkout, and Stripe

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

Arcade Vault — screen 1
Arcade Vault — screen 2
Arcade Vault — screen 3
Arcade Vault — screen 4

STACK

ReactViteNode.jsExpressMongoDBStripeMulter

LINKS

NEXT PROJECT

Prompt-O-Phobia

→

05 / CONTACT

Let'sbuild
somethinggreat.