How to optimize image upload in Appwrite and Vuejs
Tags: javascript, vue, tutorial, programming
Published: 2022-06-16 14:25:57

Image upload widgets allow users to preview images they upload to an application or database via forms, Google Drive, etc. These widgets enable the user to decide to go through or delete the images chosen for submission. 

## What we will be building

This post discusses uploading images using the [Cloudinary](https://cloudinary.com/?utm_source=hackmamba&utm_medium=hackmamba) image upload widget and storing the resulting image URL on the Appwrite database to create an e-commerce product catalog.  

**GitHub URL**

https://github.com/Iheanacho-ai/optimised-image-upload-vue



## Prerequisites 

To get the most out of this project, the following are required:

- A basic understanding of CSS, JavaScript, and Vue.js.
- Docker Desktop is installed on the computer, run the `docker -v` command to verify that we have Docker Desktop installed; if not, install it from the [Get Docker documentation](https://docs.docker.com/get-docker/). 
- An Appwrite instance running on our computer. Check out [this article](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; we will use Appwrite’s robust database and Realtime service to manage our application.
    
## Setting up our Vue.js application

We install the [Vue CLI](https://cli.vuejs.org/) by running these terminal commands to create a new Vue project.

```bash
    npm install -g @vue/cli
    # OR
    yarn global add @vue/cli
```
After installing the Vue CLI, we navigate to our preferred directory and create a new project.

```bash
    vue create <name of our project>
```
We change the directory to the project and start a development server with:

```bash
    npm run serve
```
To see the app, we go to http://localhost:8080/


## Installing dependencies

**Installing Tailwind CSS**

[Tailwind CSS](https://tailwindcss.com/) 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:

```bash
    npm install -D tailwindcss@latest postcss@latest autoprefixer@latest
    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.

```js
    module.exports = {
      purge: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
      content: [],
      theme: {
        extend: {},
      },
      plugins: [],
    }
```
Next, we add the tailwind directives in our `src/index.css` file.

```css
    @tailwind base;
    @tailwind components;
    @tailwind utilities;
```
**Installing Appwrite**

[Appwrite](https://appwrite.io/?utm_source=hackmamba&utm_medium=hackmamba-blog) Appwrite is an open-source, end-to-end, back-end server solution that allows developers to build applications faster.

To use Appwrite in our Vue application, we install the Appwrite client-side SDK for web applications.

```bash
    npm install appwrite
```

## Creating a new Appwrite project

During the creation of the Appwrite instance, we specified what hostname and port we use for our console. The default value is [localhost:80](http://localhost/).

We go to [localhost:80](http://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.

![Appwrite Console](https://paper-attachments.dropbox.com/s_314B50E78D2533608F0DEE9BE97FEF3C5B7916B2F8CAC55559F4DCA3A07D86F6_1648400276700_Screenshot+64.png)


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**.  

![Appwrite Console](https://paper-attachments.dropbox.com/s_17FAC54E0DBFC815EFFDF72E073F06A369E729DDABD8DFA8B3E8F0ACC56D0153_1653386539775_Screenshot+274.png)


We copy the **Project ID** and **API Endpoint**, which we need to initialize the Appwrite Web SDK.

![Appwrite Console](https://paper-attachments.dropbox.com/s_17FAC54E0DBFC815EFFDF72E073F06A369E729DDABD8DFA8B3E8F0ACC56D0153_1653354974077_Screenshot+275.png)


We create an `init.js` file in our project's root directory to initialize the Appwrite Web SDK with the following code.

```js
    import { Appwrite } from 'appwrite';
    export const sdk = new Appwrite();
    sdk
      .setEndpoint('http://localhost/v1') // Replace this with your endpoint
      .setProject('projectID'); // Replace this with your ProjectID
```    

**Creating an anonymous user session**

Appwrite requires a user to sign in before reading or writing to a database to enable safety in our application. However, they allow us to create an anonymous session to bypass that policy for simple projects. We'll do so in our `init.js` file.

```js
    import { Appwrite } from 'appwrite';
    export const sdk = new Appwrite();
    sdk
      .setEndpoint('http://localhost/v1') // Replace this with your endpoint
      .setProject(projectID); // Replace this with your ProjectID
   // Create an anonymous user session
    sdk.account.createAnonymousSession().then(
        (response) => {
            console.log(response);
        },
        (error) => {
            console.log(error);
        }
    );
```
**Creating the collection and attributes**

Next, we set up our database that will store our order status. In the Appwrite web [Console](http://localhost/console), we click on **Database** on the left side of the dashboard.


![Appwrite Console Database](https://paper-attachments.dropbox.com/s_17FAC54E0DBFC815EFFDF72E073F06A369E729DDABD8DFA8B3E8F0ACC56D0153_1653355005065_Screenshot+277.png)


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. We can modify the permissions to specify who has access to read or write to our database.


![Appwrite Console](https://paper-attachments.dropbox.com/s_17FAC54E0DBFC815EFFDF72E073F06A369E729DDABD8DFA8B3E8F0ACC56D0153_1653355031990_Screenshot+279.png)


On the right of the **Permissions** page, we copy the **Collection ID**, which we need to perform operations on the collection’s documents.

Next, we go to our **Attributes** tab to create the properties we want a document to have. 


![Appwrite Console](https://paper-attachments.dropbox.com/s_17FAC54E0DBFC815EFFDF72E073F06A369E729DDABD8DFA8B3E8F0ACC56D0153_1653355067746_Screenshot+280.png)


We create three string attributes: **productName, productPrice,** and **productImage.**

![Appwrite Console](https://paper-attachments.dropbox.com/s_17FAC54E0DBFC815EFFDF72E073F06A369E729DDABD8DFA8B3E8F0ACC56D0153_1653355094632_Screenshot+281.png)

## Creating our product creation page

We create our product creation page in the `App.vue` file. This product creation page will contain two sections: the first includes a form to collect product information, and the second section lists the products in our database.

Here, we'll create a form to submit the product's name, price, and image to the database. We add the following code snippet in the `index.js` file to create the form styled with Tailwind CSS.

```vue
    <template>
      <div class= 'product-catalog'>
          <div class="product-container mt-5 md:mt-0 md:col-span-2">
          <div class="shadow sm:rounded-md sm:overflow-hidden">
            <div class="px-4 py-5 bg-white space-y-6 sm:p-6">
              <div>
                <label for="price" class="block text-sm font-medium text-gray-700">Product Name</label>
                <div class="mt-1 relative rounded-md shadow-sm">
                  <input type="text" name="price" v-model="productName" class="focus:ring-indigo-500 focus:border-indigo-500 block w-full pl-7 pr-12 sm:text-sm border-gray-300 rounded-md" placeholder="product name" />
                </div>
              </div>
              <div>
                <label for="price" class="block text-sm font-medium text-gray-700">Price</label>
                <div class="mt-1 relative rounded-md shadow-sm">
                  <div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
                    <span class="text-gray-500 sm:text-sm"> $ </span>
                  </div>
                  <input type="text" name="price" v-model="productPrice" class="focus:ring-indigo-500 focus:border-indigo-500 block w-full pl-7 pr-12 sm:text-sm border-gray-300 rounded-md" placeholder="0.00" />
                </div>
              </div>
              <button nclass="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
                Upload files
              </button>
              <div className="px-4 py-3 bg-gray-50 text-right sm:px-6">
              <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"
              >
                Save
              </button>
            </div>
            </div>
          </div>
        </div>
      </div>
    </template>
```

In the `style` section of our `App.vue` file, we build on the Tailwind CSS styles with these CSS styles.

```vue
    <style>
      .product-container{
        margin-left: 37%;
        width: 30%;
      }
    </style>
```
Here's how our product creation form looks.


![Product Catalog Creation Form](https://paper-attachments.dropbox.com/s_17FAC54E0DBFC815EFFDF72E073F06A369E729DDABD8DFA8B3E8F0ACC56D0153_1653150395500_Screenshot+333.png)


 
**Embedding the Cloudinary Upload widget**

In this project, the **Upload File** button opens up the Cloudinary image upload widget to allow us to upload images to the Appwrite database. 

![Cloudinary Upload Widget](https://paper-attachments.dropbox.com/s_17FAC54E0DBFC815EFFDF72E073F06A369E729DDABD8DFA8B3E8F0ACC56D0153_1653355202206_Screenshot+334.png)


To understand how to embed the Cloudinary image upload widget in our project, check out [this article](https://cloudinary.com/blog/uploading_vue_files_and_rich_media_in_two_easy_steps).


## Add interaction with our database

When a user clicks on the save button, we want to store the product items on the Appwrite database and list the stored items in our application. 

**Storing the product information in the database**

We store information on the Appwrite database as documents. In the script section of our `App.vue` file, we create four variables in the `data` object.

```vue
    <script>
      export default {
        name: 'App',
        data(){
          return{
            productName: '',
            productPrice: '',
            productImage: '',
            products: []
          }
        },
      }
    </script>
```
The variables hold the following information.

- The `productName` variable holds the name of the product to be stored in the database
- The `productPrice` variable contains the price of the product to be stored in the database
- The `productImage` variable holds the image URL of the product to be stored in  the database
- The `products` variable is an array of all the products stored in the database

Next, we import the `sdk` instance from our `init.js` file into our `App.vue` file. 
 

```js
    import {sdk} from '../init';
```
Then, we create a `handleProductSubmit` function in our `App.vue` file to create documents on our database.

```js
    handleProductSubmit: async function(){
      try {
          await sdk.database.createDocument(collectionID, 'unique()', {
        "productName" : this.productName,
        "productPrice": this.productPrice,
        "productImage": this.productImage
      });
       alert('your job item has been successfully saved')
       this.productName= '',
       this.productPrice= '',
       this.productImage= ''
      } catch (error) {
        console.log(error)
      }
    },
```
The `handleProductSubmit` function does the following:

- Creates a new document using Appwrite’s `createDocument()` function while passing the collection ID and attribute values as parameters
- Alerts us when we have successfully saved the document, then cleans out the information in the local state variables
- Logs any error encountered during document creation to our console


https://gist.github.com/Iheanacho-ai/d20d8322de5e1d51d0ad89febeac996e


**Listing the products**

In the `App.vue` file, we create a `listProducts` function to fetch the product information stored in our database. 

```js
    listProducts: async function(){
      try {
        let response = await sdk.database.listDocuments('628a9019078ea3c2b384');
        this.products = response.documents
      } catch (error) {
        console.log(error)
        
      }
    },
```
The `listProducts` function does the following:

- Lists all the documents in the collection
- Saves the documents in the `products` array
- Logs any error encountered to the console

We then call the `listProducts` function in our `handleProductSubmit` function and the `mounted` lifecycle. 

```js
    mounted: function(){
      this.listProducts()
    },
    mounted: function(){
      handleProductSubmit: async function(){
          try {
              ...
           this.listProducts()
          } catch (error) {
            console.log(error)
          }
        },
    }
```
**Deleting the products**

Next, we create a `handleDelete` function in our `App.vue` file to delete any product and its information from our Appwrite database.

```js
    handleDelete: async function(documentid){
      try {
        await sdk.database.deleteDocument(collectionID, documentid);
        alert("item have been deleted successfully")
        this.listProducts()
      } catch (error) {
        console.log(error)
      }
    }
```
This `handleDelete` function does the following:

- Deletes a document from our Appwrite database collection with the `deleteDocument` function. This `deleteDocument` function finds the document using its document ID and collection ID parameter.
- Alerts the user when we have deleted a document successfully.
- Lists the remaining product on our database using the `listProducts` function.
- Logs any error encountered during document deletion.

After creating our functions, here is how our `App.vue` file looks.

https://gist.github.com/Iheanacho-ai/7a7eb7a1649bfc492cf9e3282101fcb8


Next, we pass the `productName` and `productPrice` variables to our form's `input` elements.


```vue
    <input type="text" name="price" v-model="productName" class="focus:ring-indigo-500 focus:border-indigo-500 block w-full pl-7 pr-12 sm:text-sm border-gray-300 rounded-md" placeholder="product name" />


    <input type="text" name="price" v-model="productPrice" class="focus:ring-indigo-500 focus:border-indigo-500 block w-full pl-7 pr-12 sm:text-sm border-gray-300 rounded-md" placeholder="0.00" />
```

Finally, we pass the `handleProductSubmit` function to our **Save** button.

```vue
    <button
      type="button"
      @click="handleProductSubmit"
      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"
    >
      Save
    </button>
```
**NOTE:** **We use a button with a `type= button` to override the button’s default submit behavior.**

After completing the steps thus far, here is how our `App.vue` file looks.


https://gist.github.com/Iheanacho-ai/8f5106c552eee8cf1d4cd4efc8a3d5fa

## Creating the product listing user interface

To create the user interface for displaying the products stored on the Appwrite database, we paste this code into our `App.vue` file.

```vue
    <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>
            <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">
              <a href="#" v-for= 'product in products' :key= 'product.productName' className="group">
                <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="product.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">{{product.productName}}</h3>
                <p className="mt-1 text-lg font-medium text-gray-900">${{product.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"
                @click="handleDelete(product.$id)" 
                >
                  Delete
                </button>
              </a>   
            </div>
        </div>
      </div>
```
In the code block above, we:

- Loop through the `products` array to render each product.
- Pass in the `productName`, `productPrice`, and `productImage` variables on the `products` array.
- Pass the `handleDelete` function and document ID parameter to a `@Click` event listener on the **Delete** `button`.

Here is how the `App.vue` file looks.


https://gist.github.com/Iheanacho-ai/03f5eb3f75fa8e799fd217c23229b97e


Fill out the form to see how the product catalog looks.


![Product Catalog](https://paper-attachments.dropbox.com/s_17FAC54E0DBFC815EFFDF72E073F06A369E729DDABD8DFA8B3E8F0ACC56D0153_1653355239941_Screenshot+335.png)

## Conclusion

This article discussed managing optimized image upload with [Cloudinary](https://cloudinary.com/?utm_source=hackmamba&utm_medium=hackmamba) and storing the images on an Appwrite database. Using this information, we created a product catalog. You can modify the fields to accommodate more product information.


## Resources

Here are some resources that might be helpful.

- [Uploading Vue Files and Rich Media in Two Easy Steps](https://cloudinary.com/blog/uploading_vue_files_and_rich_media_in_two_easy_steps)
- [Create a local Appwrite instance in 3 steps](https://dev.to/hackmamba/create-a-local-appwrite-instance-in-3-steps-19n9?utm_source=hackmamba&utm_medium=hackmamba)
- [Database API](https://appwrite.io/docs/client/database?utm_source=hackmamba&utm_medium=hackmamba-blog)