Simple Photo App with Vue.js, Axios and Flickr API — Part 1

Project setup and our first API call

Hello! Welcome to Part 1 of this tutorial series, in which we’ll create a simple photo app using Vue.js, Axios, and the Flickr API. The goal of this series is to practice basic Vue concepts and gain exposure to working with an API. I am assuming basic working knowledge of HTML, CSS, and Javascript, and some familiarity with Vue. If you’re new to the latter, I suggest checking out a different series I wrote, Creating an Online Store with Vue CLI, where I offer more detailed explanations of core concepts. Let’s get to it!

Step 1: Project setup

Assuming you have the latest version of the Vue CLI installed, create a new project by navigating to where you want to save it in your terminal and entering the following:

vue create vue-flickr

We will manually select the following features:

? Check the features needed for your project:
◉ Babel
◯ TypeScript
◯ Progressive Web App (PWA) Support
◉ Router
◯ Vuex
◯ CSS Pre-processors
◉ Linter / Formatter
◯ Unit Testing
◯ E2E Testing
? Use history mode for router? (Requires proper server setup for index fallback in production) (Y/n) Y
? Pick a linter / formatter config:
ESLint with error prevention only
❯ ESLint + Airbnb config
ESLint + Standard config
ESLint + Prettier
? Pick additional lint features: (Press <space> to select, <a> to toggle all, <i> to invert selection)
❯◉ Lint on save
◯ Lint and fix on commit
? Where do you prefer placing config for Babel, PostCSS, ESLint, etc.?
In dedicated config files
❯ In package.json
? Save this as a preset for future projects? (y/N) N
success Saved lockfile.
✨  Done in 5.46s.
⚓ Running completion hooks...
📄 Generating README.md...
🎉  Successfully created project vue-flickr.
👉 Get started with the following commands:
$ cd vue-flickr
$ yarn serve

cd into vue-flickr and run yarn serve (or npm run dev) to confirm the project was successfully created. You will see the following:

Step 2: Additional package installation

In order to make requests to Flickr, we need a popular library called Axios. With Axios we’ll be able to:

  • Make XMLHttpRequests from the browser
  • Make http requests from node.js
  • Support the Promise API
  • Intercept request and response
  • Transform request and response data
  • Cancel requests
  • Automatically transform data into for JSON
  • Have client side protection against XSRF

Don’t worry if you don’t know what all that means. Just know axios helps us talk to Flickr. Install it by running the following in your terminal:

npm install axios --save

The installation was successful if you see this:

+ axios@0.18.0
added 90 packages from 123 contributors, removed 80 packages, updated 1082 packages and audited 24519 packages in 57.923s
found 0 vulnerabilities

Step 3: API Setup

Working with an API requires something called a key, a unique identifier that let’s Flickr know we’re a legitimate source making a request. To get that key, you have to have a Flickr account. And to get a Flickr account, you have to have a Yahoo email account. So:

  1. Create a Yahoo account if you don’t have one
  2. Log into Flickr
  3. Visit Flickr API Services and click “Create an App”

4. Click “Request an API Key” on the next page.

5. Click “Apply for a non-commercial key”.

6. Enter a name and description for your app. Check the boxes and hit “Submit”.

7. You’ll then be taken to a page where you can copy the key provided.

8. In the root project directory, (at the same level that index.html is located) create a file called config.js and add the following contents:

export default {
api_key: YOUR_KEY_HERE
}

Step 4: Making our first API call

We now have all the pieces to fetch photos from Flickr! Next, empty out Home.vue:

We need a way to enter and submit a search term. Add a form to the template that contains one text input and a submit button. The text input should usev-model to connect to a tag data property. The submit button should fire a search method when clicked, BUT it should not reload the page (hence the prevent modifier on the click event).

Make sure to actually add the data() function for the tag property and the methods section with the search function. For now the search function can just log this.tag to the console:

After a user submits their search term, the page should populate with photos that have been tagged with that term. The template should display a loading... message while the data is being fetched and then include an unordered list of photos (using v-for) once it’s ready.

Based on the above, our template is now using two new data properties, loading and images, so be sure to add them to the data() function.

Next, our search method needs to do a couple of things. When it’s first called, it should change loading to true, since the data fetching process is underway. Then it should make a request to the Flickr API using axios, and when the response (image data) is available, populate images with an array of images and set loading to false.

Let’s break the axios/API call part down a bit more. For clarity I separated out the actual API call into its own method, fetchImages(). To use axios, I imported it at the top of the script section, along with config, where our api_key is stored (because we’ll need that momentarily as well). axios then needs an object that specifies what kind of request we’re making (the method), where we’re making a request to (the url), and a bunch of parameters that further specify what kind of information we want from the url. The params object has the following contents:

  • method — we use the flickr.photos.search method to perform a search
  • api_key — we pass in our api_key so Flickr trusts us
  • tags — the terms we want to search for, provided by our tag data property
  • extras — any additional info we want about the photos that get returned. We want to see the photo so we need the url_n string. owner_name tell us who took the photo. datetaken tells us when the photo was taken. And views tell us how many people have viewed the photo.

You can find a full list of what’s available for a given photo here, and you can play around with the flickr.photos.search method here.

The rest of the params specify how many photos we get back and what kind of format the data is presented to us in. json will allow us to easily navigate the data structure in Javascript.

fetchImages() handles that actual retrieval of the photos, but it doesn’t populate images. That happens in a subsequent callback once the data is ready. We know the data is ready when a Promise is returned. Promises are what allow us to retrieve data asynchronously. In other words, we can ask axios to work on getting the photo data for us while also letting Vue do other things. In short, Promises help our app run faster and perform multiple tasks at once.

When the Promise is returned with our data, we tell Vue what to do with it. The then() method is called when a Promise is returned successfully. It accepts the response, which contains a data object. Inside data, we’ll find a photos object, which contains an array of photos named photo. That’s what we want images to store. Once we do that, we can mark loading as false and reset our tag property.

.then((response) => {
this.images = response.data.photos.photo;
this.loading = false;
this.tag = "";
})

After all this, you should get similar output to the following:

Step 5: Make an ImageCard Component

Yay! We’ve got access to data. Now let’s make it human readable by updating our template. Inside the unordered list of Home.vue, add:

...
<li v-for="image in images" :key="image.id">
<img :src="image.url_n" :alt="image.title">
<div>
<p v-if="image.title">{{image.title}}</p>
<p v-else>No Title Found</p>
<p>By {{image.ownername}}</p>
<section>
<p>{{image.datetaken}}</p>
<p>Views: {{image.views}}</p>
</section>
</div>
</li>
...

That’s better, but the page still needs some styling. To simplify Home.vue, pull the above markup into its own component. Inside the components directory, delete HelloWorld.vue and make a file called ImageCard.vue. Place the following inside:

The above component has the same markup from Home.vue, and receives an image prop so it knows what data to render. To use the ImageCard component, import it in Home.vue and register it:

...
import ImageCard from '@/components/ImageCard';

export default {
name: 'home',
components: {
ImageCard
},
...
}

Then add it to the template in place of the previous <li> markup:

...
<ul v-else>
<image-card
v-for="image in images"
:key="image.id"
:image="image" />
</ul>
....

The result should be the same as before in your browser.

Now add styles to Home.vue and ImageCard.vue to make it pretty.

During the project setup I didn’t install a pre-processor, so run the following in order to use SCSS:
npm install -D sass-loader node-sass

Update ImageCard.vue with class names in the template and a styles section:

Home.vue also needs some love. Style the navbar to be full-width but leave the list of photos is still centered with some margins. I also made a few other structural template modifications and of course added new class styles:

One last part here, I made a few CSS changes to App.vue as well:

The output should then look like this:

Step 6: Formatting dates with Moment.js

Our cards are looking great, but the dates are a little ugly. Moment.js is a popular date formatting library we can use. Install it with:

npm install moment --save

Inside ImageCard.vue, import moment and create a filter to format the date. Think of filters as functions that receive an input in need of formatting. The filter returns output in a way that we want. Visit the string formatting page for more ways to control how the dates show up:

<script>
import moment from 'moment';
export default {
name: 'ImageCard',
props: [ 'image' ],
filters: {
moment(date) {
return moment(date).format("MMMM Do, YYYY");
}
}
}
</script>

Use the filter in the template by adding the pipe character after the date we want to format, and referencing our filter by its name:

<p class="image-date">{{image.datetaken | moment}}</p>

Step 7: Basic data cleaning

You might’ve noticed that sometimes images don’t show up, producing the following in your browser:

This is probably due to data inconsistency from Flickr (ie a missing url_n string). A simple solution is to create a computed property called cleanImages that filters out any image that doesn’t have a url_n, and use cleanImages to loop through in our template instead of images.

In Home.vue, add the computed property:

...
computed: {
cleanImages() {
return this.images.filter(image => image.url_n)
}
},

Then in the template, change images to cleanImages:

<template>
...
<image-card
v-for="image in cleanImages"
:key="image.id"
:image="image" />
...
</template>

And there you have it! We’ve used axios and Vue.js to make a simple get request to the Flickr API, and made it look nice with basic CSS styles and some help from Moment.js! Next up, in Part 2, we’ll add image detail pages, and refactor our app a bit from a routing/page standpoint. See you then! 👋