Step-by-Step Guide to Building an Amazon Clone with ReactJS

This article will walk through building a full-featured e-commerce website clone of Amazon using the React JavaScript library. Read along to learn how to set up the development environment, build out the frontend and backend, implement user authentication, and add an admin dashboard.

What is an Amazon clone?

An Amazon clone is a replica e-commerce website that mimics the core functionality and features of the popular online retail giant Amazon.

It allows developers to build a full-fledged online store from scratch by modeling it after an existing successful example. Amazon clones typically include storefronts for browsing products, individual product pages, shopping carts, user accounts, admin dashboards, and more.

Why build with ReactJS?

React is a perfect fit for building an Amazon clone script for several reasons:

  • Performance - React uses a virtual DOM for rendering UI rather than mutating the real DOM. This makes the app fast and efficient for an e-commerce site with lots of dynamic content.

  • Components - React's component-based architecture lends itself well to building reusable UI elements like product cards, forms, etc that can be composed to create complex pages.

  • Declarative Code - React uses declarative JavaScript code that is easier to read and reason about compared to imperative style. This is ideal for large codebases.

  • Popularity - React is one of the most widely used frontend frameworks currently. Developing the clone in a popular library ensures skills will remain relevant and codebase maintenance is simpler.

  • Testing - React components are fairly isolated and self-contained, making them easier to test than alternatives with tangled dependencies between components.

Step 1: Plan Your Amazon Clone Project

Before starting development, it's important to plan out the scope and requirements of the project. Here are some things we need to consider:

Features:

  • Storefront/homepage for browsing categories, searching products

  • Individual product pages

  • Shopping cart

  • Checkout flow

  • User accounts/login

  • Admin area

Timeline: We'll break development into 4 sprints over 2 months with weekly milestones.

Hosting: We'll deploy the app to Heroku for ease of deployment and scalability. We'll use MongoDB Atlas for the database.

Database: Our data schema will include collections for Products, Users, Orders etc.

With planning done, we're ready to start setting up our development environment.

Step 2: Set Up Development Environment

First, we'll install Node.js and create a new React app:

npm install -g create-react-app
create-react-app amazon-clone
cd amazon-clone

Next, we'll add Express and other packages:

npm install express mongoose cors dotenv react-router-dom

We initialize our Git repo and make initial commits before moving on to the backend.

Step 3: Build Backend with Express

To build the backend, we first create a folder called backend inside our React project.

We'll initialize a Node.js app and install dependencies:

mkdir backend 
cd backend
npm init
npm install express mongoose

In server.js we'll require Express and connect to MongoDB:

const express = require('express');
const mongoose = require('mongoose');

mongoose.connect(process.env.MONGODB_URL, {useNewUrlParser: true});

const app = express();

app.listen(5000, () => console.log('Backend server running'));

Next we'll create a Product model and route:

// product.model.js

const mongoose = require('mongoose');

const productSchema = new mongoose.Schema({
  // schema
});

module.exports = mongoose.model('Product', productSchema);
// routes/product.routes.js

const express = require('express');
const router = express.Router();
const Product = require('../models/product.model');

router.get('/products', async (req, res) => {
  const products = await Product.find();
  res.json(products);
});

module.exports = router;

Our basic backend is now ready to serve our frontend.

Step 4: Design Database Schema

Next we'll define our database schema in MongoDB. We'll need collections for:

Products

  • id, name, image, price, description, category etc.

Users

  • id, name, email, password

Orders

  • id, user_id, products, total, date

Categories

  • name, products

To seed sample data, we import the data and save to the db:

// seeder.js

import Product from './models/Product';

const products = [
  {
    name: 'Phone',
    //...
  },
  //...
]

const insertData = async () => {
  try {
    await Product.insertMany(products);  
  } catch (err) {
    console.log(err) 
  }
}

insertData();

Step 5: Build Homepage/Storefront

Now onto building the frontend! We'll start with the storefront pages.

In App.js we setup routing:

import { BrowserRouter, Routes, Route } from 'react-router-dom';

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<HomePage />} />
      </Routes>
    </BrowserRouter>
  )
}

In HomePage.js, we fetch products from the API:

// HomePage.js

import { useEffect, useState } from 'react';
import axios from 'axios';

function HomePage() {
  const [products, setProducts] = useState([]);

  useEffect(() => {
    const fetchProducts = async () => {
      const response = await axios.get('/api/products');
      setProducts(response.data);
    }
    fetchProducts();
  }, []);

  return ( 
    <div>
      {products.map(product => (
        <ProductCard product={product} key={product._id} />  
      ))}
    </div>
  )
}

We'll display Products in a reusable ProductCard component. This establishes the basic storefront UI.

Step 6: Build Individual Product Pages

To view a product, we create a ProductDetailsPage:

// ProductDetailsPage.js

const { useState, useEffect } = require('react');
const { useParams } = require('react-router-dom');

function ProductDetailsPage() {
  const [product, setProduct] = useState({});
  const { id } = useParams(); 

  useEffect(() => {
    const fetchProduct = async () => {
      const response = await axios.get(`/api/products/${id}`);
      setProduct(response.data);
    }
    fetchProduct();
  }, [id]);

  return (
    <div>
      <h1>{product.name}</h1>
      <img src={product.image} />
      <p>{product.description}</p>
      <button>Add to Cart</button>
    </div>
  )
}

Now users can view details of individual products.

Step 7: Implement Shopping Cart

For the cart, we'll store items in the browser's localStorage:

// useCart.js

const getCartFromStorage = () => {
  let cart;
  if (typeof window !== 'undefined') {
    cart = localStorage.getItem('cart') 
    ? JSON.parse(localStorage.getItem('cart')) 
    : [];
  }
  return cart;
}

const useCart = () => {
  const [cart, setCart] = useState(getCartFromStorage());

  const addToCart = (product) => {
    setCart([...cart, {...product, quantity: 1}]);
  }

  useEffect(() => {
    localStorage.setItem('cart', JSON.stringify(cart));
  }, [cart])

  return { cart, addToCart }
}

export default useCart;

Now any component can import useCart() to access the cart state. We'll call addToCart() on the product details page.

Step 8: Build Checkout Process

For checkout, we collect customer details over multiple pages:

// CheckoutPage1.js

function CheckoutPage1() {

  const [name, setName] = useState('');

  const handleSubmit = () => {
    // submit to next page
  }

  return (
    <form onSubmit={handleSubmit}>
      <input 
        value={name}
        onChange={e => setName(e.target.value)} 
      />
      <button type="submit">Next</button>
    </form>
  )
}
// CheckoutPage2.js 

// collect address

// submit to payment page
// CheckoutPage3.js

// payment form
// submit order

This splits up the complex checkout into simple, sequential steps.

Step 9: Add User Authentication

To save orders against user profiles, we will implement user authentication.

First, we create a User model:

// models/User.js

const mongoose = require('mongoose');

const userSchema = new mongoose.Schema({
  name: {
    type: String,
    required: true
  },
  email: {
    type: String, 
    required: true,
    unique: true
  },
  password: {
    type: String,
    required: true
  },
  orders: [{
    type: mongoose.Schema.Types.ObjectId,
    ref: 'Order' 
  }]
})

module.exports = mongoose.model('User', userSchema);

We'll sign users up and log them in via JSON web tokens (JWT):

// auth.js

const jwt = require('jsonwebtoken');

const register = async (req, res) => {
  // create user
  const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET);
  res.json({ token });
}

const login = async (req, res) => {
  // authenticate 
  const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET);
  res.json({ token });
}

On the frontend, we use the token for authorization:

// AuthContext.js

const login = async ( credentials ) => {
  const { token } = await fetch('/login', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(credentials)
  }).then(res => res.json());

  localStorage.setItem('token', token);
}

Finally, we check for an auth token on protected routes:

// ProtectedRoute.js

function ProtectedRoute({ children }) {
  const token = localStorage.getItem('token');

  if(!token) return <Navigate to="/login" />

  return children
}

This implements user authentication for the app.

Step 10: Add Admin Features

For admin features, we'll build a protected admin route and dashboard:

// App.js

<Routes>
  <Route path="/" element={<HomePage />} />

  <Route 
    path="/admin"
    element={
      <RequireAuth>
        <AdminDashboard />  
      </RequireAuth>
    } 
  />
</Routes>
// RequireAuth.js

function RequireAuth({ children }) {
  const { user } = useAuth();

  if(!user) {
    return <Navigate to="/login" />
  }

  return children;
}

The dashboard will display analytics and allow CRUD operations:

// AdminDashboard.js

return (
  <div>
    <h1>Admin Dashboard</h1>

    <AnalyticsSection />

    <Route path="products">
      <ProductsTable />  

      <NewProductForm />
    </Route>

    <Route path="orders">
      <OrdersTable />
    </Route>
  </div>
)

We can retrieve and update data by building API methods:

// products.api.js

export const getProducts = async () => {
  const res = await axios.get('/api/admin/products');
  return res.data;
}

export const deleteProduct = async (id) => {
  await axios.delete(`/api/admin/products/${id}`);
}

Conclusion

In this tutorial, we built a fully functional Amazon clone app from end-to-end with React, Express, and MongoDB. Key takeaways included:

  • Planning the scope and architecture of the project

  • Setting up a production-ready development environment

  • Building reusable UI components

  • Creating RESTful APIs with Express

  • Implementing user authentication

  • Storing app data in MongoDB

  • Securing admin-only routes and functionality

Some enhancements could include payment integrations, additional product filters, wishlists, and integration with email/SMS libraries. With further work, this clone application could become a full-fledged e-commerce site.

1
Subscribe to my newsletter

Read articles from prasad venkatachalam directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

prasad venkatachalam
prasad venkatachalam

With over 10 years in web and app development, I'm more interested in writing about clone solutions of popular brands