Skip to content

Building an E-commerce Storefront Using Next.js, Cloudinary, and Xata

As more and more people favor the convenience of online shopping, business owners turn to e-commerce platform builders, like Shopify, to sell more products. E-retail sales have surpassed 5.7 trillion dollars globally, and this number is only likely to rise in the upcoming years.

In this article, you’ll earn how to integrate Xata and Cloudinary to Next.js to build an e-commerce storefront, the presentation layer of an online store that communicates with online customers. Next.js is a React framework used to create the user interface. Cloudinary is a robust media API used to manage media files, and Xata is a serverless database that stores users’ data.

  • Npm
  • Node
  • Git
  • Basic knowledge of Reactjs and Nextjs

The source code for this application is publicly accessible on GitHub. You can clone it locally to your computer. This article will concentrate on how we will be utilizing Xata and Cloudinary in the project.

Note:

Cloudinary is a powerful media API for websites and mobile apps alike. Developers and marketers can effectively create, manage, and deploy their digital experiences using any browser, device, or bandwidth, thanks to Cloudinary. The architecture of Cloudinary was designed from the ground up to withstand enormous loads and manage essentially infinite amounts of assets and consumption.

After you clone it, you must add a Xata API key, Cloudinary cloud name, and the upload preset in an .env file at its root if you want to execute it locally.

Note:

Upload presets can be defined as a collection of activities that would occur upon uploading a resource. These activities could include applying an add-on capability, performing a transformation, or altering a resource’s access control, among many other possibilities.

To clone the application, run the following command on the terminal:

git clone https://github.com/HarcourtHamsa/Frontier.gitCode language: PHP (php)

Create a .env file in the root directory and paste your Xata API key:

XATA_API_KEY=<your Api key>
CLOUD_NAME=<your cloudinary cloud name>
UPLOAD_PRESET=<your cloudinary upload preset>Code language: HTML, XML (xml)

Start the project:

npm run dev

Xata is a serverless database with robust search and analytics. It’a comparable to a serverless relational database, a search engine, and an analytics engine, all hidden behind a uniform API.

It’s advisable to use the SDK when adding Xata to the project. We can use the SDK in many JavaScript runtimes, including Node.js, Deno, Electron, etc.

To install Xata, run the command:

npm i -g @xata.io/cliCode language: CSS (css)

Next, we have to authenticate Xata with the command:

xata auth login

With the stated command, you can either paste in an existing API key or create a new one by opening a browser.

Also, you will need to configure the application with the command below:

xata init

The command above will start a brief survey that will assist us in setting up our project. Answer the questions, and use code generation.

Now that Xata is set up in the project, you can query data with the utmost security and efficiency.

To add an API key, you’ll go to your account settings, where you can add an API key. There you will be required to submit a name to generate a key. Because your key will only be shown to you once before becoming inaccessible for security reasons, ensure you copy it to a secure area. You can invalidate and create a new key if you misplace this one.

Visit the website to sign up for a free account, then set up your Cloudinary account. You can sign up using GitHub, Gmail, or your email address.

  1. After signing up, you’ll be required to choose a role. In this case, you should select Developer and click continue.
  2. Next, you’ll be asked to select what your project is most likely to be. Select the first option and click Done.
  3. Navigate to your Dashboard. The dashboard contains the account details, which is the most critical area of the project.
  4. By default, Cloudinary assigns a random cloud name, which you can edit to a name of your choice. To edit your cloud name, click Settings (the clog icon at the top-left corner) and scroll to the bottom of the page.
  5. Enter the cloud name of your choice and click Save.
  6. Next, you need to set your upload preset. Still on your Settings page, at the top of the page, click Upload and scroll down to the section below.
  7. Click Add upload preset.
  8. Input your desired preset name, set the Signing mode to Unsigned, and click Save.

Next, you’ll set up your Xata account.

Visit the Xata to register for free. You’ll see the Add a database card after signing up, where you’ll create your database.

  1. Click the card, input your database name and click Create. A new card with your database name will appear next to the Add a database card.
  2. Next, you’ll need to create a table. Select your new database.
  3. Click Add a table in the sidebar below and insert your table name. You can also populate your table by clicking the Add a record button and filling in the desired details.
  4. For this project, you will need three tables; Users, Stores, and Products.
  5. The Users table should have the following columns:
    • id. The user’s unique id auto-generated by Xata.
    • first_name. The user’s first name.
    • last_name. The user’s last name.
    • email. The user’s email.
    • password. The user’s password.
    • username. The user’s display name.
  6. The Products table should have the following columns:
    • id. Xata auto-generated id.
    • name. Name of product.
    • price. Price of the product.
    • description. Short description of the product.
    • image_url. Image URL gotten from Cloudinary.
    • store_id. The store id.
  7. Finally, the Store table should contain the following:
    • id. Xata auto-generated id
    • name. Store name.
    • owners_id. User id.

Congratulations! You’ve completed the basic setup for Cloudinary and Xata. Next, you’ll insert and query data with Nextjs to Cloudinary and Xata.

Go to /pages/api/auth/register.ts.

import { NextApiRequest, NextApiResponse } from "next";
import nc from "next-connect";
import { getXataClient } from "../../../utils/xata.codegen";


const xata = getXataClient();
const handler = nc().post(async (req: NextApiRequest, res: NextApiResponse) => {
  const record = await xata.db.Users.filter({
    email: req.body.email
  }).getMany();
  if (record.length !== 0) {
    res.status(403);
    res.end();
    return;
  } else {
    console.log(req.body)
    const user = await xata.db.Users.create({
      ...req.body,
    });
    return res.status(200).json({ data: user });
  }
});
export default handler;Code language: JavaScript (javascript)

The above code performs authentication for users when they try to sign up. First, the Users table is queried from the database and then filtered based on the email gotten from the client side. The result will be an array that’s passed to the record variable.

Then we check if the record contains any data using record.length. If true, we’ll parse a 403 error code and end the process. If false, we proceed to create a new user.

Go to /pages/api/auth/login.ts.

import { NextApiRequest, NextApiResponse } from "next";
import nc from "next-connect";
import Router from "next/router";
import { getXataClient } from "../../../utils/xata.codegen";

const xata = getXataClient();
const handler = nc().post(async (req: NextApiRequest, res: NextApiResponse) => {
  const record = await xata.db.Users.filter({
    email: req.body.email,
  }).getMany();

  if (!record) {
    return res.status(401).json({ message: "Invalid email/password" });
  } else if (record[0].password !== req.body.password) {
    return res.status(401).json({ message: "Invalid email/password" });
  } else {
    return res.status(200).json({ data: record });
  }
});
export default handler;Code language: JavaScript (javascript)

Like the signup page, the Users table is also queried and filtered for the login page. If no data is found, an error message is passed. If a record is found, we parse the data.

/pages/api/store/index.ts

import { NextApiRequest, NextApiResponse } from "next";
import nc from "next-connect";
import { getXataClient } from "../../../utils/xata.codegen";
import { decode } from "jsonwebtoken";
const xata = getXataClient();
const handler = nc().post(async (req: NextApiRequest, res: NextApiResponse) => {
  const { name }: { name: string } = req.body;
  const jwt = req.cookies.frontier__jwt;
  const decodedData = decode(jwt);
  const accessToken = decodedData["0"].id;

  const record = await xata.db.Store.create({
    owners_id: accessToken,
    name: name,
  });
  if (!accessToken && record) {
    res.end();
    return;
  }
  res.status(200).json({
    data: record,
  });
});
export default handler;Code language: JavaScript (javascript)

To create a store, the above code retrieves the user’s (owner) id encoded with JSON web token (JWT) and passes it along with the desired store name.

/pages/api/products/index.ts

import { NextApiRequest, NextApiResponse } from "next";
import nc from "next-connect";
import { getXataClient } from "../../../utils/xata.codegen";
const xata = getXataClient();
import { decode } from "jsonwebtoken";
const handler = nc().get(async (req: NextApiRequest, res: NextApiResponse) => {
  const jwt = req.cookies.frontier__jwt;
  const decodedData = decode(jwt);
  const accessToken = decodedData["0"].id;
  const record = await xata.db.Store.filter({
    owners_id: accessToken,
  }).getMany();
  if (!record) {
    res.end();
    return;
  }
  res.status(200).json({
    data: record,
  });
});
export default handler;Code language: JavaScript (javascript)

The code above filters the Store table by the user’s id and returns every store owned by the user.

/pages/api/store/index.ts

import { NextApiRequest, NextApiResponse } from "next";
import nc from "next-connect";
import { getXataClient } from "../../../utils/xata.codegen";
const xata = getXataClient();
const handler = nc().post(async (req: NextApiRequest, res: NextApiResponse) => {
  console.log(req.body);
  const product = xata.db.Products.create({ ...req.body });
  console.log(product);
  return res.status(200).json({ data: product });
});
export default handler;Code language: JavaScript (javascript)

To create a product, the above code passes the information sent from the client form.

/pages/api/stores/index.ts

import { NextApiRequest, NextApiResponse } from "next";
import nc from "next-connect";
import { getXataClient } from "../../../utils/xata.codegen";
const xata = getXataClient();
const handler = nc().post(async (req: NextApiRequest, res: NextApiResponse) => {
  const { storeID } = req.body;
  const record = await xata.db.Products.filter({
    store_id: storeID,
  }).getMany();
  if (!record) {
    res.end();
    return;
  }
  res.status(200).json({
    data: record,
  });
});
export default handler;Code language: JavaScript (javascript)

The code above filters the Product table based on the store’s id and returns every product owned by the store.

/pages/app/products/new/index.ts


const NewProduct = () => {
  const router = useRouter();
  const [file, setFile] = useState()
  const [image_url, setImage_url] = useState("")
  const [name, setName] = useState()
  const [price, setPrice] = useState()
  const [description, setDescription] = useState()

  const cloudinary_url = `https://api.cloudinary.com/v1_1/${process.env.CLOUD_NAME}/image/upload`;

  async function handleSubmit(e){
    e.preventDefault()
    const formData =  new FormData()
    formData.append("folder", "Frontier store")
    formData.append("file", file)
    formData.append("upload_preset", `${process.env.UPLOAD_PRESET}`)
    await fetch(cloudinary_url, {
      method: "POST",
      body: formData
    }) .then((response) => {
      return response.json();
    })
    .then((data) => {
      // console.log(data)
      setImage_url(data.url)
      setImage_url((image_url) => {
        product({name, price, image_url, description})
        .then(() => router.push('/app'))
        .catch((err) => console.log(err));
        return image_url
      })
    })
    console.log(image_url)
  }
  function handleFileChange(e){
    setFile(e.target.files[0])
  }
  function handleProductNameChange(e){
    setName(e.target.value)
  }
  function handleProductPriceChange(e){
    setPrice(e.target.value)
  }
  function handleProductDescriptionChange(e){
    setDescription(e.target.value)
  }
  return (
    <div>
      <Layout>
        <div className={styles.main}>
          <h3>Create New Product</h3>
          <form onSubmit={handleSubmit}>
            <InputField
              type="file"
              label="Product Image"
              onChange={handleFileChange}
              name="image"
              id="image"
              // value={String(file)}
            />
            <InputField
              type="text"
              label="Product Name"
              placeholder="Enter the product name"
              onChange={handleProductNameChange}
              name="product_name"
              id="product_name"
              value={name}
            />
            <InputField
              type="text"
              label="Product Price"
              placeholder="Enter the price for the product"
              onChange={handleProductPriceChange}
              name="product_price"
              id="product_price"
              value={price}
            />
            <textarea placeholder="Describe the product" onChange={handleProductDescriptionChange} />
            <Button label="Create product" theme="green" type="submit" />
          </form>
        </div>
      </Layout>
    </div>
  );
};
export default NewProduct;Code language: JavaScript (javascript)

The above code aids in passing the image file to Cloudinary. The handleFileChange() function sets the file state with the image file from the input field. After filling in other fields, the submit button calls the handleSubmit() function when clicked. The formData is used to construct a set of key/value pairs representing the required field for uploading media to Cloudinary.

Then a POST request is made, passing the cloudinary_url, along with the formData. The response is parsed, and the URL for the image is set to state image_url. Next, all the information from the form is passed to the server side using the product() function, where they are inserted into the Product table.

In this article, you learned how to set up and use Cloudinary to upload and store media files and Xata to query data from your Next.js project. You also learned how to configure Xata on your project using the CLI. Remember the project is available and accessible on GitHub.

You can check out the live application to get a feel for how the application works.

Akunne Pascal

Harcourt Hamsa

If you found this article helpful and want to discuss it in more detail, head over to Cloudinary Community forum and its associated Discord.

Note:

This article was written for Hackmamba. Hackmamba partnered with with Cloudinary, Xata, and Uniform for a series of workshops to create technical blog posts, building optimized Jamstack products.

Back to top

Featured Post