Create an Ecommerce Admin App with Xata and Cloudinary
Tags: nextjs, xata, cloudinary, ecommerce
Published: 2022-11-23 09:26:04

---
title:Create an Ecommerce Admin App with Xata and Cloudinary
published: true
description: In this article, we‘ll learn how to create a dummy ecommerce store CMS admin with Next.js, Cloudinary's Upload widget, and Xata.
tags: nextjs, xata, cloudinary, ecommerce
# cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8wv02hhqzbb97yqwsgs0.png
# Use a ratio of 100:42 for best results.
# published_at: 2022-11-23 09:17 +0000
---

Content is king, and behind every content is the CMS (Content Management System), which is the queen. CMSs revolutionalized how we store, produce, and share content. Today, there are a variety of content management solutions, whether traditional, headless or hybrid.

In this article, we‘ll learn how to create a dummy ecommerce store CMS admin with Next.js, Cloudinary's Upload widget, and Xata.


## Source Code

Access the source code for the application on [GitHub](https://github.com/nefejames/hackmamba-ecom-admin-app/?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog).


## Prerequisites

To follow along with this article, we need to have:

- Knowledge of React and Next.js
- A [Cloudinary account](https://cloudinary.com/console/?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog)
- A [Xata account](https://app.xata.io/signin/?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog)
- Understanding of Chakra UI is advantageous but not required


## What Is Xata?

[Xata](https://xata.io/?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog) is a serverless database built on top of PostgreSQL and Elasticsearch. It comes with useful integrations with popular frameworks and technologies like Next.js, Netlify, Nuxt.js, Vercel, Remix, and SvelteKit.


## What is Cloudinary?

[Cloudinary](https://cloudinary.com/?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog) is a cloud-based and end-to-end platform with tools and functionalities for storing, processing, editing, managing, delivering, and transforming media assets.


## What We Will Create

The screenshots below shows the application we will create. 
Users can add new products by filling in the name, price and image of the products. They can also delete products or update the products’ details.

![Products page](https://paper-attachments.dropboxusercontent.com/s_1C55655C95AAF502349049F0EF30F8117EE5A0F153AA17142C2B2E74BFD2F447_1668799732854_Capture.PNG)

![Product’s detail page](https://paper-attachments.dropboxusercontent.com/s_1C55655C95AAF502349049F0EF30F8117EE5A0F153AA17142C2B2E74BFD2F447_1668799860858_Capture2.PNG)



## Getting Started

Download, clone, or fork [this starter template](https://github.com/nefejames/hackmamba-ecom-admin-app-ui/?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog) that contains the code for the UI of the ecommerce admin.

```
    npx create-next-app xata-cloudinary-chat-app -e https://github.com/nefejames/hackmamba-ecom-admin-app-ui
    or
    yarn create next-app xata-cloudinary-chat-app -e https://github.com/nefejames/hackmamba-ecom-admin-app-ui
```
Next, navigate to the project directory, install the necessary dependencies, then run the application

The starter template consists of the following files:


- `components/AddProductForm.js`: responsible for adding a new product to the shop
- `components/ProductCard.js`: displays a product’s details
- `components/ProductModal.js`: the modal that holds the form
- `components/UpdateProductForm.js`: responsible for updating a product’s details
- `layout/index.js`: the layout for the application
- `utils`: will hold the Xata instance, and the logic for the product image upload and product deletion



## Setting up Xata Database

We start by creating a new database called **chat-app** on our Xata account.

![creating a new Xata database](https://paper-attachments.dropboxusercontent.com/s_767BD3F7E260EFDC7E9C209798C513C5E8831A1EBE6D22594749206A5E483F79_1668167312852_Screenshot_191.png)


Next, create a `Products` table that will contain the data of different products. Each record will have `name`, `price`, `img`, and `id` properties; the `id` is automatically generated by Xata.
We got the images from the media filesuploads in our Cloudinary account.

![create the Products table](https://paper-attachments.dropboxusercontent.com/s_1C55655C95AAF502349049F0EF30F8117EE5A0F153AA17142C2B2E74BFD2F447_1668801514673_Capture4.PNG)


The database schema is ready, so let’s set up the Xata instance next, and integrate it into the application. 


## Setting up Xata Instance

Run the command below to install the CLI globally.
```
    npm install @xata.io/cli -g
```
Next, run `xata auth login`, which will prompt you to Create a new API key in the browser or use an existing one; we’ll go with the first option.

![Xata auth login](https://paper-attachments.dropboxusercontent.com/s_767BD3F7E260EFDC7E9C209798C513C5E8831A1EBE6D22594749206A5E483F79_1668174894564_xata-auth-login.png)


Once in the browser, give the API a name and click the **Create API key** button.

![create a new Xata API key](https://paper-attachments.dropboxusercontent.com/s_767BD3F7E260EFDC7E9C209798C513C5E8831A1EBE6D22594749206A5E483F79_1668175038404_xata-create-new-api-key.png)


Back on the dashboard, click on the **Get Code Snippet** button at the top right corner and copy the second command. We will use it to initialize the project locally with the aid of the CLI.

Run the command in the terminal and choose from the configuration options. The screenshot below shows the configurations for this application.

![Xata CLI config](https://paper-attachments.dropboxusercontent.com/s_767BD3F7E260EFDC7E9C209798C513C5E8831A1EBE6D22594749206A5E483F79_1668175900013_xata-cli-config.png)


The CLI will automatically create a `xata.js` file that contains the `XataClient` instance we need; let’s move on to the coding aspects of the application.



## Setting up the API Routse

We need to set up these 3 API (Application Programming Interface) routes for the app:

- `api/add-product.js`: contains the logic for adding a product to the `Products` database
- `api/update-product.js`: contains the logic for updating the information of a product on the `Products` database
- `api/delete-prduct.js`: contains the logic for deleting a product on the `Products` database

The code for the `add-product.js` API route:
```JavaScript
    import { getXataClient } from "@utils/xata";
    const xata = getXataClient();
    
    const handler = async (req, res) => {
      const { name, price, imgUrl } = req.body;
      await xata.db.Products.create({
        name: name,
        price: price,
        img: imgUrl,
      });
      res.end();
    };
    
    export default handler;
```
It takes the `name`, `price`, and URL of the uploaded image and passes them as arguments to Xata’s `create` method to add a new product to the `Products` db.

The code for the `update-product.js` API route:
```JavaScript
    import { getXataClient } from "@utils/xata";
    const xata = getXataClient();
    
    const handler = async (req, res) => {
      const { id, name, price } = req.body;
      await xata.db.Products.update(id, {
        name: name,
        price: price,
      });
      res.end();
    };
    
    export default handler;
```
It takes the `name`, `price`, and `id` of a specific product and passes them as arguments to Xata’s `update` method to update the product’s information.

The code for the `delete-product.js` API route:
```JavaScript
    import { getXataClient } from "@utils/xata";
    const xata = getXataClient();
    
    const handler = async (req, res) => {
      const { id } = req.body;
      await xata.db.Products.delete(id);
      res.end();
    };
    
    export default handler;
```
It takes the `id` of a specific product and passes it as a parameter to Xata’s `delete` method to delete the product from the `Products` db.

Having set up the API routes, let’s proceed with other parts of the application.


## Fetching and Displaying the Products in the Database 

We’ll start by fetching and displaying the products that are currently in the database. Update the `pages/shop.js` file with the following code:
```JavaScript
    import { Box, Flex, Heading, VStack } from "@chakra-ui/react";
    import ProductCard from "@components/ProductCard";
    import { getXataClient } from "utils/xata";
    import ProductModal from "@components/ProductModal";
    import AddProductForm from "@components/AddProductForm";
    
    export default function Shop({ data }) {
      return (
        <Box>
          <Flex justify="space-between" align="">
            <Heading as="h1" mb={5}>
              Products
            </Heading>
            <ProductModal modalTitle="Add product" modalBtnTitle="Add Product">
              <AddProductForm />
            </ProductModal>
          </Flex>
          <VStack spacing="2em">
            {data.map(({ id, img, name, price }) => (
              <ProductCard id={id} img={img} name={name} price={price} key={id} />
            ))}
          </VStack>
        </Box>
      );
    }
    
    export async function getServerSideProps() {
      const xata = getXataClient();
      const data = await xata.db.Products.getAll();
      return { props: { data } };
    }
```
Here, we did the following:

- Imported the `getXataClient` utility and initialized a new instance
- Queried the `Products` table and fetched all records with `getServerSideProps`
- Passed the fetched data to the `ProductCard` component to render


## Adding a Product

Update the `components/AddProductForm.js` file with the code below to set up the product addition functionality.
```JavaScript
    import { useState } from "react";
    import { Button, Box, Image, Input, VStack, useToast } from "@chakra-ui/react";
    import ShowImageUploadWidget from "@utils/upload";
    
    export default function AddProductForm() {
      const [imgUrl, setImgUrl] = useState(null);
      const toast = useToast();
      
      const handleSubmit = async (e) => {
        e.preventDefault();
        const formData = new FormData(e.target);
        const data = Object.fromEntries(formData);
        const name = data.name;
        const price = data.price;
        fetch("/api/add-product", {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
          },
          body: JSON.stringify({ name, price, imgUrl }),
        });
        e.target.reset();
        toast();
      };
      return (
        <form onSubmit={handleSubmit}>
          //insert form fields here
          <Button
            onClick={() => ShowImageUploadWidget(setImgUrl)}>
            Upload Image
          </Button>
        </form>
      );
    }
```
Here, we did the following:

- Created a `handleSubmit` function that takes the form data and passes it to the `add-product` route, which will handle pushing the form data to the `Products` db
- Set up an `imgUrl` state that will contain the URL of the product’s image users upload to Cloudinary
- Imported a `ShowImageUploadWidget` function from the `utils` directory
- Passed `ShowImageUploadWidget` to the **Upload Image** button’s `onClick` event handler
- Used the `toast` method to display a message upon successful form submission
- Passed `handleSubmit` to the form’s `onSubmit` event handler


Here’s the code for the `ShowImageUploadWidget` function. Create a `utils/upload.js` file and update it with the code. The product image upload is handled by [Cloudinary’s Upload Widget](https://cloudinary.com/documentation/upload_widget/?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog).  
```JavaScript
    function ShowImageUploadWidget(setImgUrl) {
      window.cloudinary
        .createUploadWidget(
          {
            cloudName: "your-cloud-name",
            uploadPreset: "ml_default",
          },
          (error, result) => {
            if (!error && result && result.event === "success") {
              setImgUrl(result.info.thumbnail_url);
            }
            if (error) {
              console.log(error);
            }
          }
        )
        .open();
    }
    
    export default ShowImageUploadWidget;
```
Let’s breakdown the snippet above:

- `ShowImageUploadWidget` calls the `createUploadWidget` that exists in the `cloudinary` object
- `createUploadWidget` takes in our `cloudname` as part of its config
- `ShowImageUploadWidget` accepts the `setImgUrl` function as an argument. If an avatar is uploaded successfully, the `imgUrl` state will be updated with the URL of the avatar a user uploads

We have to load the widget’s script in order for it to work. Next.js provides a `[Script](https://nextjs.org/docs/basic-features/script)` [component](https://nextjs.org/docs/basic-features/script/?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog) that we can use to load third-party scripts in our application. 

Update the `_app.js` file with the following code:

```JavaScript
    import { ChakraProvider } from "@chakra-ui/react";
    import Wrapper from "@layout/index";
    import Script from "next/script";
    
    function MyApp({ Component, pageProps }) {
      return (
        <ChakraProvider>
          <Script
            src="https://upload-widget.cloudinary.com/global/all.js"
            type="text/javascript"
            strategy="beforeInteractive"
          />
          <Wrapper>
            <Component {...pageProps} />
          </Wrapper>
        </ChakraProvider>
      );
    }
    export default MyApp;
```
Here, we imported the `Script` component from Next.js and used it to load the widget’s script.


## Updating a Product

Update the `components/UpdateProductForm.js` file with the code below to set up the product update functionality.
```JavaScript
    import { useRouter } from "next/router";
    import { Button, Input, VStack, useToast } from "@chakra-ui/react";
    
    export default function UpdateProductForm({ product }) {
      const toast = useToast();
      const router = useRouter();
      
      const handleSubmit = async (e) => {
        e.preventDefault();
        const formData = new FormData(e.target);
        const data = Object.fromEntries(formData);
        const name = data.name;
        const price = data.price;
        fetch("/api/update-product", {
          method: "PUT",
          headers: {
            "Content-Type": "application/json",
          },
          body: JSON.stringify({ id: product.id, name, price }),
        });
        toast();
        router.push("/shop");
      };
      
      return (
        <form onSubmit={handleSubmit}>
          //insert form fields here
        </form>
      );
    }
```
Here, we did the following:

- Created a `handleSubmit` function that takes the form data and passes it to the `update-product`route, which will update the product’s data in the `Products` db
- Used the `toast` method to display a message upon successful form submission
- Redirected the user to the `/shop` route after form submission
- Passed `handleSubmit` to the form’s `onSubmit` event handler



## Deleting a Product

Update the `pages/product/[id].js` file with the code below to set up the product deletion functionality.
```JavaScript
    import Head from "next/head";
    import Image from "next/image";
    import { useRouter } from "next/router";
    import { Box, Button, Flex, Heading, HStack, Text } from "@chakra-ui/react";
    import { getXataClient } from "@utils/xata";
    import ProductModal from "@components/ProductModal";
    import UpdateProductForm from "@components/UpdateProductForm";
    
    const xata = getXataClient();
    
    export default function Product({ product }) {
      const router = useRouter();
      
      function deleteProduct(id) {
        fetch("/api/delete-product", {
          method: "DELETE",
          headers: {
            "Content-Type": "application/json",
          },
          body: JSON.stringify({ id }),
        });
        router.push("/shop");
      }
      return (
        <Box>
          //insert ui code here 
        </Box>
      );
    }
    
    export async function getStaticProps({ params }) {
      try {
        const data = await xata.db.Products.filter({
          id: params.id,
        }).getMany();
        return {
          props: { product: data[0] },
        };
      } catch (error) {
        return {
          props: {},
        };
      }
    }
    
    export async function getStaticPaths() {
      const products = await xata.db.Products.getAll();
      return {
        paths: products.map((product) => ({
          params: { id: product.id },
        })),
        fallback: true,
      };
    }
```
Here, we did the following:

- Used `getStaticPaths` and `getStaticProps` to dynamically render the paths for each product
- Created a `deleteProduct` function that takes in a product’s `id` and passes it to the `delete-product` route, which will delete the product from the db
- Redirected the user to the `/shop` route after form submission

With that, we have successfully set up the functionality for the application, and it is good to go.


## Conclusion

In this article, we learned how to create an ecommerce admin application with Xata and Cloudinary.


## Resources
- [Cloudinary](https://cloudinary.com/documentation/product_gallery)['s Upload Widget Documentation](https://cloudinary.com/documentation/upload_widget/?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog)
- [Next Script Documentation](https://nextjs.org/docs/basic-features/script/?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog)
- [Xata’s Documentation](https://xata.io/docs/overview/?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog)