Build a product information management app with Appwrite Cloud and NextJS

Odewole Babatunde Samson - Jul 6 '23 - - Dev Community

Product information management (PIM) apps are that help businesses manage their product information. This information can include product descriptions, images, specifications, and pricing. Also, PIM apps can help businesses improve their product listings by providing accurate and up-to-date information. This can lead to increased sales and improved customer satisfaction.

This post discusses building a PIM system that allows us to add, update, and delete our product information in a Next.js application and an open-source design system, Appwrite Pink Design, to style the application. A custom backend server is not required.

GitHub

The project's GitHub repository can be found here.

Prerequisites

To follow along with this tutorial, a working knowledge of the following is required:

  • React, Next.js, and CSS

You’ll also need an Appwrite Cloud account. Request access to Appwrite Cloud here.

Setting up the project

To begin, let’s create a Next.js starter project by navigating to the desired directory and running the command below in our terminal.

npx create-next-app@latest product-info
Enter fullscreen mode Exit fullscreen mode

This will guide us through several prompts to set up the project, including selecting a package manager, a UI framework, and additional features.

nextjs-command-prompt

Once we’ve set up the project, let’s navigate to the project directory and start the development server using the following commands:

cd product-info
npm run dev
Enter fullscreen mode Exit fullscreen mode

Installing dependencies

Installing Pink Design
Pink Design is an open-source system from Appwrite used to build consistent and reusable user interfaces. It enhances collaboration, development experience, and accessibility.

To install Pink Design, let’s open the terminal in the project directory and run the following command.

npm install @appwrite.io/pink
Enter fullscreen mode Exit fullscreen mode

To use Pink Design in our project, we import it into our project's files like so:

import '@appwrite.io/pink';
import '@appwrite.io/pink-icons';
Enter fullscreen mode Exit fullscreen mode

Installing Appwrite
Appwrite is a development platform that provides a powerful API and management console for building backend servers for web and mobile applications. To install it, run the command below:

npm install appwrite
Enter fullscreen mode Exit fullscreen mode

Creating an Appwrite Cloud project

To get started, we need to log into our Appwrite cloud, click the Create project button, input product-info as the name, and then click Create.

product-info project

Create a database, collection, and add attributes
With our project created, we can set up our application database. First, navigate to the Database tab, click the Create database button, input product-info-management as the name, and then click Create.

product-info-management database

Secondly, we need to create a collection for storing our products. To do this, click the Create collection button, input productInfo_collection as the name, and then click Create.

product-info collection

Thirdly, we need to create attributes to represent our database fields. To do this, we need to navigate to the Attributes tab and create attributes for each of the values shown below:

Attribute key Attribute type Size Required
productName String 250 YES
productDesc String 500 YES
productImage String 5000 YES
productPrice Integer min 1 - max 100000 YES

product-info attribute

Lastly, we need to update our database permission to manage them accordingly. Navigate to the Settings tab, scroll to the Update Permissions section, select Any, mark accordingly, and then click Update.

permission update

Integrating Appwrite Cloud into the Next.js project

To integrate Appwrite into the UI, create a utils/web-init.js file. The file should look like this.

import { Client, Account } from "appwrite";
export const client = new Client();
export const account = new Account(client);
client
  .setEndpoint("https://cloud.appwrite.io/v1")
  .setProject("643fee63b2b5b506495c");
Enter fullscreen mode Exit fullscreen mode

The code above:

  • Imports the module Client and Account from Appwrite
  • Instantiates the Client and Account objects
  • Uses the client object to set the endpoint and project

Building the user interface

The user interface for the product information management app will showcase all the products created using an input field and list all of them with an edit and a delete icon.

To get started, first, we need to navigate to the src directory and create a components folder, and in this folder, create a AddProduct.js file and add the snippet below:

In the gist above, the code does the following:

  • Imports required packages and Appwrite Pink Design
  • Implements state variables to store the product’s name, descriptions, image, price, and size
  • Sets a state variable for the Modal popup

Next, we import the src/components/AddProduct.js into the src/app/page.js, like so:

import AddProduct from "@/components/AddProduct";

export default function Home() {
  return (
    <main className="u-main-center">
      <div>
        <h1 className="u-text-center heading-level-4">
          Product Information Management
        </h1>
      </div>
      <AddProduct/>
    </main>
  );
}
Enter fullscreen mode Exit fullscreen mode

At this point, our application should look like the following:

new-product-information-input

Note: We can use any image link for the product information system images. In this tutorial, however, we use images from Cloudinary as it is easier to apply transformations and optimize delivery.
To learn how to upload images to Cloudinary, check out the Cloudinary documentation.

Creating database documents
Next, we need to add new documents to the database collection. In the src/components/AddProduct.js file, write a createProduct() function to create the document.

const createProduct = async (e) => {
    e.preventDefault();
    await databases.createDocument(
      "[DATABASE_ID]",
      "[COLECTION_ID]",
      ID.unique(),
      {
        productName: productName,
        productDesc: productDesc,
        productImage: productImage,
        productPrice: productPrice,
        productSize: productSize
      }
    )
      .then((response) => {
        console.log(response);
        alert("product saved successfully")
      }, function (error) {
        console.log(error);
        alert("product not saved")
      });
}
Enter fullscreen mode Exit fullscreen mode

In the code block above, the createProduct() function does the following:

  • Creates a new document using Appwrite's createDocument() function while passing the collection ID and attribute values as parameters

Make sure to add a click event to the Save button like so:

<div className="u-flex u-main-end u-gap-16">
      <button className="button" type="submit" onClick={createProduct}>
          <span className="text">Save</span>
      </button>
</div>
Enter fullscreen mode Exit fullscreen mode

Fill out the form, and go to the Documents section of our database to see our saved documents.

product-info documents

Displaying our product information

Our page displays the product information we entered in our form. With this logic, we want our createProduct() function to create the documents to display our product.

Next, we need to navigate to the src directory and create a components folder, and in this folder, we create a ListProduct.js file and add the snippet below:

<div className="u-flex u-main-end u-gap-16">
      <button className="button" type="submit" onClick={createProduct}>
          <span className="text">Save</span>
      </button>
</div>
Enter fullscreen mode Exit fullscreen mode

The getProducts() function uses the Appwrite listDocuments API that receives a collection ID parameter. The getProducts() function finds the collection with that ID.

Deleting products from our database
Next, in the src/components/ListProduct.js file, create a deleteProduct() function to handle deleting products that we no longer need in our collection or database.

const deleteProduct = async (document_id) => {
    console.log(document_id)
    try {
      await databases.deleteDocument(
        "[DATABASE_ID]",
        "[COLECTION_ID]",
        document_id
      );
      alert("Item has been deleted successfully");
      await getProduct();
    } catch (error) {
      console.log("Error deleting product:", error.message);
      alert("Item was not deleted");
    }
  };
Enter fullscreen mode Exit fullscreen mode

The deleteProduct() function does the following:

  • Finds a document using its DATABASE_ID, COLECTION_ID, and DOCUMENT_ID
  • Deletes that document using the Appwrite deleteDocument() function
  • Alerts us if the item was successfully deleted
  • Runs the getProduct() function to display our updated product list

Creating the product information listing interface

Next, we display the products on our product information page. Paste this code into the src/components/ListProduct.js file to do so.

<div className="container">
      <table className="table is-selected-columns-mobile">
        <thead className="table-thead">
          <tr className="table-row">
            <th className="table-thead-col" style={{ "--p-col-width": 100 }}>
              <span className="eyebrow-heading-3">Product Name</span>
            </th>
            <th
              className="table-thead-col is-only-desktop"
              style={{ "--p-col-width": 100 }}
            >
              <span className="eyebrow-heading-3">Image</span>
            </th>
            <th
              className="table-thead-col is-only-desktop"
              style={{ "--p-col-width": 200 }}
            >
              <span className="eyebrow-heading-3">Description</span>
            </th>
            <th
              className="table-thead-col is-only-desktop"
              style={{ "--p-col-width": 100 }}
            >
              <span className="eyebrow-heading-3">Price</span>
            </th>
            <th
              className="table-thead-col is-only-desktop"
              style={{ "--p-col-width": 120 }}
            >
              <span className="eyebrow-heading-3">Size</span>
            </th>
            <th
              className="table-thead-col"
              style={{ "--p-col-width": 40 }}
            ></th>
          </tr>
        </thead>
        <tbody className="table-tbody">
          {products.map((product) => (
            <tr key={product.$id} className="table-row">
              <td className="table-col" data-title="Name">
                <div className="u-inline-flex u-cross-center u-gap-12">
                  <span className="text u-break-word u-line-height-1-5">
                    {product.productName}
                  </span>
                </div>
              </td>
              <td className="table-col is-only-desktop" data-title="Type">
                <div className="text">
                  <span className="image">
                    <img
                      className="avatar"
                      width="32"
                      height="32"
                      src={product.productImage}
                      alt=""
                    />
                  </span>
                </div>
              </td>
              <td className="table-col is-only-desktop" data-title="Type">
                <div className="text">
                  <span className="text">{product.productDesc}</span>
                </div>
              </td>
              <td className="table-col is-only-desktop" data-title="Size">
                <span className="tag">{product.productPrice}</span>
              </td>
              <td className="table-col is-only-desktop" data-title="Created">
                <time className="text">{product.productSize}</time>
              </td>
              <td className="table-col u-overflow-visible">
                <button
                  className="button is-text is-only-icon"
                  type="button"
                  aria-label="more options"
                  onClick={() => deleteProduct(product.$id)}
                >
                  <span className="icon-trash" aria-hidden="true"></span>
                </button>
              </td>
            </tr>
          ))}
        </tbody>
      </table>
</div>
Enter fullscreen mode Exit fullscreen mode

In the code block above, we:

  • Loop through the products to render each product
  • Destructure and pass in our productName, productDesc, productImage, productPrice, and productSize
  • Pass the deleteProduct() function we created to the onClick() event listener of our button

Updating our product information
Next, in the src/components/ListProduct.js file, we create a updateProduct() function to handle updating and correcting our created products in our collection or database.

In the gist above, the following happened:

  • The variable showModal populates the modal with a click on the pencil icon.
  • The editMode was initialized with an object containing a single property index, initially set to null
  • The editProduct function initiates the editing mode for a specific product. It takes a productId as its parameter. Inside the function, it searches for the product with the matching productId in the products array
  • The updateProduct() function locates the product with the provided productId in the products array, creates a copy with the updated values, and replaces the old product with the updated one
  • Passed the updateProduct() function we created to the onClick() event listener of the Update button

Fill out the form to see what the product information looks like.

Conclusion

This article discussed creating a product information management system using the Appwrite Cloud’s Database feature to create, retrieve, update, and delete data on our database. It can implement authentication features to make it more secure. This PIM can serve as the basis for a full-fledged inventory creation system for a store.

Resources

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .