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.
Prerequisites
To follow along with this article, we need to have:
- Knowledge of React and Next.js
- A Cloudinary account
- A Xata account
- Understanding of Chakra UI is advantageous but not required
What Is Xata?
Xata 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 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.
Getting Started
Download, clone, or fork this starter template 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.
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.
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.
Once in the browser, give the API a name and click the Create API key button.
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.
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 theProducts
database -
api/update-product.js
: contains the logic for updating the information of a product on theProducts
database -
api/delete-prduct.js
: contains the logic for deleting a product on theProducts
database
The code for the add-product.js
API route:
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:
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:
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:
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 withgetServerSideProps
- 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.
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 theadd-product
route, which will handle pushing the form data to theProducts
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 theutils
directory - Passed
ShowImageUploadWidget
to the Upload Image button’sonClick
event handler - Used the
toast
method to display a message upon successful form submission - Passed
handleSubmit
to the form’sonSubmit
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.
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 thecreateUploadWidget
that exists in thecloudinary
object -
createUploadWidget
takes in ourcloudname
as part of its config -
ShowImageUploadWidget
accepts thesetImgUrl
function as an argument. If an avatar is uploaded successfully, theimgUrl
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 that we can use to load third-party scripts in our application.
Update the _app.js
file with the following code:
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.
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 theupdate-product
route, which will update the product’s data in theProducts
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’sonSubmit
event handler
Deleting a Product
Update the pages/product/[id].js
file with the code below to set up the product deletion functionality.
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
andgetStaticProps
to dynamically render the paths for each product - Created a
deleteProduct
function that takes in a product’sid
and passes it to thedelete-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.
Top comments (0)