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
This will guide us through several prompts to set up the project, including selecting a package manager, a UI framework, and additional features.
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
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
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';
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
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.
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.
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.
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 |
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.
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");
The code above:
- Imports the module
Client
andAccount
from Appwrite - Instantiates the
Client
andAccount
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>
);
}
At this point, our application should look like the following:
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")
});
}
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>
Fill out the form, and go to the Documents section of our database to see our saved 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>
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");
}
};
The deleteProduct()
function does the following:
- Finds a document using its
DATABASE_ID
,COLECTION_ID
, andDOCUMENT_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>
In the code block above, we:
- Loop through the
products
to render each product - Destructure and pass in our
productName
,productDesc
,productImage
,productPrice
, andproductSize
- Pass the
deleteProduct()
function we created to theonClick()
event listener of ourbutton
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 propertyindex
, initially set tonull
- The
editProduct
function initiates the editing mode for a specific product. It takes aproductId
as its parameter. Inside the function, it searches for the product with the matchingproductId
in theproducts
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 theonClick()
event listener of the Updatebutton
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.