
E-Commerce Project
This is a demo e-commerce store built with the MERN stack, featuring product listings, a shopping cart, a basic checkout flow, user accounts, and an admin panel. The goal was to demonstrate full-stack skills, scalability, and UI/UX design.
👉 View Live ProjectYou can view the github repo here.
🔧 Features Built
Product Listing
Browse products with images, descriptions, and prices.

Shopping Cart
Add products to the cart and view total price.

Checkout Flow
Proceed to checkout with user authentication.

User Accounts
Create and manage user accounts.

Admin Panel
Manage products, orders, and users.

📚 Lessons Learned
This project helped reinforce API design, authentication flows, and working with modular state management (Zustand). I also learned how to refine UI decisions based on user expectations.
🚀 Tech Stack
- Frontend: React (Vite) + TypeScript + Zustand
- Backend: Node.js + Express + MongoDB
- Styling: TailwindCSS
- Hosting: Vercel (Frontend), Railway (Backend)
📐 Design Patterns
One of the things that I really wanted to do well with this project was to make sure that I was using the right design patterns and structure. I refactored the structure multiple times during development as features were added and changed. These changes caused me to take new factors into consideration, such as how to best handle seperation of concerns, and how to best handle the state of the application. I ended up using Zustand for state management, and I found that it worked really well with the structure I had in place. I also used a lot of custom hooks to handle the logic of the application, which helped to keep the components clean and easy to read.
📦 File Structure
src
|── api
│ ├── admin
│ ├── anon
│ ├── cart
│ ├── order
│ ├── etc
├── components
│ ├── breadcrumb
│ ├── card
│ ├── cartIcon
│ ├── confirmDialog
│ ├── etc
├── hooks
│ ├── useAddToCart
│ ├── useRequest
├── pages
│ ├── 404
│ ├── adminDashboard
│ ├── adminLogin
│ ├── cart
│ ├── etc
├── services
│ ├── adminService
│ ├── anonService
│ ├── appService
│ ├── etc
├── store
│ ├── adminStore
│ ├── cartStore
│ ├── cookieStore
│ ├── etc
- The user interacts with the UI, loading specific pages using react-router.
- The page uses hooks to fetch data needed for the page, either using a service or the api directly.
- The service handles fetches data via the appropriate api and any business logic needed for the page, such as formatting the data, handling errors, and caching the data in the store.
- The api formats the request sent to the backend service and returns the response.
- The service will either place the data in a store or return it directly to the page depending on the buisiness logic.
- The page will render the data using the appropriate components.
Each part of the application is responsible for a specific task, and it can be tested and debugged independently.
Pages
The pages are responsible for rendering the UI and handling the user interactions.
Example: Orders Page
import { useNavigate } from "react-router-dom"; import { useEffect } from "react"; import useOrderStore from "../store/orderStore"; import { fetchOrders } from "../services/orderService"; import Card from "../components/card"; import { useRequest } from "../hooks/useRequest"; import LoadingSpinner from "../components/loadingSpinner"; const Orders = () => { const { orders } = useOrderStore(); const navigate = useNavigate(); const { error, loading, execute } = useRequest<void>(); useEffect(() => { execute(fetchOrders); }, [execute]); if (loading) { return ( <div className="flex justify-center mt-6"> <LoadingSpinner /> </div> ); } if (error) { return ( <div className="p-4 text-danger"> <p>{error}</p> </div> ); } return ( <div> <div className="header"> <h2>My Orders</h2> </div> <div className=" p-6 max-w-4xl mx-auto"> {orders.length === 0 ? ( <p className="text-center">No orders found.</p> ) : ( <div className="space-y-4"> {orders.map((order) => ( <Card className="hover:scale-110 hover:shadow-2xl" key={order._id} title={`Order #${order._id}`} onClick={() => navigate(`/orders/${order._id}`)} footer={`Total: $${order.totalPrice.toFixed(2)}`} > <p className="mb-2"> Status: <span className="font-semibold">{order.status}</span> </p> </Card> ))} </div> )} </div> </div> ); }; export default Orders;
This page is responsible for rendering the orders page. It uses the useOrderStore hook to get the orders from the store, and it uses the useRequest hook to fetch the orders from the server. It then renders the orders in a list. If there are no orders, it shows a message saying that there are no orders found. If there is an error, it shows the error message. If it is loading, it shows a loading spinner.
Components
The components are responsible for rendering specific parts of the UI. They are reusable and can be used in different pages.
Example: ProductCard Component
import { useNavigate } from "react-router-dom"; import { Product } from "../types"; import { useAddToCart } from "../hooks/useAddToCart"; import Card from "./card"; import clsx from "clsx"; const ProductCard = ({ product, className }: { product: Product, className?: string }) => { const { added, handleAddToCart } = useAddToCart(); const navigate = useNavigate(); const footer = ( <button className='btn btn-primary' onClick={() => handleAddToCart(product, 1)} > {added ? "Added" : "Add to Cart"} </button> ) return ( <Card className={clsx("text-center", className)} title={product.name} footer={footer} onClick={() => navigate(`/products/${product._id}`)}> <h3>{product.price}</h3> <img src={product.imageUrl} alt={product.name} className="w-full h-48 object-contain" /> </Card> ); }; export default ProductCard;
This component is responsible for rendering the product card. It uses the useAddToCart hook to handle adding the product to the cart, and it uses the useNavigate hook to navigate to the product details page when the card is clicked. It also uses the Card component to render the card.
Services
The services are responsible for handling the business logic of the application. They call the API and handle the responses. If we are storing the data in a global store, that is done here as well.
Example: Order Service
import * as api from "../api/order"; import { logError } from "../utils/logging"; import { getOrders, setOrders } from "../utils/storage"; /** * Fetches all orders from the server and sets them in the store. */ export const fetchOrders = async () => { try { const response = await api.getOrders(); setOrders(response); } catch (error) { logError("fetchOrders", error); throw error; } };
This service function is responsible for fetching the orders from the server and setting them in the store. It uses the api module to call the API and handle the response. If there is an error, it logs the error and throws it.
Store
The store is responsible for storing the data in a global state. It uses Zustand to create the store and handle the state management.
Example: Cart Store
import { create } from "zustand"; import { Cart } from "../types"; interface CartState { cart: Cart; setCart: (cart: Cart) => void; } const useCartStore = create<CartState>((set) => ({ cart: { _id: "", user: "", items: [], total: 0 }, setCart: (cart) => set({ cart }), })); export default useCartStore;
This store is responsible for storing the cart data in a global state. It uses Zustand to create the store and handle the state management. It has a cart state and a setCart function to update the cart. The store is kept very simple on purpose.
Hooks
The hooks are responsible for handling the reusable logic in the pages and components.
Example: useRequest Hook
import { useState, useCallback } from "react"; export const useRequest = <T,>() => { const [loading, setLoading] = useState(false); const [error, setError] = useState<string | null>(null); const [data, setData] = useState<T | null>(null); const execute = useCallback(async (requestFn: () => Promise<T>) => { setLoading(true); setError(null); try { const result = await requestFn(); setData(result); } catch (err) { setError("Something went wrong. Please try again."); console.error(err); } finally { setLoading(false); } }, []); return { data, loading, error, execute }; };
This hook is responsible for handling the API requests and managing the loading and error states. It uses the useState and useCallback hooks to manage the state and handle the requests. It returns the data, loading, error, and execute function to call the API.
API
The API is responsible for handling the requests and responses from the server. It uses Axios to handle the requests and responses.
Example: Order API
import axios from "./axiosConfig"; import { Order } from "../types"; import { authHeader } from "../utils"; // Fetch all orders export const getOrders = async (): Promise<Order[]> => { const response = await axios.get("/orders", authHeader()); return response.data; }; // Place an order export const placeOrder = async (): Promise<Order> => { const response = await axios.post("/orders", null, authHeader()); return response.data; }; // Get order by ID export const getOrderById = async (orderId: string): Promise<Order> => { const response = await axios.get(`/orders/${orderId}`, authHeader()); return response.data; };
This API module is responsible for handling the requests and responses from the server. It uses Axios to handle the requests and responses. It has functions to fetch all orders, place an order, and get an order by ID. It uses the authHeader function to add the authorization header to the requests.
📡 API
The API is built using Express and MongoDB, and it follows RESTful principles. The API is designed to be modular and scalable, with a clear separation of concerns. The API is also designed to be easy to use and understand, with clear documentation and error handling.
The API is built using the following endpoints:
📸 Screenshots



