/ Coding

Building a simple NASA image search using Vue.js and Axios

I have been hard at work learning JavaScript. I try to learn something new everyday. A couple of days ago I was watching some codecasts about Vue.js and I got really exited again. So just this morning I set out to build something based on one of the codecasts I saw and I'd like to try and write about this. So here we go.

What we're building is a simple search app that searches 🚀 NASA 🚀 images. NASA has a super nice API that's ready and free to use for anyone. So great for testing your skills. In the app we're building we have a basic search field, that takes a query, and based on that query, it will return images. It will do all of this without reloading the page, thanks to Vue.

Screen-Shot-2018-01-25-at-12.54.24

Getting set up for action

First, if you haven't done so already, we have to install vue-cli so we can initialize a new project. We want to install this globally, so we'll use the -g flag.

npm install -g vue-cli

Next, we'll initialize our new project. vue-cli comes with a bunch of blueprints you can use to start a new project. We'll be using the webpack blueprint so all compiling and bundling will be handled by webpack. You'll be asked a bunch of questions. Just say yes to everything, or maybe leave out testing, we're not going to touch on that in this guide.

vue init webpack nasa-search

When this is done you can cd into nasa-search and run npm run dev and this will start a local server, watching all your files and reloading when you change something.

cd nasa-search && npm run dev

That was all for setting up. You've already got yourself a working app now on localhost:8080. How easy was that?!

Screen-Shot-2018-01-25-at-15.44.56

Action!

Now, when you open the folder that was generated for you in your favorite editor, you will see a whole bunch of files. Most of these files you don't need to worry about. In fact, we're going to be working with just two files inside the /src directory (see the structure below).

/assets
/components
    HelloWorld.vue // We're only using 
/router
    index.js       // these two file today :)
App.vue
main.js

Now go on and open up /components/HelloWorld.vue. This is the component that was generated and the one you're looking at when you're at http://localhost:8080/. This also the component we're using to build our little search app. But first, we're going to do some cleaning up, we're going to start with a blank component.

1. Clean Up

When you have the HelloWorld.vue file open, let's delete all the stuff that was auto generated and make it look like the codeblock below. In the end you should have three sections: <template></template>, <script></script> and <style></style>.

Vue.js uses what's called: single file components. So even though you're able to split components into different, .html, .css and .js files. You can also the code that's responsible for one thing, all in one file. My experience is that this keeps your code super focussed and clean.

Your <template></template> part is responsible for rendering the html, the <script></script> is responsible for the logic and your <style></style> is in charge of all styling.

<template>
  <div>
    
  </div>
</template>

<script>
export default {
  name: 'HelloWorld',
  data () {
    return {
    }
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>

</style>

2. Connecting template and logic

For our little search app, we'll need a search bar. So let's add that to our template as follows:

<template>
  <div>
    <input type="text">
  </div>
</template>

Now, to be able to do anything with the data that the user types into the search bar, we need to pull it into our javascript part of the app. This is actually really simple, so let's go!

We'll add a property to the data function on our component called query and we set it to be an empty string.

<script>
export default {
  name: 'HelloWorld',
  data () {
    return {
      query: '',
    }
  }
}
</script>

The query property is what we want to fill with what the user types into the input field. So we need a way to connect the two. If you're familiar with Angular, you're going to like this. It's super easy. Just add v-model="query" to your input field. Now, just doing this will make it work, but you won't be able to tell because nothing changes on the screen. So let's also add a <p>{{ query }}</p> in your template, underneath the input field.

<template>
  <div>
    <input type="text" v-model="query">
    <p>{{query}}</p>
  </div>
</template>

Now, try typing in the input field. If everything went well so far you should see what you've typed in the input field appear underneath the input field in the paragraph. This is really how easy it is to work with Vue.js.

3. Getting results

Now that we got this far. We just have to take this home.

To be able to handle the search, we need a method getPics on our component that actually handles the search, we need a property results to store our results, and we need a place in our template to display the results.

So, I have changed our code a little bit. I have removed the {{query}} from the template and added a placeholder for our results in a div. I also set a results property on our component to be an empty string.

Last but not least, I added a method's section to our component with one method in there: getPics().

<template>
  <div>
    <input type="text" v-model="query">
    <div class="results">{{results}}</div>
  </div>
</template>

<script>
export default {
  name: 'HelloWorld',
  data () {
    return {
      query: '',
      results: '',
    }
  },
  methods: {
    getPics() {
      this.results = 'results go here...' // this is where we'll get and set our results
    }
  }
}
</script>

So how do we get the method getPics() to run when a search happens. It's actually as easy as binding the method to an event. And you can do that with v-on. In our case, v-on:keyup so our search runs everytime someone types in a letter. When you've added this to the code, you'll see the results go here... appear as soon as you start typing.

<input type="text" v-model="query" v-on:keyup="getPics()">

So how do we get these actual results? We basically do an ajax request every keyup that get's the results and resets the results porperty to be the newly returned results.

For this, we'll use Axios. A super nice promise based HTTP request library that works really well with Vue.js as well. We'll need to install this first:

So stop you're server with crtl - c or open a new terminal window and while in your app's directory type the following:

npm install -save axios

Restart the development server with npm run dev.

Now we need to import axios into our component to be able to use it, so we'll add the following line on top of our <script> section:

import axios from 'axios'

Now we can write our getPics() method:

getPics(){
  axios.get(`https://images-api.nasa.gov/search?q=${this.query}&media_type=image`)
  .then((response) => {
    this.results = response.data.collection.items;
  })
  .catch((error) => {
    console.log(error)
  });
}

This basically does the following:

  • Sends a GET request to the NASA images api with this.query as a parameter. This parameter is always what the user typed in the box at any given time. At the same time the user releases a key on the keyboard, the function is run again and thus refreshing the results everytime.
  • Then the method stores the returned data in this.results. Because the API returns a big blob of data, I moved into the data a couple of steps to clear some of the unwanted data out: response.data.collection.items.

4. Showing results

If you've been following along, you now have a search bar that returns a whole big blob of mumble jumble. We don't want this of course we want to see some images. But we only want to show stuff, if there's stuff. So first we're going to make sure the results only show, when there's results. We can do this with v-if:

<div class="results" v-if="results"> // show results if results === true
    {{results}}
</div>

The data we got back is basically a whole big piece of JSON. And it returned us many objects we can loop over. So we need to iterate over our results property. We can do this with v-for, which lets you iterate over an iterable and display the element as many times as nececary. In our case this works as follows:

<div v-for="result in results" class="img-result">
    <img v-bind:src="result.links[0].href" />
    <p>{{result.data[0].title.substring(0,80)}}</p>
</div>

This says: for every result in the results object Vue creates a <div class="img-result"> and inside that div it will create an <img> tag with the href of the result and a <p> tag with the title of the image.

Don't be confused by this part: {{result.data[0].title.substring(0,80)}}. It's me diving into each objec to find the data we need. In this case the first 80 characters of the title of the picture.

And when you type in the search bar now, you should see the images appearing below the search bar.

5. Styling results

Last part: styling.

You can do this however you like. I think the code is readable enough for anyone to edit the layout themselves. But if you want my styling you can use the following CSS:

<style scoped>
  input[type="text"] {
    max-width: 960px;
    width: 100%;
    padding: 8px;
    /*font-size: 16px;*/
  }

  .results {
    max-width: 960px;
    margin: 0 auto;
    display: flex;
    justify-content: space-between;
    flex-wrap: wrap;
  }
  .img-result {
    position: relative;
    margin: 1em;
    width: 200px;
    height: 200px;
    float: left;
    display: flex;
    align-items: flex-end;
    justify-content: center;
  }
  .img-result p {
    width: 100%;
    z-index: 10;
    padding: 8px;
    margin: 0;
    color: white;
    text-align: center;
    text-shadow: 0px 1px 3px rgba(0,0,0,0.4);
    background: transparent;  /* fallback for old browsers */
    background: -webkit-linear-gradient(to bottom, rgba(0,0,0,0), rgba(0,0,0,0.5));  /* Chrome 10-25, Safari 5.1-6 */
    background: linear-gradient(to bottom, rgba(0,0,0,0), rgba(0,0,0,0.5)); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
    
  }
  .img-result img {
    position:absolute;
    width: inherit; height: inherit;
    object-fit: cover;
  }
</style>

That's it!

You can checkout my version off the app on github (source). Let me know if you liked this article, if you ran into any problems or if you like me to write about anything else! You can reach me on twitter @jaapbakker!