Marvel API + Vuepack (Vue + Vuex)

November 12, 2017    api javascript programming vuejs vuex

Getting Started with Vue, Vuex, and API calls

Originally published at https://codeburst.io/marvel-api-vuepack-vue-vuex-c84067a7f7fc

TL;DR — This article will show you how to get quickly started with Vue and Vuex. In addition to grabbing data from an API and displaying it.

We will be building a simple application that searches the Marvel database for characters then displays the results.

What is Vue?

Vue (pronounced /vjuː/, like view) is a progressive framework for building user interfaces. Unlike other monolithic frameworks, Vue is designed from the ground up to be incrementally adoptable. The core library is focused on the view layer only, and is easy to pick up and integrate with other libraries or existing projects. (https://vuejs.org/v2/guide/)

What is Vuex?

Vuex is a state management pattern + library for Vue.js applications. It serves as a centralized store for all the components in an application, with rules ensuring that the state can only be mutated in a predictable fashion. (https://vuex.vuejs.org/en/intro.html)

https://vuex.vuejs.org/en/images/vuex.png

What is VuePack?

Vuepack is a starter kit which uses Vue 2, Vuex, Vue-router and Webpack 2. It essentially bootstraps and configures your project for you with these technologies.

Getting Started

What we will Build

The application we are implementing will search the Marvel database (via API) for characters based on user input and then display the results.

GitHub Repo for this demo

Marvel API

First, you will need a Marvel API key, so go over to the Marvel Developer Portal and sign up for an API key.

For the API calls, you can either directly call the API from http://gateway.marvel.com/v1/public or use a Node server as a proxy. However, if you are calling it directly then you will have to deal with Cross-Origin Resource Sharing (CORS). For this article, I will be using my own Marvel Power Levels API, written in Node and Express.

VuePack

We will be creating our Vue.js project with VuePack using the vue-cli. The vue-cli allows you to use different open source templates which will get you up and running without all the hassle of configurations.

$ npm install -g vue-cli
$ vue init egoist/vuepack new-project
$ cd new-project
$ npm install

Use the default options for the scaffolding (vue init):

? Do you want to use ESLint? Yes
? Generate components in JSX format? No
? Support Electron? No
? Add testcafe to run integration tests? No

Axios for API calls

We will be using Axios for our API calls.

$ npm install axios --save

Semantic UI

Finally, we will be using Semantic UI as our CSS framework.

<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="Content-type" content="text/html; charset=utf-8"/>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/semantic-ui/2.2.6/semantic.min.css">
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>
  <body>
    <div id="app"></div>
  </body>
</html>

Inside build/index.html

Start Developing

To run the application and start developing:

$ npm run dev

Open up your browser to localhost:4000. You should now see the default counter that is built in with the application.

Inside store/index.js we will be removing the default counter store code and setting up our own store for Marvel characters.

Replace the code with the following:

import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'

Vue.use(Vuex)

const state = {
  data: []
}

const mutations = {
  RECEIVE_CHARACTERS (state, { characters }) {
    state.data = characters
  }
}

const actions = {
  async FETCH_CHARACTERS ({ commit }, name) {
    const url = `http://localhost:8080/api/characters?limit=12&name=${name}`
    const { data } = await axios.get(url)
    commit('RECEIVE_CHARACTERS', { characters: data.results })
  }
}

const getters = {
  characters: state => {
    return state.data.map(data => {
      return {
        name: data.name,
        url: data.urls[1] ? data.urls[1].url : data.urls[0].url,
        image: `${data.thumbnail.path}.${data.thumbnail.extension}`,
        description: data.description === '' ? 'No description listed for this character.' : data.description
      }
    })
  }
}

const store = new Vuex.Store({
  state,
  mutations,
  actions,
  getters
})

export default store
store/index.js

In our store, we have 4 different parts that make up the store.

State

The state refers to an object where we define the data for our application structure. This data is to be used throughout our components, also known as the “single source of truth”.

const state = {
  data: []
}

For our application, we will only have 1 state which is data. data will contain our character data that is returned from the Marvel API.

Mutations

Mutations are where we write events that update the store. Mutation events are also the only way to update the store. A mutation has a string type and a handler function.

const mutations = {
  RECEIVE_CHARACTERS (state, { characters }) {
    state.data = characters
  }
}

In our application, the type is RECEIVE_CHARACTERS and the handler function updates the state’s data array with the payload that is passed in.

Actions

Actions are where we write functions that will commit changes to the store. Instead of mutating the store’s state directly, actions commit mutations, which then updates the state. Actions are called with a dispatch call, ex: this.$store.dispatch('FETCH_CHARACTERS')

const actions = {
  async FETCH_CHARACTERS ({ commit }, name) {
    const url = `http://localhost:8080/api/characters?limit=12&name=${name}`
    const { data } = await axios.get(url)
    commit('RECEIVE_CHARACTERS', { characters: data.results })
  }
}

Additionally, actions can be asynchronous so this is where we will put our API calls.

In our action FETCH_CHARACTERS, we are doing an asynchronous call to the API using Axios. Then when the async call is finished, we do a commit to the RECEIVE_CHARACTERS mutation with the API data results.

Getters

Getters are helper functions that retrieve data from our state.

const getters = {
  characters: state => {
    return state.data.map(data => {
      return {
        name: data.name,
        url: data.urls[1] ? data.urls[1].url : data.urls[0].url,
        image: `${data.thumbnail.path}.${data.thumbnail.extension}`,
        description: data.description === '' ? 'No description listed for this character.' : data.description
      }
    })
  }
}

In our application, we are using a getter to take the computed data and return a simplified object that we can use. The reason to use a getter here is because sometimes an API will return data we do not need or the data that we do need is deeply nested.

Special Mention: Modules

We are not using modules inside our application because they are a better fit for larger applications with multiple states.

Due to using a single state tree, all state of our application is contained inside one big object. However, as our application grows in scale, the store can get really bloated.

To help with that, Vuex allows us to divide our store into modules. Each module can contain its own state, mutations, actions, getters, and even nested modules — it’s fractal all the way down. (https://vuex.vuejs.org/en/modules.html)

Creating Components

Now that we have created our store and understand the core concepts of the Vuex store, we can now create components that interact with the store.

SearchCharacterForm Component

The first component we will be creating is the SearchCharacterForm. This component will contain our template and logic for the search textbox.

Search Character Form Component

Inside client/components, create a new file called SearchCharacterForm.vue.

Add this code to SearchCharacterForm.vue:

<template>
  <div class="search-character__form">
    <form @submit.prevent
      @submit="handleSearch()">

      <div class="ui action input">
        <input v-model="name"
          placeholder="Character Name"
          type="text"
          required />

          <button class="ui icon pink button">
            <i class="search icon"></i>
          </button>
      </div>
    </form>
  </div>
</template>

<script>
export default {
  data: function () {
    return {
      name: ''
    }
  },
  methods: {
    handleSearch () {
      this.$store.dispatch('FETCH_CHARACTERS', this.name)
    }
  }
}
</script>
client/components/SearchCharacterForm.vue

The main things to note here are the data and methods.

Data is used to define the properties that will be added to Vue’s reactivity system. This means that when the value of the property changes, the view will “react”, updating to match the new values and vice-versa. Also known as two-way binding. We will be using the name property to tell our action what Marvel character name to fetch.

Methods are used to define functions that will be used for any event handlers inside the template. In our application, we created the handleSearch method which dispatches an action to fetch characters. The fetch characters action will then update the store’s data state with the API results.

CharactersList Component

To show the results from the search textbox, we will need to create a CharactersList component. This component will contain our template and logic to display the characters data from our store.

Characters List Component

Inside client/components, create a new file called CharactersList.vue.

Add this code to the CharactersList.vue:

<template>
  <div v-if="!!characters.length" class="ui characters-list cards">
    <div v-for="character in characters" :key="character.name" class="ui card fadeIn-animation">
      <div class="image">
          <img :src="character.image" />
      </div>
      <div class="content">
        <div class="header">{{character.name}}</div>
        <div class="description">
          {{character.description}}
        </div>
      </div>
      <div class="extra content">
        <span class="right floated">
          <a target="_blank" :href="character.url">
            <button class="ui icon purple tiny button">
                More Info
            </button>
          </a>
        </span>
      </div>
    </div>
  </div>
</template>

<script>
import { mapGetters } from 'vuex'

export default {
  computed: mapGetters([
    'characters'
  ])
}
</script>

<style>
.characters-list.cards {
  margin-top: 2em;
  justify-content: center;
}

.characters-list.cards .image {
    position: relative;
    width: 100%;
    height: 246px;
    overflow: hidden;
}

.characters-list.cards .image img {
  position: absolute;
  left: 50%;
  top: 50%;
  height: 100%;
  width: auto;
  -webkit-transform: translate(-50%,-50%);
      -ms-transform: translate(-50%,-50%);
          transform: translate(-50%,-50%);
}

.ui.card, .ui.cards > .card {
  width: 364px;
}
</style>

The component will display and render the search results from our store’s state. In this component, we use the characters getter that we created earlier in our store file. Using a getter allows us to retrieve and use our state data in a clean and minimal way instead of doing a lot of data manipulation to get what we want.

Furthermore, we access our characters getter as a computed property because we want our component to always get the current state once it gets altered. So when the user searches a different Marvel character, the list will be updated because our CharactersList component knows to use the latest and current state data.

Displaying the Components

To finish up, we need to add these components in our Home view.

Inside views/Home.vue , replace the code with:

<template>
  <div class="page">
    <search-character-form></search-character-form>
    <characters-list></characters-list>
  </div>
</template>

<script>
import SearchCharacterForm from 'components/SearchCharacterForm'
import CharactersList from 'components/CharactersList'

export default {
  components: {
    SearchCharacterForm,
    CharactersList
  }
}
</script>

Conclusion

Going through the whole flow, a character search will dispatch an action that calls the API, and commits a mutation. The mutation then updates the state data, which is then displayed with the CharactersList component that uses the characters getter.

Now, if you run the app with npm run dev you should be able to see a search form. Searching a valid Marvel character should then display a list of the results.

Final Result

GitHub Repo for this demo


Phong Huynh
Software Developer
@xphong