Frontend
Mar 31, 2026 · 6 min read · 0 views
The Smart Cart
Multi-vendor e-commerce marketplace
The Smart Cart is a next-generation multivendor e-commerce platform built on the MERN stack. It empowers sellers with independent product catalogs, pricing flexibility, and streamlined order management, while giving buyers a fast, secure, and engaging shopping experience. From intuitive store management to real-time vendor communication and smooth checkout, every feature is designed to blend scalability, simplicity, and innovation in one platform.
- Timeline
- 2026-01 - 2026-03
- Origin
- Built from a tutorial, then extended
- Role
- Solo build. Followed a MERN roadmap tutorial for the core, then extended it.
Overview
The Smart Cart is a modern multivendor e-commerce platform built to handle the complexity of online retail while maintaining simplicity for users. Developed with the MERN stack, it empowers vendors to create independent stores with complete control over product catalogs, pricing, and order fulfillment. At the same time, buyers enjoy a seamless shopping journey with advanced filtering, wishlists, secure payments and responsive design across all devices.
Sellers get their own shop with control over catalog, pricing, and fulfillment. Buyers get one catalog, one cart, and one checkout across every seller, with three ways to pay and a direct line to whoever is shipping the order. Buyer, seller, and administrator each see three different surfaces of the same data.
Ten Mongoose models, thirty-plus REST endpoints, and a separate Socket.IO service for messaging.
Demo
A recorded walkthrough of The Smart Cart running, covering the main flows end to end.
Problem
Problems to solve
Seller storefront control
Small sellers need a storefront they can control without building and maintaining a separate marketplace presence.
Fragmented buyer journey
Shopping across several small sellers can mean separate catalogs, carts, checkouts, and accounts.
Limited buyer-seller contact
Buyers need a direct line to the seller handling an order rather than relying on an indirect email thread.
Coordinating independent sellers
Sellers need control over pricing, stock, and fulfillment while the marketplace still presents one coherent shopping experience.
The project brings seller control and a shared buyer journey into one marketplace without claiming production outcomes.
Goals
Goals for the build
- Seller autonomy
Independent shops with real control over products, orders, inventory, and earnings, through a dashboard that belongs to the seller rather than to the platform.
- One buyer experience
One catalog, one cart, one checkout across many sellers, with search, wishlists, and order tracking that do not fragment by vendor.
- Direct communication
Chat between buyer and seller, so a question about an order does not become an email thread nobody answers.
- Payment choice
Card, PayPal, and cash on delivery. Cash matters in this market and omitting it would exclude a large share of buyers.
- Vendor visibility
Vendors need visibility into their own orders and performance through an analytics surface tied to their store.
- Fulfillment control
Each seller needs control over products, inventory, pricing, and fulfillment within an independent shop.
The goals describe the marketplace relationships the build was intended to support, not measured business outcomes.
Tech stack
Routing
Styling
Backend
Database
Real-time
Payments
Media
Architecture
Technologies I work with day to day, from the frontend through the backend to deployment.
Features
Buyer
6 features
Search and filtering across the whole catalog
Wishlists and a cart that survives a refresh
Checkout through Stripe, PayPal, or cash on delivery
Coupon codes applied at the cart level
Order tracking, refund requests, and purchase history
Reviews tied to a completed order for verified purchases
Seller
6 features
Dashboard for product creation, editing, and stock management
Product images uploaded and managed through Cloudinary
Order fulfillment with status transitions and seller-side refund approval
Promotional events with countdown timers
Seller-specific coupon codes
Earnings tracking and withdrawal requests through a configured payout method
Platform
6 features
Role-based access enforced in API middleware
Admin routes protected from unauthorized client-side access
Analytics dashboard covering orders, users, products, and active events
MongoDB aggregation pipelines returning chart-ready analytics data
Transactional emails through an SMTP relay
Email notifications for account activation and order events
API design
Users and sellers
Buyers and sellers are separate models with separate auth routes, not one user table with a role flag. That was the tutorial's shape and I kept it. It duplicates registration, activation, and profile handling almost exactly. The clearest structural thing I would undo.
- Create UserPOST
/api/user/register - ActivatePOST
/api/user/activateemail activation token
- Login as UserPOST
/api/user/login - Update AddressesPUT
/api/user/update-addresses - DeleteDELETE
/api/user/delete-address/:id - Create SellerPOST
/api/seller/register - Update Withdraw MethodPUT
/api/seller/update-withdraw-method - All SellersGET
/api/seller/admin/all-sellersadmin only
Orders and refunds
Refunds are two separate routes rather than one with a role check, because the buyer's action is a request and the seller's is a decision. Collapsing them would have meant a single handler branching on who called it, and that branch is where an authorisation mistake would eventually live.
- CreatePOST
/api/order/create - GetGET
/api/order/user/:id - GetGET
/api/order/seller/:id - PutPUT
/api/order/seller/update-status/:id - PutPUT
/api/order/user/order-refund/:idbuyer requests
- PutPUT
/api/order/seller/order-refund/:idseller decides
Products and reviews
Reviews are a PUT onto the product rather than their own collection. Embedding keeps the read path to one query, which is the right call for a product page but it means a review cannot be moderated or paginated independently, and at any real volume that becomes the wrong call.
- AddPOST
/api/product/add - ListGET
/api/product/all - GetGET
/api/product/seller/:id - AddPUT
/api/product/review/add
Data model
Ten collections. The interesting decisions are all about where to embed and where to reference.
Productembedded reviews, referenced sellerReviews live inside the product document because they are always read with it and never on their own. One query renders a product page.
The cost is real: reviews cannot be paginated or moderated independently, and a product with thousands of them would carry all of them on every read.
Orderdenormalised cart snapshotAn order copies the cart contents at the moment of purchase rather than referencing live products. This is the one denormalisation I would defend without hesitation: a price change or a deleted product must not rewrite what someone already bought.
Sellerseparate root collectionSellers are their own collection rather than users with a role. It duplicates most of the auth surface, and it is the structural decision I would reverse.
Conversation and Messagereferenced, splitConversations hold the participants, messages reference the conversation. Splitting them keeps a long thread from growing one document past the point where it is cheap to read.
CouponCode, Event, Withdraw, VisitorreferencedAll small, all scoped to a seller, all queried independently. Nothing interesting here, which is the point. Most collections should be boring.
Key flows
Challenges
Checkout writes to several documents with no transaction
Problem
Placing an order writes an order, decrements stock on every product in the cart, and updates the seller's balance. MongoDB gave me no transaction across those writes.
If the process dies between the order write and the stock decrement, the buyer has an order for something the catalog still thinks is in stock. Money and inventory disagree, and nothing in the system notices.
How it was solved
I ordered the writes so the least damaging failure is the likeliest one: the order is written first, then stock, then balances. A crash after the order leaves stock briefly overstated, which a seller can correct; the reverse would silently take payment for nothing.
This is mitigation, not a fix. The correct answer is a transaction, and the honest version of this section is that I chose an ordering rather than solving the problem.
Three payment paths, three ways to mishandle money
Problem
Supporting Stripe, PayPal, and cash on delivery without widening the surface where a payment can be recorded incorrectly.
Cash on delivery is a meaningful share of e-commerce in this market, so omitting it was not an option. But it behaves nothing like the other two: there is no confirmation event, and the money arrives days after the order.
How it was solved
Card and wallet payments are tokenized through their SDKs, so no card data reaches the application. Cash orders are created in an unpaid state and only settle when the seller marks delivery.
Treating cash as a first-class state rather than a special case is what kept it from leaking conditionals through the order code.
Analytics that recompute on every dashboard load
Problem
Sellers needed sales, order, and revenue figures. With no budget for a scheduled job or a warehouse, every figure is aggregated live.
A seller without numbers cannot tell a good week from a bad one, and has no basis for pricing or restocking.
How it was solved
MongoDB aggregation pipelines return chart-ready shapes directly, so the client does no reshaping. Grouping and projection happen in the database, and only the summary crosses the wire.
The honest caveat: this is fine at the data volume it has ever held, and I have not tested it beyond that. At real scale these become a precomputed job, not a live query.
Practices
- Authorization in middleware, not in the interface. role checks sit on the route. Hiding a button is not access control.
- Payment data never touches the server. tokenized through the provider SDKs, so card details are out of scope entirely.
- Query shaping in the database. indexes, pagination, and projections, so a list endpoint returns a page rather than a collection.
- Global error middleware. one place that turns a thrown error into a response, instead of a try/catch in every controller.
- Secrets in environment variables. nothing committed, and the example file lists names without values.
- Real-time separate from persistence. Socket.IO handles live delivery; MongoDB stores the source of truth.
Two things are missing that ought to be here: there is no rate limiting on the auth routes, and no automated tests. Both are absent because I ran out of time, which is a reason and not a defence.
Metrics
- 15k+
- Lines of code
- 60+
- React components
- 30+
- API endpoints
- 10
- Mongoose models
counted
counted
counted
counted
Lessons
The most useful thing this project taught me was where a tutorial stops being enough. The core flows were guided; the multi-vendor payout logic, the aggregation queries behind the analytics, and the deployment of two separate services were not, and that is where I actually learned.
Data modelling decisions are expensive to reverse. The order schema went through three revisions because I modelled it around the checkout flow rather than around what sellers needed to query later. The aggregation pipelines behind the analytics dashboard would have been considerably simpler if I had started from the reporting requirements.
Separating buyers and sellers into two collections cost more than it saved. Registration, activation, password handling, and profile updates are all written twice, and every future feature that touches both has to be written twice too. One collection with a role field would have been less code and fewer places to get authorisation wrong.
Links
Related
Related work
The LearnX
- education
- video
- payments
- caching
Instructors selling premium video courses need their content protected from copying, payments handled reliably, and students enrolled automatically without manual admin.
The Care Nexus
- ai
- healthcare
- real-time
- international
Doctors lose clinical time to manual paperwork, patient records are scattered across visits, clinics run without operational insight, and English-only systems exclude a large share of patients.
Open to full-time remote roles and freelance work.