Transactions have been a universal concept as old as time. With improvements in the technologies and financial institutions governing buying and selling, we have evolved from the traditional markets of buying and selling to e-commerce websites.
What we will be building
This post will discuss building a product catalog that allows us to create, delete and display our products in a NextJS application. We do not require a custom backend server.
GitHub URL
https://github.com/Iheanacho-ai/appwrite-product-catalog
Prerequisites
To get the most out of this project, we require the following:
- A basic understanding of CSS, JavaScript, and React.js.
- Docker Desktop installed on the computer. Run the
docker -v
command to verify if we have docker desktop installed. If not, install it from here. - An Appwrite instance running on our computer. Check out the documentation to create a local Appwrite instance. We will use Appwrite’s powerful database service and experience to manage our catalog.
Setting up our Next.js app
Next.js is an open-source React framework that enables us to build server-side rendered static web applications.
To create our Next.js app, we navigate to our preferred directory and run the terminal command below:
npx create-next-app@latest
# or
yarn create next-app
After creating our app, we change the directory to the project and start a development server with:
cd <name of our project>
npm run dev
To see our app, we go to http://localhost:3000.
Installing Tailwind CSS
Tailwind CSS is a "utility-first" CSS framework that allows us to rapidly create user interfaces for web applications.
To install Tailwind CSS in our project, we run these terminal commands.
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
These commands create two files in the root directory of our project, tailwind.config.js
and postcss.config.js
.
In our tailwind.config.js
, we add the paths to all our template files with this code below.
module.exports = {
content: [
"./pages/**/*.{js,ts,jsx,tsx}",
"./components/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}
Next, we add the tailwind directives in our styles/global.css
file.
@tailwind base;
@tailwind components;
@tailwind utilities;
Installing Appwrite
Appwrite is an open-source, end-to-end, back-end server solution that allows developers to build applications faster.
To use Appwrite in our Next.js application, we install the Appwrite client-side SDK for web applications.
npm install appwrite
Creating a new Appwrite project
We go to localhost and create a new account to see our console.
On our console, there is a Create Project button. Click on it to start a new project.
Our project dashboard appears once we have created the project. At the top of the page, there is a settings bar. Click it to access the Project ID and API Endpoint.
We copy the Project ID and API Endpoint, which we need to initialize the Appwrite Web SDK.
In our index.js
file, we initialize a new Appwrite instance with the following.
import {Appwrite} from "appwrite";
import {useEffect, useState } from "react";
const Home = () => {
// Init our Web SDK
const sdk = new Appwrite();
sdk
.setEndpoint('http://localhost/v1') // our API Endpoint
.setProject(projectID) // our project ID
;
return(
<div>Hello World!</div>
)
}
export default Home;
Creating the collection and attributes
In the Appwrite web Console, on the left side of the dashboard, we click on Database.
We create a collection in our database tab by clicking on the Add Collection button. This action redirects us to a Permissions page.
At the Collection Level, we want to assign a Read Access and Write Access with a role:all value. You can modify this customize roles and access to your database.
On the right of the Permissions page, we copy the Collection ID, which we need to perform operations on collection’s documents.
Next, we go to our attributes tab to create the properties we want a document to have.
We create a string attribute of productName, an integer attribute of productPrice, and a string attribute of productImage. We use these base attributes and could add more in the future.
Creating the Product catalog.
In the index.js
file, we create our product catalog page, which will be divided into two sections. The first section will contain a form to collect product information and the second section to list the products in our database.
In this section of the article, we will work on the form. To create the form styled with Tailwind CSS, we add the following code snippet in the index.js
file.
https://gist.github.com/Iheanacho-ai/0e0d68b6f8d17cfeddccbfc4b25c5f72
Next, we augment the styles with the following in the global.css
file.
@tailwind base;
@tailwind components;
@tailwind utilities;
.product-container{
margin-left: 37%;
width: 30%;
}
.cursor{
cursor: pointer;
}
Here is what our form looks like.
Adding form interaction with the database
Appwrite has a safety policy that allows only signed users to read and write to the database. However, it allows us to create an anonymous session, which we will use in this project.
In our index.js
file, we create our anonymous user session using Appwrite's createAnonymousSession
method.
import {Appwrite} from "appwrite";
import {useEffect, useState } from "react";
const Home = () => {
// Init our Web SDK
const sdk = new Appwrite();
sdk
.setEndpoint('http://localhost/v1') // our API Endpoint
.setProject(projectID) // our project ID
;
//creating an anonymous Session
const createAnonymousSession = async() => {
try{
await sdk.account.createAnonymousSession();
}catch(err){
console.log(err)
}
}
useEffect(()=> {
createAnonymousSession()
}, [])
return(
<div>Hello World!</div>
)
}
export default Home;
Creating state variables to hold our form values
In the index.js
file, we create state variables that will hold form input values.
In addition to the form input state variables, we will create a productList
variable used later in this tutorial. This productList
variable will hold the products we get from our database.
const [productName, setProductName] = useState('');
const [productPrice, setProductPrice] = useState('');
const [productImage, setproductImage] = useState('');
const [productList, setProductList] = useState([]);
Next, in our index.js
file, we pass the state variables as our input field values.
https://gist.github.com/Iheanacho-ai/51f4f3ef58ab56ef22026639a0514258
In the code block above, we do the following:
- Pass the state variables as values to their respective input fields.
- Use the
onChange
event listener to update the state variables when users type in the input fields.
Creating Appwrite database documents
In our index.js
file, we create our handleProductCatalog
function to add new documents to the database collection.
//creates the documents and runs listProducts
const handleProductCatalog = async () => {
try{
let promise = await sdk.database.createDocument(collectionID, 'unique()', {
"productName" : productName,
"productPrice": productPrice,
"productImage": productImage
});
setProductName('');
setProductPrice('');
setproductImage('');
alert('your job item has been successfully saved')
}catch(error){
console.log(error)
}
}
In the code block above, our handleProductCatalog
function does the following:
- Create a new document using Appwrite’s
createDocument()
function, while passing the collection ID and attribute values as parameters. - Alert us when we have successfully saved the document, then clears the information in the local state variables.
Next, we pass our handleProductCatalog
to an onClick
event listener on our button
element.
<button type= "button" className="cursor inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" onClick={handleProductCatalog}>
Save
</button>
NOTE: We use a button with a type= button
to override the button’s default submit behavior.
Here's how our index.js
file should look.
https://gist.github.com/Iheanacho-ai/918a519813cf7fa14fa7b2f8753ac15c
For your product catalog images, you can use any image link. In this tutorial, however, we use images from Cloudinary as it is easier to apply transformations and optimize delivery.
To understand how to upload images to Cloudinary, check out the Cloudinary documentation.
Fill out the form, and go to the Documents section of our database to see our saved documents.
Creating our Product Listing Page
Our page displays the product information we entered in our form. With this logic, we want our handleProductCatalog
function to be responsible for creating the documents to display our product.
In our index.js
file, we create a listProducts
function that gets called when we mount our app and when our handleProductCatalog
function runs.
//lists our products
const listProducts = async () => {
try {
let response = await sdk.database.listDocuments(collectionID);
setProductList(response.documents)
} catch (error) {
console.log(error)
}
}
useEffect(()=> {
createAnonymousSession(),
// runs list product function when our app mounts
listProducts()
}, [])
const handleProductCatalog = async () => {
try{
let promise = await sdk.database.createDocument(collectionID, 'unique()', {
"productName" : productName,
"productPrice": productPrice,
"productImage": productImage
});
setProductName('');
setProductPrice('');
setproductImage('');
alert('your job item has been successfully saved')
// runs our listProducts function
listProducts()
}catch(error){
console.log(error)
}
}
The listProducts
function uses the Appwrite listDocuments
API that receives a collection ID parameter. ThelistProducts
function finds the collection with that ID and updates the productList
variable with the products from that collection.
Deleting products from our database
Next, in our index.js
file we create a handleDelete
function, to handle deletion of products we do not need anymore in our collection or database.
const handleDelete = async (documentid) => {
try {
await sdk.database.deleteDocument('624098515dbd1ae68ea2', documentid);
alert("item have been deleted successfully")
listProducts()
} catch (error) {
console.log(error)
}
}
The handleDelete
function does the following:
- Finds a document using its collection ID.
- Deletes that document using Appwrite
deleteDocument()
function. - Alerts us if the item was successfully deleted.
- Runs the
listProducts
function to display our updated product list.
Creating our product listing UI
Next, we display the products on our product listing page. We paste this code into our index.js
file to do this.
<div className="bg-white">
<div className="max-w-2xl mx-auto py-16 px-4 sm:py-24 sm:px-6 lg:max-w-7xl lg:px-8">
<h2 className="sr-only">Products</h2>
{
productList ? (
<div className="grid grid-cols-1 gap-y-10 sm:grid-cols-2 gap-x-6 lg:grid-cols-3 xl:grid-cols-4 xl:gap-x-8">
{
productList.map(({productName, productImage, productPrice, $id}) => (
<a href="#" className="group" id={$id}>
<div className="w-full aspect-w-1 aspect-h-1 bg-gray-200 rounded-lg overflow-hidden xl:aspect-w-7 xl:aspect-h-8">
<img src={productImage} alt="Tall slender porcelain bottle with natural clay textured body and cork stopper." className="w-full h-full object-center object-cover group-hover:opacity-75" />
</div>
<h3 className="mt-4 text-sm text-gray-700">{productName}</h3>
<p className="mt-1 text-lg font-medium text-gray-900">${productPrice}</p>
<button
type="button"
className="cursor inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
onClick={()=> handleDelete($id)}
>
Delete
</button>
</a>
))
}
</div>
) : null
}
</div>
</div>
In the code block above, we:
- Check if the
productList
variable is not null. - Loop through the
productList
variable to render each product. - Destructure and pass in our
productName
,productPrice
,productImage
and$id
variables. - Pass the
handleDelete
function we created to theonClick
event listener of ourbutton
.
Here is what our complete index.js
file looks like
https://gist.github.com/Iheanacho-ai/cbcb04211612c527a40c3642f064691d
Fill out the form to see what the product catalog looks like.
Conclusion
This article discussed creating a product catalog using the Appwrite Database feature to create, retrieve and delete data on our database. This product catalog can serve as the basis for a full fledged inventory creation system for a store. Modify the documents to include more product fields.
Resources
Here are some resources that might be helpful: