An online store for everyday things.
Browse with live stock counts, save favourites, keep a bag that follows you across devices, and check out in under a minute with UPI, cards or netbanking.
haul is a small, focused web store for headphones, books, tees, mugs and other everyday things. Shoppers sign up, browse a catalogue that always shows how many units are left, save items for later, and pay through Razorpay. Prices, totals and stock are decided by the server, and an order only counts as paid once the payment signature has been verified.
It was built as ShopKart, a MERN project at Scaler School of Technology, and renamed haul with the redesign. The repository keeps its original name.
| Accounts | Sign up, log in and log out. Sessions live in an HTTP-only cookie for 7 days. Emails are case-insensitive. |
| Catalogue | Search by name, filter by category with shareable links (/products?category=Books), live stock on every card, "Almost gone" list for low-stock items. |
| Saved items | Heart any product from a card or its page; everything saved is on the Saved page. |
| Bag | Stored on the server, so it is the same on every device. Quantities are capped at stock, and products removed from the store drop out automatically. |
| Checkout | One address form with Indian mobile and pincode validation, then Razorpay (UPI, cards, netbanking, wallets). The total is recalculated on the server. |
| Orders | Order history, order details and a confirmation screen after payment. Stock is reduced exactly once per paid order. |
| Design | Sage and teal palette, Instrument Sans, rounded cards and pill controls, loading skeletons and empty states. Works from 390 px phones to wide desktops. |
![]() |
![]() |
- The shopper adds items to their bag. The server checks each quantity against stock.
- At checkout the browser sends only the shipping address. The server rebuilds the order from the bag and the live catalogue, creates a pending order and a Razorpay order for that exact amount.
- Razorpay Checkout opens and the shopper pays.
- The browser forwards Razorpay's result to the server, which checks the HMAC-SHA256 signature against the order ID it stored.
- Only then is the order marked paid, stock is reduced (never below zero), and the bag is emptied. A duplicate verification can't reduce stock twice.
A failed or abandoned payment leaves the bag untouched.
| Layer | Choice |
|---|---|
| Frontend | React 19, Vite 8, Tailwind CSS 4, React Router 7, Axios |
| Backend | Node.js 20+, Express 5 |
| Database | MongoDB with Mongoose 9 |
| Auth | JWT in an HTTP-only cookie, bcrypt |
| Payments | Razorpay Orders API + Checkout |
| Hosting | Vercel (frontend), Render (API), MongoDB Atlas |
- Node.js 20 or newer
- A MongoDB database (Atlas free tier works)
- Optional: Razorpay test keys (dashboard → Test Mode → API Keys). Without them everything runs except payment
git clone https://github.com/Priyansh10ff/ShopKart_SST.git
cd ShopKart_SST
# API → http://localhost:8001
cd backend
cp .env.example .env # fill in MONGO_URL and JWT_SECRET (Razorpay keys optional)
npm install
npm run dev
# Web app → http://localhost:5173 (second terminal)
cd frontend
cp .env.example .env # VITE_API_URL=http://localhost:8001
npm install
npm run devA new database has no products and there is no admin screen yet, so add them through the API:
curl -X POST http://localhost:8001/products \
-H "Content-Type: application/json" \
-d '{"name":"Wireless Headphones","description":"Over-ear, 30h battery","price":2499,"category":"Electronics","image":"https://example.com/headphones.jpg","stock":10}'Use the categories Electronics, Fashion, Books or Home so products appear under the filters. image must be a public image URL.
With rzp_test_ keys, pay with Razorpay's test cards or UPI IDs. No real money moves.
backend/.env
| Key | Required | Description |
|---|---|---|
NODE_ENV |
production turns on cross-site cookies (SameSite=None; Secure) |
|
PORT |
API port, default 8001. Render sets it automatically |
|
MONGO_URL |
✓ | MongoDB connection string |
JWT_SECRET |
✓ | Long random string used to sign login tokens |
CLIENT_URL |
✓ in production | Allowed frontend origin(s) for CORS, comma-separated |
RAZORPAY_KEY_ID |
Razorpay key ID. Optional, see below | |
RAZORPAY_KEY_SECRET |
Razorpay key secret. Optional, see below |
Running without payments: leave both Razorpay keys unset and the whole store still works (accounts, catalogue, saved items, bag, orders history). Only the final Pay step is disabled: checkout replies "Payments are not configured on the server yet." Add both keys and restart to turn payments on.
frontend/.env
| Key | Required | Description |
|---|---|---|
VITE_API_URL |
✓ in production | Base URL of the API. Built into the bundle, so redeploy after changing it |
| Piece | Platform | Notes |
|---|---|---|
| API | Render | render.yaml Blueprint: root backend, npm install, npm start, health check /health; you enter MONGO_URL, JWT_SECRET, CLIENT_URL. Razorpay keys optional |
| Web app | Vercel | Root frontend, framework Vite, VITE_API_URL = Render URL. vercel.json handles SPA routes and caching |
| Database | MongoDB Atlas | Allow network access from 0.0.0.0/0 (Render free tier has no fixed IP) |
- Deploy the API on Render (New → Blueprint → this repo) and check
https://<service>.onrender.com/health. - Deploy the frontend on Vercel with
VITE_API_URLset to the Render URL. - Set
CLIENT_URLon Render to the exact Vercel URL.
Full walkthrough, custom domains and a pre-launch checklist: docs/DEPLOYMENT.md.
| Location | Command | Does |
|---|---|---|
backend |
npm run dev |
API with auto-reload (nodemon) |
backend |
npm start |
API for production |
frontend |
npm run dev |
Vite dev server |
frontend |
npm run build |
Production build to dist/ |
frontend |
npm run preview |
Serve the production build locally |
frontend |
npm run lint |
ESLint |
ShopKart_SST/
├── backend/ Express API
│ ├── controllers/ Auth, products, cart, wishlist, orders and payments
│ ├── middlewares/ JWT cookie auth
│ ├── models/ Customer, Product, Order
│ ├── routes/
│ └── index.js App setup, CORS, /health, DB connect → listen
├── frontend/ React SPA
│ └── src/
│ ├── components/ Layout, Navbar, ProductCard, ProductImage, AuthLayout…
│ ├── context/ Auth and cart state
│ ├── pages/ Home, Products, ProductDetails, Cart, Checkout, Orders, Saved, auth
│ └── lib/ Formatting helpers
├── docs/ Product, PRD, technical and deployment docs
└── render.yaml Render Blueprint for the API
- Passwords hashed with bcrypt and never returned by the API.
- Session JWT in an
HttpOnlycookie;SecureandSameSite=Nonein production. - CORS limited to
CLIENT_URL; JSON bodies capped at 100 kB. - Prices and totals are always computed on the server; payments are verified with a timing-safe HMAC check.
- Every cart, saved-item and order query is scoped to the logged-in customer.
- Search input is escaped before it reaches MongoDB.
POST /productsis not protected yet. Keep the API URL private until admin-only access ships.- No Razorpay webhook: if a shopper pays and closes the tab before verification, the order stays pending. Reconcile from the Razorpay dashboard.
- On the default
vercel.app+onrender.comdomains, Safari blocks the cross-site login cookie. Use one custom domain for both (see DEPLOYMENT.md). - Render's free instance sleeps when idle; the first request after a pause takes 30 to 50 seconds.
| Doc | Contents |
|---|---|
| PRODUCT.md | What haul is, who it is for, how it works |
| PRD.md | Goals, user stories, acceptance criteria, release plan |
| TECHNICAL.md | Architecture, data model, payment flow, full API reference |
| DEPLOYMENT.md | Step-by-step deploy, custom domains, troubleshooting |

