How to optimize image upload in Appwrite and Vuejs

Amarachi Iheanacho - Jun 16 '22 - - Dev Community

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 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.
  • An Appwrite instance running on our computer. Check out this article 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 by running these terminal commands to create a new Vue project.

    npm install -g @vue/cli
    # OR
    yarn global add @vue/cli
Enter fullscreen mode Exit fullscreen mode

After installing the Vue CLI, we navigate to our preferred directory and create a new project.

    vue create <name of our project>
Enter fullscreen mode Exit fullscreen mode

We change the directory to the project and start a development server with:

    npm run serve
Enter fullscreen mode Exit fullscreen mode

To see the app, we go to http://localhost:8080/

Installing dependencies

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@latest postcss@latest autoprefixer@latest
    npx tailwindcss init -p
Enter fullscreen mode Exit fullscreen mode

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 = {
      purge: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
      content: [],
      theme: {
        extend: {},
      },
      plugins: [],
    }
Enter fullscreen mode Exit fullscreen mode

Next, we add the tailwind directives in our src/index.css file.

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

Installing Appwrite

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 Vue application, we install the Appwrite client-side SDK for web applications.

    npm install appwrite
Enter fullscreen mode Exit fullscreen mode

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.

We go to localhost:80 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

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

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

Appwrite Console

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

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

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.

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

Creating the collection and attributes

Next, we set up our database that will store our order status. In the Appwrite web Console, we click on Database on the left side of the dashboard.

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

Appwrite Console

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

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

Appwrite Console

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.

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

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

    <style>
      .product-container{
        margin-left: 37%;
        width: 30%;
      }
    </style>
Enter fullscreen mode Exit fullscreen mode

Here's how our product creation form looks.

Product Catalog Creation Form

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

To understand how to embed the Cloudinary image upload widget in our project, check out this article.

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.

    <script>
      export default {
        name: 'App',
        data(){
          return{
            productName: '',
            productPrice: '',
            productImage: '',
            products: []
          }
        },
      }
    </script>
Enter fullscreen mode Exit fullscreen mode

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.

    import {sdk} from '../init';
Enter fullscreen mode Exit fullscreen mode

Then, we create a handleProductSubmit function in our App.vue file to create documents on our database.

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

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.

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

      }
    },
Enter fullscreen mode Exit fullscreen mode

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.

    mounted: function(){
      this.listProducts()
    },
    mounted: function(){
      handleProductSubmit: async function(){
          try {
              ...
           this.listProducts()
          } catch (error) {
            console.log(error)
          }
        },
    }
Enter fullscreen mode Exit fullscreen mode

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.

    handleDelete: async function(documentid){
      try {
        await sdk.database.deleteDocument(collectionID, documentid);
        alert("item have been deleted successfully")
        this.listProducts()
      } catch (error) {
        console.log(error)
      }
    }
Enter fullscreen mode Exit fullscreen mode

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.

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

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

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

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.

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

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

Conclusion

This article discussed managing optimized image upload with Cloudinary 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.

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