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
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
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
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: [],
};
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;
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'],
});
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
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.
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.
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.
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 |
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.
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;
}
Here is what our form looks like at this point.
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>
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");
}
}
}
...
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
The script will run GraphQL on port 3000: http://localhost:3000/
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);
});
},
}
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>
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.
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:
...
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:
<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.
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.