Key Takeaways
- Vue.js works well for the teams that have to rewrite old codebases one step at a time.
- Vue CLI is the tooling for the Vue ecosystem and ensures that the various build tools work well together.
- Axios is a standard library for making HTTP requests.
- Bulma is a UI framework that works without dependencies.
- Cloudinary is a tool used for smart image manipulation.
- Amazon S3 (Amazon Simple Storage Solution) service is a solid choice for hosting simple web applications.
TL;DR
In this rather long tutorial, you'll learn how to use Vue CLI to build a Vue.js 2 application for searching and displaying Giphy images, and then manipulating/transforming them by using the Cloudinary API. Also, you'll learn how to deploy this application to AWS.
__ We’ll be using Vue.js 2, which will be referred to as Vue.js in the rest of this post.
Introduction
Due to Vue's progressive and flexible nature, it's ideally suited for teams that have to rewrite old codebases one step at a time.
If you want to check out some detailed comparisons between popular frontend frameworks, here are three great posts:
- If we chose our JavaScript Framework like we chose our music…
- Angular vs. React vs. Vue: A 2017 comparison
- Vue.js vs. React, Angular, AngularJS, Ember, Knockout, Polymer, Riot
I tend to answer general 'framework war' kind of questions in the following way:
- Stop with the ‘analysis paralysis’ discussion.
- Do your research, pick a framework—any framework—that the community is using, use it, and see how far it gets you.
- It isn’t worthwhile to discuss X being slow or Y being better until you try it for your specific use case and preference.
In this post, we'll use Vue.js and Cloudinary to apply some image manipulation techniques to the images that we'll fetch using the Giphy API in the demo application that we'll build. In the end, we'll use AWS to host the application. This is a hands-on from start to finish tutorial with all steps outlined.
Demo app
You can fork the complete source code on GitHub, or you can see the finished app in action.
Prerequisites
Make sure that you have the following tools installed:
- Node.js - step by step guide for both Windows and Mac
- Git - see this getting started tutorial
Vue CLI
As detailed in the Vue CLI README:
Vue CLI aims to be the standard tooling baseline for the Vue ecosystem. It ensures the various build tools work smoothly together with sensible defaults so you can focus on writing your app instead of spending days wrangling with configurations. At the same time, it still offers the flexibility to tweak the config of each tool without the need for ejecting.
To install vue-cli
, run:
npm install -g @vue/cli
To confirm that the installation ran successfully, run:
vue --help
__ For reference, the version of the CLI (vue --version
) used in this tutorial is 3.0.0-rc.3
.
Starting a new app with Vue CLI
We'll call our app image-search-manipulator
. Let's start a new app using vue-cli
:
vue create image-search-manipulator
After this command finishes, let's cd into the project and run it:
cd image-search-manipulator
npm run dev
You should get a console log similar to:
DONE Compiled successfully in 7859ms
Your application is running here: http://localhost:8080
You should see the following page in your browser if you visit this link: http://localhost:8080.
Folder structure
Now, let's open this project in the editor of your choice (I'm using Visual Studio Code), and you should see something like this:
Here we'll only focus on the src
folder. The contents of that folder should be something like this:
Adding content
If you search for the string Welcome to Your Vue.js App
, you'll see the string is within the HelloWorld.vue
file. A *.vue file is a custom file format that uses HTML-like syntax to describe a Vue component. This file contains the following (... is used for brevity in the listing below):
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<h2>Essential Links</h2>
<ul>
...
</ul>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data () {
return {
msg: 'Welcome to Your Vue.js App'
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h1, h2 {
font-weight: normal;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>
Without knowing anything about Vue.js, we can see where we would change the Welcome to Your Vue.js
App text. So, let's change that to Welcome to Image Search Manipulator
. While you do that, also remove the contents of the style
tag, as we don’t need it here.
Adding input and a button
Our basic application should have one input field and one button.
To do this, add the following code to the HelloWorld.vue
file in the template:
<input name="search">
<button>Search</button>
The template element of HelloWorld.vue
should look like this now:
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<input name="search">
<button>Search</button>
</div>
</template>
Actions
Having a simple search input field and a button does not do much on their own. We want to click the button, and we want to output something to the console just to verify that the button is working correctly.
This is how we define a function that will handle the button click in Vue:
<button @click="performSearch">Search</button>
Vue applications often contain an equivalent alternative shorthand syntax:
<button v-on:click="performSearch">Search</button>
Within the browser's developer tools, you'll observe an error such as:
webpack-internal:///./node_modules/vue/dist/vue.esm.js:592 [Vue warn]: Property or method "performSearch" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option or for class-based components, by initializing the property. See https://vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.
found in
---> <HelloWorld> at src/components/HelloWorld.vue
<App> at src/App.vue
<Root>
This error occurs because we haven't defined the performSearch
function. In the HelloWorld.vue
file, add the following function definition inside the script
tag, methods
object property:
<script>
export default {
name: "HelloWorld",
data() {
return {
msg: "Welcome to Image Search Manipulator"
};
},
methods: {
performSearch: function() {
console.log("clicked");
}
}
};
</script>
We’ve now defined the performSearch
function, which doesn't accept any parameters and has no return value.
Taking input
To print the string that was typed in the input field to the console, we first need to add a new attribute to the input field:
<input name="search" v-model="searchTerm">
The v-model instructs Vue.js to bind the input to the new searchTerm
variable, such that whenever the input text updates, the value of searchTerm also gets updated.You can learn more about v-model
and other form input bindings in the Vue documentation.
Finally, change the performSearch
function in order to log the value to the console:
performSearch: function() {
console.log(this.searchTerm);
}
Giphy search API
With our example application, we now want to connect our search fields to an API call to return images. Giphy provides a search API. We need to determine the request parameters needed to search Giphy's database. If we open the search API link, we determine the format of the service:
In the next section, we'll cover retrieving this data from within our app.
Vue.js HTTP requests
There are several ways to send HTTP requests in Vue.js. For additional options, check out this post about making AJAX calls in Vue.js.
In this tutorial, we’ll use Axios, a popular JavaScript library for making HTTP requests. It's an HTTP client that makes use of the modern Promises API by default (instead of JavaScript callbacks) and runs on both the client and the server (Node.js). One feature that it has over the native .fetch() function is that it performs automatic transforms of JSON data.
In your Terminal/Command prompt enter the following command to install Axios via npm:
npm install axios --save
Import Axios into the HelloWorld.vue
file just after the opening script
tag:
import axios from "axios";
The performSearch
function should now look like this:
const link = "http://api.giphy.com/v1/gifs/search?api_key=dc6zaTOxFJmzC&q=";
const apiLink = link + this.searchTerm;
axios
.get(apiLink)
.then(response => {
console.log(response);
})
.catch(error => {
console.log(error);
});
For reference, the contents of the HelloWorld.vue
file should now be:
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<input name="search" v-model="searchTerm">
<button @click="performSearch">Search</button>
</div>
</template>
<script>
import axios from "axios";
export default {
name: "HelloWorld",
data() {
return {
msg: "Welcome to Image Search Manipulator"
};
},
methods: {
performSearch: function() {
const link = "http://api.giphy.com/v1/gifs/search?api_key=dc6zaTOxFJmzC&q=";
var apiLink = link + this.searchTerm;
axios
.get(apiLink)
.then(response => {
console.log(response);
})
.catch(error => {
console.log(error);
});
}
}
};
</script>
__ When you're testing, you may also want to limit the number of images that you get from Giphy. To do that, pass limit=5
in the link like this: http://api.giphy.com/v1/gifs/search?api_key=dc6zaTOxFJmzC&limit=5&q=
Now, if you run the app, enter something in the search box, and click the search button, you'll see something like this in your console log:
The response
object is returned, and in its data property there are 25 objects, which hold information about the images that we want to show in our app.
To show an image, we need to drill down on the object images
, then fixed_height_still
, and finally on the url
property.
Also, we don't want to just show one image but all of the images. We'll use the v-for directive for that:
<img v-for="i in images" :key="i.id" :src="i.images.fixed_height_still.url">
The v-for
directive is used to render a list of items based on an array and it requires a special syntax in the form of item in items
, where items
is the source data array and item
is an alias for the array element being iterated on.
For reference, here's the full listing of the HelloWorld.vue
file:
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<input name="search" v-model="searchTerm">
<button @click="performSearch">Search</button>
<div>
<img v-for="i in images" :key="i.id" :src="i.images.fixed_height_still.url">
</div>
</div>
</template>
<script>
import axios from "axios";
export default {
name: "HelloWorld",
data() {
return {
msg: "Welcome to Image Search Manipulator",
searchTerm: "dogs",
images: []
};
},
methods: {
performSearch: function() {
const link =
"http://api.giphy.com/v1/gifs/search?api_key=dc6zaTOxFJmzC&limit=5&q=";
var apiLink = link + this.searchTerm;
axios
.get(apiLink)
.then(response => {
this.images = response.data.data;
})
.catch(error => {
console.log(error);
});
}
}
};
</script>
At this point, if we take a look at the app and search for 'coding', we'll get this:
Making the app look pretty with Bulma
Even though our example application functions as expected, the results do not look great.
Bulma is an open source CSS framework based on Flexbox. Bulma is similar to Bootstrap with fewer interdependencies, and is just pure CSS without adding JavaScript.
First, let's install Bulma:
yarn add bulma
if you're using Yarn, or npm install bulma
if you're using NPM.
Add bulma to the App.vue file, just after the import HelloWorld from "./components/HelloWorld"; line, like this:
import "../node_modules/bulma/css/bulma.css";
Here's the template from the HelloWorld.vue
file after adding Bulma classes to it:
<template>
<div class="container">
<h1 class="title">{{ msg }}</h1>
<input name="search" class="input" v-model="searchTerm">
<br><br>
<button @click="performSearch" class="button is-primary is-large">Search</button>
<div class="myContent">
<img v-for="i in images" :key="i.id" :src="i.images.fixed_height_still.url">
</div>
</div>
</template>
Here's a recap of what we did:
- Added the class
container
to the first div tag - Added the class
title
to theh1
tag - Added the class
input
to theinput
tag - Added the following classes to the button:
button is-primary is-large
For demonstration purposes, the myContent
class was added like this:
.myContent {
padding-top: 20px;
}
And a bit of padding was added around the img tags:
img {
padding: 5px;
}
The following classes were added to the HelloWorld.vue file like this:
<style>
.myContent {
padding-top: 20px;
}
img {
padding: 5px;
}
</style>
With these few quick changes, we now have a better-looking app:
Bulma has many options without the overhead typical of larger CSS frameworks.
Image manipulation
We’ll now use a few techniques to manipulate the images.
Cloudinary is a service offering many image manipulation options.
After you create a Cloudinary account, you need to install Cloudinary:
npm install cloudinary-core --save
For reference, here's the full source code listing and a summary of changes:
- Added new div
<div class="myContent" v-html="manipulatedImages"></div>
. Because themanipulatedImages
variable will contain HTML, we need to use thev-html
directive to show it in the template as such, and not as a string. You can learn more about thev-html
directive in the Vue documentation. - Imported Cloudinary
import cloudinary from "cloudinary-core"
; - Called Cloudinary on each image returned from the Giphy API (starts with
const cloudinaryImage = cl
):
<template>
<div class="container">
<h1 class="title">{{ msg }}</h1>
<input name="search" class="input" v-model="searchTerm">
<br><br>
<button @click="performSearch" class="button is-primary is-large">Search</button>
<div class="myContent">
<img v-for="i in images" :key="i.id" :src="i.images.fixed_height_still.url">
</div>
<div class="myContent" v-html="manipulatedImages"></div>
</div>
</template>
<script>
import axios from "axios";
import cloudinary from "cloudinary-core";
export default {
name: "HelloWorld",
data() {
return {
msg: "Welcome to Image Search Manipulator",
searchTerm: "dogs",
images: [],
manipulatedImages: ""
};
},
methods: {
performSearch: function() {
var link =
"https://api.giphy.com/v1/gifs/search?api_key=dc6zaTOxFJmzC&limit=5&q=";
const apiLink = link + this.searchTerm;
const cl = new cloudinary.Cloudinary({
cloud_name: "nikola"
});
axios
.get(apiLink)
.then(response => {
this.images = response.data.data;
this.images.forEach(image => {
const cloudinaryImage = cl
.imageTag(image.images.fixed_height_still.url, {
width: 150,
height: 150
})
.toHtml();
this.manipulatedImages += cloudinaryImage;
});
})
.catch(error => {
console.log(error);
});
}
}
};
</script>
<style>
.myContent {
padding-top: 20px;
}
img {
padding: 5px;
}
</style>
Deploying to AWS S3
We're going to complete this tutorial by publishing our app on Amazon S3 (Amazon Simple Storage Solution).
First, login (or create an AWS account) to the AWS Console and search for S3:
Familiarize yourself with the interface and create a new AWS S3 bucket. Buckets are places where we may add files. Make sure that, when you create the bucket, you select the Grant public read access to this bucket setting as bucket contents are private by default:
Finally, go into your S3 bucket and enable static website hosting (enter index.html as suggested in the Index document field):
Build it
Run npm run build
to build a production version of your app. All production-ready files are going to be placed in the dist
folder.
If you get a blank page after you upload the contents of the dist
folder to the AWS bucket and visit the website, then check out the solution which basically states that you need to remove the / from the assetsPublicPath
in config/index.js
:
If you encounter the blank page issue, perform the step above and repeat the npm run build command.
Upload
Upload the entire content of the dist
directory to the S3 bucket. This can be done using a simple drag and drop. Make sure you set permissions for the files so that they are globally read accessible:
That's all there is to it!
You may view my version of the app created in this tutorial.
Backup
An important tip to finish up the tutorial is to backup your work. Several options are available for backing up apps on AWS, including the AWS native solution, and other, third-party solutions that offer added features such as zero downtime. These might not be required for your basic app, but are worth looking into for larger-scale use cases.
It all depends on your stack—but there are solutions for backing on any stack. For example, on the Azure stack, you could use Azure Backup.
Conclusion
In this tutorial, we learned how to get started with using Vue.js by building an application for searching images via Giphy's API. We prettified our app with the Bulma CSS framework. Then we transformed the images using Cloudinary. Finally, we deployed our app to AWS.
Please leave any comments and feedback in the discussion section below and thank you for reading!
About the Author
Gilad David Maayan is a technology writer who has worked with over 150 technology companies including SAP, Oracle, Zend, CheckPoint and Ixia, producing technical and thought leadership content that elucidates technical solutions for developers and IT leadership.