Welcome to the ultimate technical guide for Karma (formerly Snitch), a premium full-stack fashion e-commerce marketplace.
This document is designed not just as a standard README, but as a comprehensive learning resource. If you are preparing for an interview or need to understand exactly how this application works under the hood, read this document carefully. It explains every technology used, why it was chosen, and how the different pieces of the architecture talk to each other.
The frontend is responsible for everything the user sees and interacts with. It needs to be fast, responsive, and beautiful.
- React (v19): The core library used to build the user interface using reusable components. React uses a Virtual DOM to efficiently update the UI without reloading the page.
- Vite: Our build tool and development server. Why not Create React App (CRA)? Vite is significantly faster than CRA because it leverages native ES modules in the browser, making server start times and hot-module replacement (HMR) nearly instant.
- Redux Toolkit (RTK): Used for Global State Management. Why Redux? While React has Context API, Redux is better for complex applications with lots of moving parts (like a shopping cart, user sessions, and product lists) because it prevents unnecessary re-renders and provides a centralized "store" of data.
- Tailwind CSS (v4): A utility-first CSS framework. Why Tailwind? Instead of writing thousands of lines of custom CSS, Tailwind allows us to style components directly in the HTML/JSX. We used it to build a highly custom, dark-themed, premium editorial aesthetic without relying on cookie-cutter component libraries like Bootstrap.
- React Router DOM: Handles navigation. It allows us to create a Single Page Application (SPA) where clicking a link swaps out the components instantly without sending a request to the server to load a new HTML page.
The backend handles the business logic, database connections, and security.
- Node.js & Express.js (v5): Node allows us to run JavaScript on the server. Express is a lightweight framework built on top of Node that makes it incredibly easy to create RESTful API routes (e.g.,
GET /api/products). - MongoDB & Mongoose: Our database layer. MongoDB is a NoSQL database, meaning it stores data in flexible, JSON-like documents rather than strict tables. Mongoose is an Object Data Modeling (ODM) library that allows us to define strict schemas (rules) for our data (e.g., ensuring a User document always has an email).
- Passport.js & Google OAuth 2.0: Middleware for authentication. We use Passport to abstract away the complex logic of communicating with Google's servers to verify a user's identity when they click "Login with Google".
- JSON Web Tokens (JWT): The standard for securely transmitting information between the client and server. When a user logs in, we give them a JWT as an "entry pass".
- Bcrypt.js: A cryptography library. Crucial Security Concept: We NEVER save plain text passwords in the database. Bcrypt "hashes" the password into a scrambled string, making it impossible for hackers to steal passwords even if the database is breached.
- ImageKit.io: A cloud-based Image CDN (Content Delivery Network).
- Multer: A Node.js middleware for handling file uploads (
multipart/form-data).
Understanding authentication is critical for any full-stack developer. We use HTTP-Only Cookies, which is the gold standard for web security.
The Traditional Login Flow:
- User types email and password on the React frontend.
- React sends a
POSTrequest to/api/auth/login. - Express looks up the user in MongoDB.
- Express uses
bcrypt.compare()to check if the password matches the hash. - If valid, Express signs a JWT (JSON Web Token).
- Security Magic: Instead of sending the token back as JSON (where React would have to save it in LocalStorage—which is vulnerable to Cross-Site Scripting / XSS attacks), Express attaches the JWT to an HTTP-Only Cookie.
- The browser automatically saves this cookie. For all future API requests, the browser automatically attaches this cookie, proving the user is logged in.
The Google OAuth Flow:
- User clicks "Login with Google".
- React redirects the user to our backend route
/api/auth/google. - Passport.js redirects the user to Google's consent screen.
- Google redirects back to our backend with the user's profile info.
- We check if the user exists in MongoDB. If not, we create an account for them.
- We generate the JWT, attach it to the HTTP-Only cookie, and redirect the user back to the React dashboard.
Handling images efficiently separates junior developers from mid-level/senior developers. You never want to save user-uploaded images directly onto your backend server's hard drive because it doesn't scale.
- Frontend: User selects 4 images in the "Edit Product" React form and hits submit. React packages these files into a
FormDataobject and sends them to the backend. - Backend (Multer): The request hits an Express route guarded by the
multermiddleware. Multer reads the incoming files and buffers them in RAM (Memory Storage) rather than saving them to the server's disk. - Backend (ImageKit): Our Express controller takes those memory buffers and streams them directly via API to ImageKit.io.
- Cloud: ImageKit receives the files, stores them on global edge servers, and returns a fast, optimized public URL (e.g.,
https://ik.imagekit.io/karma/shirt1.jpg). - Database: Express takes those new URLs and saves them into the MongoDB Product document. It responds to React with "Success!".
The cart is the heart of an e-commerce app. It requires precise synchronization between the database and the global UI state.
- Price Snapshotting: When an item is added to the cart, we save the current price inside the cart document.
- Why? This prevents "price shock" where a user adds an item at $50, the seller changes it to $70, and the user's cart total changes without notice. It locks in the price at the time of the "Add to Cart" intent.
- Variant Support: Our cart doesn't just track products; it tracks specific variants (Size, Color, etc.). We use Mongoose's
$incoperator to efficiently increase quantity if the same product-variant combination is already in the cart. - Optimistic UI vs. State Sync: We use Redux Toolkit to manage the cart globally. When a user adds an item, we perform the API call and update the Redux store. This ensures the cart count (badge) and the cart page stay perfectly in sync across different components.
- Authentication Gate: The cart is protected by an
authMiddleware. If a guest user tries to add to cart, the frontend intercepts the 401 error and redirects them to the login page, preserving their intent to shop.
Handling payments securely is critical for any e-commerce application. We implemented Razorpay as our payment gateway, ensuring that sensitive financial data never touches our own servers, which reduces our PCI compliance burden.
The Payment Lifecycle:
- Order Creation (Backend): When a user initiates checkout, the React frontend requests an "order" from our backend. Our Node.js server securely communicates with the Razorpay API using our private key to create a unique
order_id. We send this ID back to the frontend. - Checkout Initiation (Frontend): React loads the Razorpay checkout script and opens the payment widget, passing the
order_idand the user's details. The user enters their payment information securely within Razorpay's environment. - Payment Processing (Razorpay): Razorpay securely processes the transaction with the bank or card network.
- Signature Verification (Backend): Upon successful payment, Razorpay sends a response back to the frontend containing three critical pieces of data:
razorpay_order_id,razorpay_payment_id, and arazorpay_signature. The frontend immediately sends these to our backend verification route. - Security Check: Our backend independently generates a cryptographic signature using the
order_id,payment_id, and our secret Razorpay key. We compare our generated signature against therazorpay_signaturesent by the client. If they match perfectly, we cryptographically prove the payment is authentic and wasn't tampered with. - Order Fulfillment: Once verified, we clear the user's cart in the database, mark the order status as successful, and the frontend redirects the user to our ultra-minimalist Order Success page.
To build a bulletproof production shopping cart, we designed a unified data model, high-performance API endpoints, and a multi-collection MongoDB aggregation system.
Rather than keeping cart data in client-side LocalStorage (which disappears across devices and blocks marketing triggers like abandoned cart recovery emails), the Cart is saved directly on the backend database matching a:
- One-to-One Owner Relationship: Links a specific authenticated
Useraccount to exactly one parentCartdocument. - Granular Items Array: An embedded list of products inside the cart. Each item specifies:
- Product Reference: A clean link pointing back to the Product Catalog.
- Variant ID: A specialized identifier linking directly to the specific size or color variant.
- Quantity: The designated quantity count.
- Snapshotted Base Price: The historical price locked in at the exact moment of addition, keeping the cost stable even if the seller edits catalog details.
The database handles updates through five core endpoints protected by secure user session validation:
POST /api/cart/add/:productId/:variantId- Validates inventory and registers the item/variant.GET /api/cart- Triggers the Aggregation Pipeline to populate catalog and user data.PATCH /api/cart/quantity/increment/:productId/:variantId- Atomically increments cart counts.PATCH /api/cart/quantity/decrement/:productId/:variantId- Atomically decrements cart counts, removing the item if it hits zero.DELETE /api/cart/remove/:productId/:variantId- Safely pulls the variant array item out of the cart.
To render the detailed shopping cart page, our application must combine data from three separate sources: User data (session identification), Cart data (quantities and selections), and Product Catalog data (images, titles, and variant descriptions).
Instead of running multiple separate database queries (which creates massive performance bottlenecks and increases server response latency), we engineered a single, highly-efficient MongoDB Aggregation Pipeline that joins the Cart, User, and Product collections and calculates totals in one roundtrip:
$match: Filters the initial database query by the active user's ID to fetch only their specific cart document.$unwind(Items Array): Deconstructs the items array into separate flat documents so that each individual product-variant selection can be matched, joined, and priced independently.$lookup(Product Join): Performs a database join between our cart item's product reference and theproductscollection, populating full product metadata (images, brand, description).$unwind(Product & Embedded Variants): Flattens the retrieved product details and its internalvariantsarray so we can query specific catalog attributes.$match(Variant Comparison): Evaluates dynamic comparisons between the selected cart item'svariantID and the product's embedded variant ID, ensuring we extract only the size/color combination the user actively selected.$addFields(Dynamic Line Sums): Dynamically multiplies item quantity by variant price (quantity * variant.price.amount) on the database side, creating a live line subtotal.$group(Reconstruct & Rollup): Groups all flattened documents back together by the Cart ID, calculating the grandtotalPricesum, choosing the primarycurrencydenomination, and pushing all matching product-variant details back into a clean, unifieditemsarray.
This pipeline offloads heavy calculations and relationships directly to MongoDB's highly optimized engine, keeping our Node.js memory footprint extremely lightweight and responding to the frontend instantly!
Our cart controllers prioritize data integrity and prevent race conditions:
- Embedded Stock Verification: Before adding items, our controller fetches the embedded variant from the catalog, checks its
stockvalue, and rejects the addition with an HTTP 400 if the cart exceeds current physical warehouse inventory. - Concurrency Protection (Atomic Operations): Instead of reading, modifying, and saving the full array in JavaScript, we use MongoDB atomic operators like
$incand positional operators (items.$.quantity) to increment/decrement counts inside a single query. - Atomic Pruning: Removing items completely is performed in a single database instruction using the
$pulloperator.
Global cart synchronization is managed via Redux Toolkit (RTK) to handle badge totals and optimistic updates:
- Initial State: Manages items, grand subtotals, and core currency.
- Core Reducers:
setCart: Populates the populated items and subtotals retrieved from the aggregation payload.incrementCartItem&decrementCartItem: Instantly alters local counts, allowing the UI to remain snappy (optimistic rendering).removeCartItem: Filters out the matching variant immediately on checkout panels.
During the latest release, we solved real-world mobile debugging challenges and created an elite streetwear diagnostic interface.
- The Problem: When developers test e-commerce sites on actual mobile phones, they connect via the local network IP (e.g.
http://192.168.1.15:5173). However, our backend Google callback had a hardcoded redirect target tohttp://localhost:5173/. Once mobile users gave Google permission, the backend redirected their phone's browser tolocalhost, which crashed with a "connection refused" error because the Vite server wasn't running inside the phone itself. - The Solution:
- We modified the
/api/auth/googleroute handler to extract the original calling frontend origin dynamically from the request'sRefererheader. - We passed this origin URL into Google OAuth 2.0 via the official
stateparameter:passport.authenticate("google", { scope: ["profile", "email"], state: origin }) - Google authenticates the request and passes the
stateparameter back to our/google/callbackURL unchanged. - Our backend callback reads
req.query.stateand dynamically redirects both successes and failures back to the correct calling host (e.g.,http://192.168.1.15:5173/or a custom production domain), fully fixing mobile Google logins!
- We modified the
- The Problem: Mobile users landing on
/loginor/registerwere stuck with no visible option to exit the auth screens and return to the main store catalog without using their browser's back button. - The Solution: We implemented a highly visible, absolute positioned "← Back to Shop" button at the top-left of both screens (
top-6 left-6 z-20). Styled with modern borders, typography, and a hover micro-animation that slides the arrow, it gives guest shoppers a seamless return route.
We built an extremely premium catch-all route mapped to * in the React Router definitions, serving as both a 404 handler and an active system health dashboard:
- Off-White Industrial Style: Designed with a deep black backdrop, a digital grid layout, neon-red blinking beacons, and large outlined numbers with a solid "OFFLINE" running overlay.
- Active Connection Health Check: Features a terminal console running mock diagnostics, and a "Retry Connection" button that pings the backend
/api. If the server is offline, it informs the user the gateway is down and asks them to check back in an hour. If the backend responds, it reports a successful handshake and auto-redirects them back to the shop catalog!
If an interviewer asks you about this project, they don't just want to know what you built; they want to know why you made specific technical decisions. Use these answers to demonstrate deep understanding.
Q: "Why did you choose MongoDB over a SQL database like PostgreSQL?"
Answer: "For an e-commerce platform like Karma, product catalogs can be highly variable. A shirt might have sizes and colors, while an accessory might have entirely different attributes. MongoDB's schema-less document structure allowed me to rapidly iterate on the Product schema without dealing with complex SQL migrations or rigid table joins. It maps perfectly to JavaScript objects in my Node/Express backend."
Q: "How did you secure user sessions in your app?"
Answer: "I avoided using LocalStorage for session tokens because it exposes the application to XSS (Cross-Site Scripting) attacks—any malicious script could read the token. Instead, I implemented JWTs stored inside HTTP-Only, Secure cookies. This means the token is handled entirely by the browser and cannot be accessed via JavaScript on the client side, significantly increasing the security posture of the authentication flow."
Q: "How did you handle the performance of loading high-quality fashion images?"
Answer: "Serving large images directly from my Express server would bottleneck the application and increase hosting costs. I implemented a cloud architecture using ImageKit as a CDN. I used Multer in memory-storage mode to intercept uploads and pipe them directly to ImageKit. This not only offloaded the storage burden from my server but also ensured images are served globally with automatic format optimization (like WebP), resulting in a much faster, premium UI experience."
Q: "Why did you use Redux Toolkit for state management?"
Answer: "Initially, I could have used React's Context API, but as the app grew—managing user authentication state, complex seller dashboards, and product edits—Context API would cause unnecessary re-renders across the application tree. Redux Toolkit allowed me to create strict, predictable state slices. It decoupled my data fetching and global state from my UI components, making the codebase much cleaner and easier to debug."
Q: "How did you implement the 'Add to Cart' functionality to be robust?"
Answer: "I implemented a two-tier validation system. On the frontend, I use a custom
useCarthook to manage loading states and provide immediate feedback. On the backend, I usedexpress-validatorto ensure product IDs and quantities are valid. Crucially, I implemented price snapshotting within the Cart schema, so the price the user sees when they add an item is preserved even if the seller updates the product price later."
Q: "How did you manage the UI design without using a component library like Material-UI?"
Answer: "I wanted a unique, premium 'editorial' streetwear aesthetic that didn't look like a standard dashboard template. I chose Tailwind CSS v4 because its utility-first approach allowed me to build a highly custom design system directly in my components. It gave me granular control over layout, typography, and dark-theme colors, resulting in a bespoke interface while keeping the final CSS bundle size incredibly small."
Q: "How did you implement variant stock protection in your cart?"
Answer: "We store individual item variants (Size, Color, etc.) inside the product document. When a user adds an item or increases its quantity in the cart, the backend doesn't just check basic product existence; it retrieves the exact embedded variant ID from MongoDB, reads its specific stock count, and compares it against the user's current cart quantity. If they exceed the stock, we reject it at the controller level with an HTTP 400. This ensures we never sell products we don't physically have in stock."
Q: "What are atomic updates and how did you use them in the Cart?"
Answer: "In a high-traffic app, if two processes try to update a document at the same time, they can overwrite each other's changes. To avoid this, I used atomic operations on MongoDB instead of pulling the cart array into Javascript, editing it, and sending it back. By using MongoDB's
$incoperator for incrementing/decrementing quantities and$pullfor removing items directly inside a singlefindOneAndUpdatequery, I bypassed database race conditions completely, ensuring bulletproof data concurrency."
Q: "Explain how you solved the Cart Page data requirements. How did you join Cart, User, and Product details?"
Answer: "To display the cart page, we need information scattered across User, Cart, and Product catalogs. I designed a multi-stage MongoDB Aggregation Pipeline rather than doing standard population or running multiple queries. I use
$matchto target the user's cart,$unwindto split individual items,$lookupto join with the products collection, and$unwindto flatten variants. We then run a dynamic variant matching comparison, calculate line subtotals via$addFields, and finally$groupitems back together while summing up the subtotal into a grandtotalPricesum. This runs entirely in the database engine in a single database trip, maximizing performance."
Q: "How did you solve the Google OAuth redirect issue when debugging on physical mobile phones?"
Answer: "In a local dev environment, our mobile devices connect via local network IP addresses, but Google OAuth callbacks redirect back to the server, which hardcoded a redirect target to
localhost:5173. Because Vite doesn't run on the phone, the phone would crash. To solve this, I designed a dynamic OAuth pipeline: the backend captures the original host origin from the HTTPRefererheader at start, stores it inside Google's OAuthstateparameter, and on successful callback, extracts this dynamic host IP to redirect the user back to their starting device IP. This completely solved the localhost connection issue on mobile!"
Q: "How did you build the connection check diagnostics in your custom 404 page?"
Answer: "I designed the catch-all
*route to render a custom status page that checks the system's heartbeat. If the user encounters a route error or backend down error, the component pings the backend/apiroot. If it detects a 200 OK response, it alerts the user that the system connection is restored and triggers a React Router redirect back to the home page automatically, while showing full terminal diagnostic readouts to the user if the server remains offline."
Q: "Why did you choose Razorpay, and how did you ensure payment security?"
Answer: "I integrated Razorpay because it provides a robust, developer-friendly API and handles the heavy lifting of PCI-DSS compliance. To ensure security, I implemented a strict two-step verification process. The frontend handles the UI and securely collects payment info directly to Razorpay's servers. However, we never trust the frontend's 'success' message alone. Instead, the backend cryptographically verifies the
razorpay_signatureusing HMAC SHA256 and our private secret key. Only if this backend signature verification passes do we actually clear the cart and fulfill the order, preventing any client-side tampering or spoofing."
server.js- The main entry point. Sets up Express, connects to MongoDB, and registers routes./models- Mongoose schemas. e.g.,User.js,Product.js,Cart.js./controllers- The business logic. (e.g.,cart.controller.jshandles adding/removing items)./routes- Maps URL endpoints to specific controllers./middleware- Functions that run before the controller (e.g.,authMiddlewarefor security).
src/main.jsx- The React entry point. Wraps the app in Redux Providers and React Router.src/features/- Feature-based architecture:features/auth/(Authentication state & UI).features/products/(Catalog management).features/cart/(Shopping cart logic, Redux slice, and UI).
src/store/- The central Redux configuration.
- Open two terminal windows.
- Backend Setup:
cd backend npm install npm run dev - Frontend Setup:
cd frontend npm install npm run dev - Access the app at
http://localhost:5173.
