# Create a React Ecommerce Store with Medusa

## **Introduction**

This guide will explain how to set up [Medusa](https://github.com/medusajs/medusa) in a React application. Medusa is an open source headless commerce engine built for developers. Medusa's composable architecture allows for endless customization and provides a friendly developer experience. React is a popular JavaScript library that provides a declarative and component-based architecture for building user interfaces.

Throughout this tutorial, you will learn how to build a React ecommerce store with Medusa. You will learn how to set up the Medusa server and admin panel and build out some pages to make up the storefront.

The code for the project developed in this article can be found in this [GitHub repository](https://github.com/aholland-work/react-medusa-storefront).

![Demo of the completed project](https://cdn.hashnode.com/res/hashnode/image/upload/v1673409863541/7b56d209-05c1-41e9-ad80-734663918634.gif align="center")

## **Prerequisites**

To complete this tutorial, you will need:

* [Node.js](https://docs.medusajs.com/tutorial/set-up-your-development-environment/#nodejs) version 14.18+ or 16+ installed on your local machine.
    
* One of the following package managers: `npm`, `yarn`, or `pnpm` (This tutorial will specifically use `npm`.)
    

## **Set up the Medusa Server**

In order to set up the server, you first need to install Medusa's CLI with the following command:

```bash
npm install @medusajs/medusa-cli -g
```

Next, run the following command to install the Medusa server:

```bash
medusa new medusa-server --seed
```

The command above installs the necessary packages and adds the Medusa server to the `medusa-server` directory. The `--seed` command populates an SQLite database with sample data that can be referenced from the storefront and admin panel.

### **Test the Medusa Server**

Navigate to the `medusa-server` folder and start the server:

```bash
cd medusa-server
medusa develop
```

The command above launches the server on [`localhost:9000`](http://localhost:9000).

You now have a complete commerce engine running locally. You can verify that the server is running by going to [`http://localhost:9000/store/products`](http://localhost:9000/store/products) in your browser. You should see a JSON blob that has the following response:

![JSON response of sample products data](https://cdn.hashnode.com/res/hashnode/image/upload/v1673398752571/8e492109-6214-48b5-80e2-a8e53c96efaf.jpeg align="center")

  
If you want the ability to upload product images to your Medusa server, you will need to install and configure a file service plugin like [S3](https://docs.medusajs.com/add-plugins/s3) or [DigitalOcean Spaces](https://docs.medusajs.com/add-plugins/spaces).

## **Setup the Medusa Admin**

Medusa provides an admin dashboard with numerous functionalities that equip you to manage your store, including order and product management.

To set up the Medusa Admin, clone the [Admin GitHub repository](https://github.com/medusajs/admin):

```bash
git clone https://github.com/medusajs/admin medusa-admin 
```

Change into the cloned directory and then install the dependencies:

```bash
cd medusa-admin
npm install
```

### **Test the Medusa Admin**

Before testing the Medusa admin, ensure that the Medusa server is up and running.

Once the installation of dependencies is complete and the Medusa server is running, execute the following command to start the Medusa admin:

```bash
npm run start
```

You can now view the Medusa Admin in the browser at [`http://localhost:7000/`](http://localhost:7000/) where you will be prompted with the following login screen:

![Medusa Admin Login Screen](https://cdn.hashnode.com/res/hashnode/image/upload/v1673398977069/f5b5cabf-37db-4c31-9ccb-55c56740eb20.png align="center")

To log in, you’ll need to input the credentials of the demo user that was created when the server’s database was seeded. For the email field, enter [`admin@medusa-test.com`](mailto:admin@medusa-test.com), and for the password field, enter `supersecret`.

Once logged in, click on “Products” in the sidebar menu. Here, you will see a list of demo products and you can add a few products by clicking on the “New Product” button in the upper right-hand corner.

![Medusa Admin Products Page](https://cdn.hashnode.com/res/hashnode/image/upload/v1673399050872/26ab6a60-3da3-4da2-aef7-5c25d1daba72.png align="center")

To learn more about the features and functionalities of the Medusa Admin, check out the [user guide](https://docs.medusajs.com/user-guide/).

## **Setup the React App**

In this section, you will scaffold a React application with [Vite](https://vitejs.dev/guide/). Vite is a build tool that aims to provide a faster and leaner development experience for modern web projects and is an alternative to tools like [Create React App](https://create-react-app.dev/docs/getting-started).

Run one of the following commands to create a Vite app. The command will vary slightly depending on the npm version that you have installed:

```bash
# npm version 6.x
npm create vite@latest react-medusa-storefront --template react
​
# npm version 7+, extra double-dash is needed
npm create vite@latest react-medusa-storefront -- --template react
```

From here, change into the new directory and install the project dependencies:

```bash
cd react-medusa-storefront
npm install
```

Start the dev server locally:

```bash
npm run dev
```

Navigate to [`http://localhost:5173/`](http://localhost:5173/) in your browser and you should now see a screen similar to the one below.

![Vite default landing page](https://cdn.hashnode.com/res/hashnode/image/upload/v1673399167254/b452c50c-83a7-4e7c-8e94-acd6e717d727.jpeg align="center")

### **Install React Bootstrap**

To assist with styling and building the components for the storefront you will use [React Bootstrap](https://react-bootstrap.github.io/getting-started/introduction/). To install, run the following command:

```bash
npm install react-bootstrap bootstrap
```

In order to apply the styles needed for the React Bootstrap library, you need to add the following import to the top of `src/main.jsx`:

```javascript
import 'bootstrap/dist/css/bootstrap.min.css';
```

### **Update the CSS**

Importing the React Bootstrap stylesheet in `src/main.jsx` applies a myriad of base styles therefore the stylesheet can be trimmed down.

Open `src/index.css` and replace its content with the following CSS:

```css
:root {
  font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
  font-size: 16px;
  line-height: 24px;
  font-weight: 400;
  font-synthesis: none;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  -webkit-text-size-adjust: 100%;
}
​
a {
  font-weight: 500;
}
a:hover {
  cursor: pointer;
}
​
body {
  margin: 0;
  min-height: 100vh;
}
```

### **Install React Router**

In order to enable [client-side routing](https://reactrouter.com/en/main/start/overview#client-side-routing) in our app, we will need to install and configure [React Router](https://reactrouter.com/en/main) by running the following command:

```bash
npm install react-router-dom
```

Since `src/main.jsx` is our entry point, we need to import [`BrowserRouter`](https://reactrouter.com/en/main/router-components/browser-router) and wrap the `App` component within it. Doing so will enable client-side routing for our entire web app.

Open `src/main.jsx` and replace its contents with the following code:

```javascript
import React from 'react'
import ReactDOM from 'react-dom/client'
import { BrowserRouter } from "react-router-dom";
import App from './App'
import './index.css'
import 'bootstrap/dist/css/bootstrap.min.css';
​
ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </React.StrictMode>
)
```

With React Router installed and configured, we can begin to build out the other pages for the storefront.

## **Connect the React Storefront to the Medusa Server**

The [Medusa JS Client](https://www.npmjs.com/package/@medusajs/medusa-js) is an [SDK](https://www.ibm.com/cloud/blog/sdk-vs-api) that provides an easy way to access the Medusa API from a web application. This Medusa JS client is an alternative to interacting with the [REST APIs](https://docs.medusajs.com/api/store).

Run the following command to install the Medusa JS Client:

```bash
npm install @medusajs/medusa-js
```

Medusa uses CORS to only allow specific origins to access the server. By default, the Medusa server is configured to allow access to the storefront on port `8000`. Because the default port for Vite is `5713`, you need to [configure Vite](https://vitejs.dev/config/) to point to the desired port number.

To address this, add the [`server.port`](https://vitejs.dev/config/server-options.html#server-port) option to the `vite.config.js` file:

```javascript
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
​
// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
  server: {
    port: 8000,
  },
})
```

Restart the Vite dev server to reflect the changes made in the config file, and you can now open a new browser tab at [`http://localhost:8000/`](http://localhost:8000/).

### **Create Medusa JS Client Utility**

Create a new file at the root of the project called `.env` and add the following environment variable that points to the URL of the Medusa server:

```bash
VITE_MEDUSA_SERVER_URL="http://localhost:9000"
```

Vite requires all [env variables](https://vitejs.dev/guide/env-and-mode.html#env-files) that need to be exposed to the client source code to be prefixed with `VITE_` and exposes the environment variable to the client via `import.meta.env.VITE_MEDUSA_SERVER_URL`.

Create the file `src/utils/client.js` and add the following code to create a utility that will make accessing the instance of the Medusa JS Client reusable across the application:

```javascript
import Medusa from "@medusajs/medusa-js"
​
const medusaClient = new Medusa({ baseUrl: import.meta.env.VITE_MEDUSA_SERVER_URL })
​
export { medusaClient }
```

## **Create the Storefront Components**

This section will cover building out the components that will compose the home page of product listings. Here is a list of the components that we will be building in this section:

1. **NavHeader** - Displays the logo, links, and shopping cart info.
    
2. **ProductCard** - Displays metadata about the products.
    

To start, create a new `components` folder in the `/src` directory to house all the components we will build for the storefront.

### **NavHeader Component**

Create the file `src/components/NavHeader.jsx` and add the following code:

```javascript
import React from 'react'
import { Link } from 'react-router-dom'
import Container from 'react-bootstrap/Container';
import Navbar from 'react-bootstrap/Navbar';
import Nav from 'react-bootstrap/Nav';
import Badge from 'react-bootstrap/Badge';
​
export default function NavHeader() {
    const cartCount = localStorage.getItem('cartCount') ?? 0
​
    return (
        <Navbar bg="dark" variant="dark">
            <Container fluid>
                <Nav className="w-25 d-flex align-items-center justify-content-between">
                    <img
                        alt="Medusa logo"
                        src="https://raw.githubusercontent.com/aholland-work/react-medusa-storefront/main/src/assets/logo-dark.svg"
                        width="150"
                    />
​
                    <Navbar.Text className="text-light fw-bold">
                        <Link className="text-decoration-none" to="/"> 🛍️ Products</Link>
                    </Navbar.Text>
                </Nav>
                <Nav>
                    <Navbar.Text className="text-light fw-bold">
                        Cart
                        <Badge bg="success" className="ms-2">{cartCount}</Badge>
                        <span className="visually-hidden">{cartCount} items in the cart</span>
                    </Navbar.Text>
                </Nav>
            </Container>
        </Navbar>
    )
}
```

The `NavHeader` component contains a link back to the home page via the “Products” link and a total count of the number of items that have been added to the cart.

### **ProductCard Component**

In the `components` directory, create a new file called `ProductCard.jsx` and add the following code:

```javascript
import React from 'react'
import { Link } from 'react-router-dom'
import PropTypes from 'prop-types';
import Card from 'react-bootstrap/Card';
​
export default function ProductCard(props) {
    const formattedPrice = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(props.price / 100)
    
    return (
        <Card>
            <Card.Img variant="top" src={props.thumbnail} alt={props.title} />
            <Card.Body>
                <Card.Title>{props.title}</Card.Title>
                <Card.Text className='text-success fw-bold'>
                    {formattedPrice}
                </Card.Text>
                <Link to={`/products/${props.productId}`}>View Details</Link>
            </Card.Body>
        </Card>
    )
}
​
ProductCard.propTypes = {
    title: PropTypes.string.isRequired,
    thumbnail: PropTypes.string.isRequired,
    price: PropTypes.number.isRequired,
    productId: PropTypes.string.isRequired
};
```

This component uses [React Bootstrap’s Card component](https://react-bootstrap.github.io/components/cards/) to display metadata about each product.

## **Create the Home Page**

The home page is the root route that uses the [Medusa JS Client’s `list` method](https://docs.medusajs.com/references/js-client/classes/ProductsResource#list) to pull in a list of products and display information about them using the `ProductCard` component that was created earlier.

Create a new file in`src/routes/Home.jsx` and add the following code:

```javascript
import React, { useEffect, useState } from 'react'
import Col from 'react-bootstrap/Col';
import Row from 'react-bootstrap/Row';
import ProductCard from '../components/ProductCard'
import { medusaClient } from '../utils/client.js'
​
export default function Home() {
    const [products, setProducts] = useState([])
​
    useEffect(() => {
        const getProducts = async () => {
            const results = await medusaClient.products.list();
            setProducts(results.products)
        }
​
        getProducts()
    }, []);
​
    return (
        <>
            <header>
                <h1 className="my-4">All Products</h1>
            </header>
            <main>
                <Row xs={1} sm={2} md={3} lg={4} className="g-4">
                    {products.map((product) => (
                        <Col key={product.id}>
                            <ProductCard
                                title={product.title}
                                productId={product.id}
                                price={product.variants[0].prices[1].amount}
                                thumbnail={product.thumbnail} />
                        </Col>
                    ))}
                </Row>
            </main>
        </>
    )
}
```

### **Register the Home Page Route**

Next, leverage React Routers [`Routes`](https://reactrouter.com/en/main/components/routes) and [`Route`](https://reactrouter.com/en/main/route/route) components in order to register the pages needed for the react webshop.

Open up `src/App.jsx` and update it to include the following code:

```javascript
import { Routes, Route } from "react-router-dom";
import './App.css'
import NavHeader from './components/NavHeader'
import Home from './routes/Home'
​
function App() {
  return (
    <div className="App">
      <NavHeader />
      <Routes>
        <Route path="/" element={<Home />} />
      </Routes>
    </div>
  )
}
​
export default App
```

With this setup, the `NavHeader` component will be visible across all our routes and the list of all products can be viewed at the root route.

### **Test the Home Page**

Ensure that the Medusa server and the storefront are running and navigate to [`http://localhost:8000/`](http://localhost:8000/) to see a preview of the home page:

![List of all products on the home page](https://cdn.hashnode.com/res/hashnode/image/upload/v1673399826780/e57dee42-872f-4f03-afe2-f41f22031987.jpeg align="center")

## **Create the Product Page**

In the `routes` folder, create a new file called `Product.jsx` and add the following code:

```javascript
import React, { useEffect, useState } from 'react'
import { useParams } from 'react-router-dom'
import Container from 'react-bootstrap/Container';
import Row from 'react-bootstrap/Row';
import Col from 'react-bootstrap/Col';
import Button from 'react-bootstrap/Button';
import { medusaClient } from '../utils/client.js'
​
const getFormattedPrice = (amount) => {
    return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(amount / 100);
}
​
// TODO: Add functions to handle the add to cart functionality
​
export default function Product() {
   const { id } = useParams();
   const [product, setProduct] = useState({})
​
    useEffect(() => {
        const getIndividualProduct = async () => {
            const results = await medusaClient.products.retrieve(id);
            setProduct(results.product)
        }
​
        getIndividualProduct()
    }, []);
​
    return (
      <main className="mt-5">
          <Container>
              <Row>
                  <Col>
                      <img width="500px"
                          alt={product.title}
                          src={product.thumbnail} />
                  </Col>
                  <Col className="d-flex justify-content-center flex-column">
                      <h1>{product.title}</h1>
                      <p className="mb-4 text-success fw-bold">{getFormattedPrice(product.variants?.[0]?.prices?.[1]?.amount)}</p>
                      <p className="mb-5">{product.description}</p>
                      <Button variant="success" size="lg" onClick={() => { console.log("Add to cart") }}>Add to cart</Button>
                  </Col>
              </Row>
          </Container>
      </main>
    )
}
```

This code takes the ID in the URL’s query param and uses the [Medusa JS Client’s `retrieve` method](https://docs.medusajs.com/references/js-client/classes/ProductsResource#retrieve) to obtain information about an individual product.

### **Register the Product Page Route**

Re-open `src/App.jsx`, import the `Product` page component, and add a new React Router `Route` that will point to the individual product page:

```javascript
import { Routes, Route } from "react-router-dom";
import './App.css'
import NavHeader from './components/NavHeader'
import Home from './routes/Home'
import Product from './routes/Product'
​
function App() {
  return (
    <div className="App">
      <NavHeader />
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="products/:id" element={<Product />} />
      </Routes>
    </div>
  )
}
​
export default App
```

### **Test the Product Page**

To test this out, click on the “View Details” button for the “Medusa Sweatpants” and you will be directed to the product's details page:

![Product page for the “Medusa Sweatpants”](https://cdn.hashnode.com/res/hashnode/image/upload/v1673399911546/2a653a28-196d-46d4-8aed-b4aac4a94c84.jpeg align="center")

## **Create Add to Cart Functionality**

To wire up the logic for the add-to-cart functionality, you need to check if a cart was previously created or not.

For this example, if the `cartID` is present, then we call the [add the line item to the cart](https://docs.medusajs.com/guides/carts-in-medusa/#add-line-item-to-the-cart) and store the cart’s item count in local storage under the `cartCount` field. If the `cartID` is not present, then we [create a new cart](https://docs.medusajs.com/guides/carts-in-medusa/#create-a-cart) and store the newly generated ID in local storage. The `cartCount` is used to update the display count in the navigation header.

Re-open `Product.jsx` in the `routes` folder and update the file to include the following code:

```javascript
import React, { useEffect, useState } from 'react'
import { useParams } from 'react-router-dom'
import Container from 'react-bootstrap/Container';
import Row from 'react-bootstrap/Row';
import Col from 'react-bootstrap/Col';
import Button from 'react-bootstrap/Button';
import { medusaClient } from '../utils/client.js'
​
const getFormattedPrice = (amount) => {
    return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(amount / 100);
}
​
const addProduct = async (cartId, product) => {
    const { cart } = await medusaClient.carts.lineItems.create(cartId, {
        variant_id: product.variants[0].id, //For simplicities sake only adding the first variant
        quantity: 1
    })
    localStorage.setItem('cartCount', cart.items.length)
    window.location.reload()
}
​
export default function Product() {
    // Get the product ID param from the URL.
    const { id } = useParams();
    const [product, setProduct] = useState({})
    const [regionId, setRegionId] = useState("")
​
    useEffect(() => {
        const getIndividualProduct = async () => {
            const results = await medusaClient.products.retrieve(id);
            setProduct(results.product)
        }
​
        const getRegions = async () => {
            const results = await medusaClient.regions.list()
            setRegionId(results.regions[1].id)
        }
​
        getIndividualProduct()
        getRegions()
    }, []);
​
    const handleAddToCart = async () => {
        const cartId = localStorage.getItem('cartId');
​
        if (cartId) {
            //A cart was previously created so use the cartId found in localStorage
            addProduct(cartId, product)
        } else {
            //Create a cart if there isn't a pre-existing one
            const { cart } = await medusaClient.carts.create({ region_id: regionId })
            localStorage.setItem('cartId', cart.id);
​
            //Use the newly generated cart's ID
            addProduct(cart.id, product)
        }
    }
​
    return (
        <main className="mt-5">
            <Container>
                <Row>
                    <Col>
                        <img width="500px"
                            alt={product.title}
                            src={product.thumbnail} />
                    </Col>
                    <Col className="d-flex justify-content-center flex-column">
                        <h1>{product.title}</h1>
                        <p className="mb-4 text-success fw-bold">{getFormattedPrice(product.variants?.[0]?.prices?.[1]?.amount)}</p>
                        <p className="mb-5">{product.description}</p>
                        <Button variant="success" size="lg" onClick={handleAddToCart}>Add to Cart</Button>
                    </Col>
                </Row>
            </Container>
        </main>
    )
}
```

The code above adds the following functionality:

1. `addProduct` async function that uses the `medusaClient` utility to add a product to a cart.
    
2. `getRegions` function to grab a [list of all the regions](https://docs.medusajs.com/references/js-client/classes/RegionsResource#list) and set it to the US region found at the first index in the `regions` array. This ID then gets stored in the `regionId` state variable.
    
3. `handleAddToCart` function that is invoked when the “Add to Cart” button is clicked.
    

For simplicities sake, the code adds the first variant of a product. This means that a product can only be added to a cart once. In addition, a page reload is initiated in order to pick up the new cart count. This decision was intentional to keep the project simple but know that a real-world app could allow the ability to select different variants and add more advanced capabilities.

### **Test Add to Cart Functionality**

To test out the functionality, click on the “View Details” link for any item to navigate to the product details page. From there, click on the “Add to Cart” button which will initiate a page refresh and update the cart’s badge count in the navigation header.

![Cart count increment](https://cdn.hashnode.com/res/hashnode/image/upload/v1673399968727/d7aa0971-2267-4c5b-8eab-f21df1d56c1a.png align="center")

## **Conclusion**

Throughout this tutorial, you learned about the [Medusa Admin](https://docs.medusajs.com/admin/quickstart), [Medusa Server](https://docs.medusajs.com/quickstart/quick-start), and how you can use the [Medusa JS Client](https://docs.medusajs.com/js-client/overview/) to create a sample ecommerce store with React. A lot of material was covered, however, there is still so much more that you can generate with Medusa such as:

* Expanding upon the “add to cart” functionality and building a shopping cart screen that [retrieves a cart](https://docs.medusajs.com/guides/carts-in-medusa/#retrieve-a-cart) and displays a list of all the products that have been added to it.
    
* Payment integrations with [Stripe](https://docs.medusajs.com/add-plugins/stripe).
    
* [Implementing a Checkout Flow](https://docs.medusajs.com/advanced/storefront/how-to-implement-checkout-flow/)
    

Should you have any issues or questions related to Medusa, then feel free to reach out to the Medusa team via [Discord](https://discord.gg/F87eGuwkTp).
