Build an e-commerce product catalog with Nuxt.js and GraphQL

Odewole Babatunde Samson - Feb 27 '23 - - Dev Community

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
  • An Appwrite instance running on your computer. Check out this post to create a local Appwrite instance. It can also be installed using digital ocean or 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.

npx create-nuxt-app product-catalog
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. 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:

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

Installing dependencies

Installing TailwindCSS
TailwindCSS 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.

npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
Enter fullscreen mode Exit fullscreen mode

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:

/** @type {import('tailwindcss').Config} */
module.exports = {
    content: [
        './components/**/*.{js,vue,ts}',
        './layouts/**/*.vue',
        './pages/**/*.vue',
        './**/*.vue',
        './plugins/**/*.{js,ts}',
    ],
    theme: {
        extend: {},
    },
    plugins: [],
};
Enter fullscreen mode Exit fullscreen mode

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:

@tailwind base;
@tailwind components;
@tailwind utilities;
Enter fullscreen mode Exit fullscreen mode

Lastly, we need to add TailwindCSS as a dependency in the nuxt.config.js, as shown below:

// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
    postcss: {
        plugins: {
            tailwindcss: {},
            autoprefixer: {},
        },
    },
    css: ['~/css/tailwind.css'],
});
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

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

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

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

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

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

appwrite-permission

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.

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

@tailwind base;
@tailwind components;
@tailwind utilities;

.product-container{
  margin-left: 37%;
  width: 30%;
}
.cursor{
  cursor: pointer;
}
Enter fullscreen mode Exit fullscreen mode

Here is what our form looks like at this point.

product-cat-app

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

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.

<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>
Enter fullscreen mode Exit fullscreen mode

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.

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

Using Appwrite's GraphQL

Appwrite's GraphQL API 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:

#!/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
Enter fullscreen mode Exit fullscreen mode

The script will run GraphQL on port 3000: http://localhost:3000/

appwrite-graphQL

Uploading products

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

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);
        });
    },
}
Enter fullscreen mode Exit fullscreen mode

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:

<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>
Enter fullscreen mode Exit fullscreen mode

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 getProducts() function that gets called when we mount our app, puts all the details in an object, and pushes it to the products array.

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);
        });
    },
Enter fullscreen mode Exit fullscreen mode

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:

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

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:

<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>
Enter fullscreen mode Exit fullscreen mode

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

product-catalog

appwrite-database

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.

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

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