Build an e-commerce product catalog with Nuxt.js and GraphQL
Tags: graphql, javascript, programming, tutorial
Published: 2023-02-27 13:49:02

The widespread use of e-commerce platforms has contributed to substantial growth in online retail. With improvements in the technologies and financial institutions governing buying and selling, traditional buying and selling markets have evolved to e-commerce websites. This post discusses building a product catalog that allows us to create, delete, and display our products in a Nuxt.js application. We do not require a custom backend server.

## GitHub repository
https://github.com/Tundesamson26/nuxt-product-catalog

## Prerequisites

- Basic understanding of CSS, JavaScript, and Nuxt.js
- Docker Desktop installed on your computer (run the `docker -v` command to verify the installation); if you don’t yet have it, install it from [here](https://www.docker.com/products/docker-desktop/)
- An Appwrite instance running on your computer. Check out [this post](https://dev.to/hackmamba/create-a-local-appwrite-instance-in-3-steps-19n9?utm_source=hackmamba&utm_medium=hackmamba) to create a local Appwrite instance. It can also be installed using [digital ocean](https://docs.digitalocean.com/products/marketplace/catalog/appwrite/) or [gitpod](https://www.gitpod.io/guides/appwrite-and-gitpod)

## Setting up the project
We need to create a Nuxt.js starter project by navigating to the desired directory and running the command below in our terminal.

```js
npx create-nuxt-app product-catalog
```
This will guide us through several prompts to set up the project, including selecting a package manager, a UI framework, and additional features. Make sure to choose Axios as the HTTP client, as we'll use it to make API calls to Appwrite later.

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

```js
cd product-catalog
npm run dev
```

## Installing dependencies
**Installing TailwindCSS**
[TailwindCSS](https://tailwindcss.com/) is a utility-first CSS framework packed with classes to help us style our web page. To use it in the application, run the command below in your terminal.

```js
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
```
The command installs TailwindCSS and its dependencies and generates a `tailwind.config.js` file.

Next, we need to update the `tailwind.config.js` file with the snippet below:

```js
/** @type {import('tailwindcss').Config} */
module.exports = {
    content: [
        './components/**/*.{js,vue,ts}',
        './layouts/**/*.vue',
        './pages/**/*.vue',
        './**/*.vue',
        './plugins/**/*.{js,ts}',
    ],
    theme: {
        extend: {},
    },
    plugins: [],
};
```
We now need to add TailwindCSS directives to our application. The directives give our application access to TailwindCSS utility classes. To do this, create a `css/tailwind.css` file in the root directory and add the snippet below:

```js
@tailwind base;
@tailwind components;
@tailwind utilities;
```
Lastly, we need to add TailwindCSS as a dependency in the `nuxt.config.js`, as shown below:

```js
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
    postcss: {
        plugins: {
            tailwindcss: {},
            autoprefixer: {},
        },
    },
    css: ['~/css/tailwind.css'],
});
```
**Installing Appwrite**
[Appwrite](https://appwrite.io/?utm_source=hackmamba&utm_medium=hackmamba-blog) 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:

```js
npm install appwrite
```

## Setting up Appwrite

To get started, we need to log in to our Appwrite console, click the **Create project** button, input `product-catalog` as the name, and then click **Create**.

![appwrite-project](https://paper-attachments.dropboxusercontent.com/s_FF63B95DA969D8F944ED3218AE5C44BBEC895BC6F666C4C760FF11E8DA6661D2_1674244939514_image.png)


**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 `products-cat` as the name, and then click **Create**.

![appwrite-db](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nyd0pxzt51w65jaur215.png)

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

![appwrite-collection](https://paper-attachments.dropboxusercontent.com/s_FF63B95DA969D8F944ED3218AE5C44BBEC895BC6F666C4C760FF11E8DA6661D2_1674476883367_image.png)


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      |
| productPrice  | Integer        | `min` 1 - `max` 100000 | YES      |
| productImage  | String         | 5000                   | YES      |

![appwrite-attr](https://paper-attachments.dropboxusercontent.com/s_FF63B95DA969D8F944ED3218AE5C44BBEC895BC6F666C4C760FF11E8DA6661D2_1674477574786_image.png)


Lastly, we need to update our database permission to manage them accordingly. To do this, we need to navigate to the **Settings** tab, scroll to the **Update Permissions** section, select `Any`, mark accordingly, and then click **Update**.


![appwrite-permission](https://paper-attachments.dropboxusercontent.com/s_FF63B95DA969D8F944ED3218AE5C44BBEC895BC6F666C4C760FF11E8DA6661D2_1674477727743_image.png)

![appwrite-permission](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aesr8qvsdomenh8qszj8.png)


## Building the product catalog

In the `app.vue` file, we create our product catalog page, which will be divided into two sections. This first section will contain a form to collect product information.

Here, we’ll work on the form. To create the form styled with Tailwind CSS, add the following code snippet in the `app.vue` file.

{% embed https://gist.github.com/Tundesamson26/835bbbb5f5e06efd71d8f0823004f2f0 %}

Next, we augment the styles with the following in the `~assets/css/tailwind.css` file.

```js
@tailwind base;
@tailwind components;
@tailwind utilities;

.product-container{
  margin-left: 37%;
  width: 30%;
}
.cursor{
  cursor: pointer;
}
```
Here is what our form looks like at this point. 

![product-cat-app](https://paper-attachments.dropboxusercontent.com/s_FF63B95DA969D8F944ED3218AE5C44BBEC895BC6F666C4C760FF11E8DA6661D2_1674560427805_image.png)


In the components folder, create a file called `Products.vue`, and paste the code below into it:

{% embed https://gist.github.com/Tundesamson26/8f8ccbfd87fd113e52ef3540cd862e21 %}

This second section is a simple card that contains all the details of a product catalog: the product name, image, and price. There's also a `props` object for each of these attributes.

## Adding form interaction with the database

Appwrite has a safety policy that allows only signed-in 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 the `app.vue` file, let’s create our anonymous user session using Appwrite's `createAnonymousSession` method.

```js
<script>
import { Client, Account, Databases, Graphql} from "appwrite";

const client = new Client();
client
  .setEndpoint("http://localhost/v1/graphql") // The Appwrite Endpoint
  .setProject("63caf356633daedc90d6");

const account = new Account(client);
const database = new Databases(client);
const storage = new Storage(client);
const graphql = new Graphql(client);

account.createAnonymousSession().then(
  (response) => {
    console.log(response);
  },
  (error) => {
    console.log(error);
  }
);
export default {
  name: "IndexPage",

  mounted() { },
};
</script>
```
In this file, we create an Appwrite instance with an account, database, and GraphQL, which we'll use later in the app. Also, we set up an anonymous session.

In the mounted method, check whether the anonymous account is active and subscribe to the documents channel. Subscribing to a channel updates our app in real time once a change is made in the database.

```js
...
 mounted() {
    if (account.get !== null) {
      try {
        client.subscribe("documents", (response) => {
        });
      } catch (error) {
        console.log(error, "error");
      }
    }
  }
...
```

## Using Appwrite's GraphQL

[Appwrite's GraphQL API](https://appwrite.io/docs/graphql?utm_source=hackmamba&utm_medium=hackmamba-blog) is a tool for building APIs that allows clients to request only the data they need and nothing more, making it more efficient and flexible than traditional REST APIs. 

Through the endpoint `/v1/graphql`, we can query and modify any resource type on our Appwrite server. Except for OAuth, every REST endpoint is available via GraphQL.

We can set up the GraphQL playground locally to test for Appwrite’s GraphQL APIs. To do so, create a file with a `.sh` extension — `appwrite-grapghql.sh` — and copy the following:

```js
#!/bin/sh

# To ensure the GraphQL docs load in the explorer, set the _APP_OPTIONS_ABUSE
# variable to disabled in your .env file.
# 
# If you have a domain, run this script passing the endpoint like:
#   ./start-appwrite-graphql-explorer.sh https://appwrite.example.com/v1
#
# After you see "GraphQL Explorer listening on port 3000!", open your browser
# and browse to http://localhost:3000/

APPWRITE_ENDPOINT=http://localhost/v1
if [ ! -z "$1" ]; then
    APPWRITE_ENDPOINT=$1
fi

docker run --rm -p "3000:3000" -e "SERVER_URL=$APPWRITE_ENDPOINT/graphql" appwrite/altair:0.3.0
```
The script will run GraphQL on port 3000: http://localhost:3000/


![appwrite-graphQL](https://paper-attachments.dropboxusercontent.com/s_D8DF24E69B6961169A9CFA34377755DCFEE181868E275886A4D520907BD1998A_1675057222929_image.png)

## Uploading products

In our `app.vue` file, we create our `uploadProduct()` function to add new documents to the database collection.

```js
async uploadProduct() {
      await database.createDocument(
        "[REPLACE_DATABASE_ID]",
        "[REPLACE_COLLECTION_ID]",
        "unique()",
        {
          productName: this.productName,
          productImage: this.productImage,
          productPrice: this.productPrice,
        }
      );
      const mutation = await graphql.mutation({
        query: `mutation CreateDocument(
        $databaseId: String!,
        $collectionId: String!,
        $documentId: String!
    ) {
        databasesCreateDocument(
            databaseId: $databaseId,
            collectionId: $collectionId,
            documentId: "ID.unique()"
        ) {
            _id
            _collectionId
            _databaseId
            _createdAt
            _updatedAt
            _permissions
             data
        }
    }`
      });
      mutation
        .then((response) => {
          console.log(response);
        })
        .catch((error) => {
          console.log(error);
        });
    },
}
```
In the code block above, our `uploadProduct()` function does the following:

- Creates a new document using Appwrite’s `createDocument()` function, while passing the collection ID and attribute values as parameters
- The `mutation` variable uses Appwrite’s GraphQL feature to create data 

Make sure to add a click event to the **Save** button and also prevent the page from reloading, like so:

```html
<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" @click.prevent="uploadProduct">
  Save
</button>
```

## Fetching our products

Our page displays the product information we entered in our form. With this logic, we want our `uploadProduct()` function to be responsible for creating the documents to display our product.

In our `app.vue` file, we create a `get``Products``()` function that gets called when we mount our app, puts all the details in an object, and pushes it to the products array.

```js
async getProducts() {
      let productData = await databases.listDocuments(
        "[REPLACE_DATABASE_ID]",
        "[REPLACE_COLLECTION_ID]"
      );
      this.products = productData
      
      const query = await graphql.query({
        query: `query ListDocument(
        $databaseId: String!,
        $collectionId: String!,
    ) {
        databasesListDocument(
            databaseId: $databaseId,
            collectionId: $collectionId,
        ) {
          total
          documents{
            _id
            _databaseId
            _collectionId
            _createdAt
            _updatedAt
            _permissions
            data
          }       
        }
    }`
      });
      query
        .then((response) => {
          console.log(response);
        })
        .catch((error) => {
          console.log(error);
        });
    },
```
Every time the page reloads or when changes occur in the database, we want to fetch all the products in real time. To do that, call the `getProducts` function on the mounted method like so:

```js
...
  mounted() {
    this.getProducts();
    if (account.get !== null) {
      try {
        client.subscribe("documents", (response) => {
          console.log(response);
          this.getProducts();
        });
      } catch (error) {
        console.log(error, "error");
      }
    }
  },
...
```

## Displaying the products

To display the products, all we have to do is loop over the products array and pass in all the data as props, like so:

```html
<div class="
          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
        ">
        <Products
        v-for="product in products.documents"
        :key="product.id"
        :productName="product.productName"
        :productImage="product.productImage"
        :productPrice="product.productPrice"
      />
</div>
```
Fill out the form to see what the product catalog looks like.

![product-catalog](https://paper-attachments.dropboxusercontent.com/s_FF63B95DA969D8F944ED3218AE5C44BBEC895BC6F666C4C760FF11E8DA6661D2_1676293465356_image.png)



![appwrite-database](https://paper-attachments.dropboxusercontent.com/s_FF63B95DA969D8F944ED3218AE5C44BBEC895BC6F666C4C760FF11E8DA6661D2_1675115058950_image.png)


For the product catalog images, any image link can be used. 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](https://cloudinary.com/blog/automating_file_upload_and_sharing?utm_source=appwrite&utm_medium=affiliate&utm_term=hackmamba).


## Conclusion
This article discussed creating a product catalog using the Appwrite GraphQL feature to mutate and retrieve 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
- [Appwrite Documentation](https://appwrite.io/docs?utm_source=hackmamba&utm_medium=hackmamba-blog)
- [Appwrite GraphQL](https://appwrite.io/docs/graphql?utm_source=hackmamba&utm_medium=hackmamba-blog)
- [Nuxt.js Documentation](https://nuxtjs.org/docs/get-started/installation/)