Chapter 11. Communicating with a server

This chapter covers

  • Using Nuxt.js for server-side rendering
  • Retrieving third-party data with Axios
  • Using VuexFire
  • Adding authentication

We’ve discussed Vuex and how state management can benefit our larger Vue.js applications. Now we’re going to look at communicating with a server. In this chapter, we’ll look at server-side rendering (SSR) and how we can use it to help improve our app’s responsiveness. We’ll use Axios to retrieve data from a third-party API. Then we’ll look at VuexFire. VuexFire is library that helps us communicate with Firebase, a backend service that helps with application development. Last, we’ll see how to add simple authentication to our VuexFire app.

Before we move on, let me preface this chapter by saying there are many ways to communicate with a server in Vue.js. We could use an XMLHttpRequest or use any number of AJAX libraries out there. In the past, Vue officially recommended the Vue resource library as the official AJAX library. Evan You, the creator of Vue, retired the library in late 2016 from official recommendation status. As far as the Vue community goes, you can use whatever library you like.

With all that said, I determined that Axios, Nuxt.js, and VuexFire are several of the most popular libraries that could help us communicate with a server in one way or another. They’re all different, however. Nuxt.js is a powerful framework for creating server-rendered apps, but Axios is a frontend HTTP client. VuexFire helps us communicate with Firebase. All three take different approaches to communication.

The purpose of this chapter is to give you working knowledge of all three of these libraries and frameworks. We’ll create examples for each, but we won’t go too deep. Each subject could warrant its own chapter—and in the case of Nuxt, its own book. Nevertheless, this will be a good primer for these topics, and I’ll include links to each resource, so you can dive in deeper.

11.1. Server-side rendering

Vue.js is a single-page application framework that uses client-side rendering. The logic and routing of the application are written in JavaScript. When the browser connects to the server, the JavaScript is downloaded. The browser is then responsible for rendering the JavaScript and executing the Vue.js application. With larger applications, the time to download and render the application can be significant. You can see from figure 11.1 how this might look.

Figure 11.1. Client-side rendering

Server-side rendering (SSR) with Vue.js is different. In this case, Vue.js reaches out to the server, which then sends over the HTML so the browser can display the page immediately. The user sees the page load quickly. The server then sends the JavaScript, and it loads in the background. It’s worth mentioning that even if the user sees the webpage, they may not interact with it until Vue is done executing (figure 11.2).

Figure 11.2. Server-side rendering

Typically, SSR is a more pleasant experience for the user because the initial load is fast. Most users don’t have the patience to wait for slow apps to load.

SSR also has unique advantages for search engine optimization (SEO). SEO is a term that describes getting organic visibility (non-paid) on search-engine results. Although little is known of the precise methods Google and other search engines use when determining search-engine rankings, there’s a worry that search-engine robots have problems when crawling client-side rendered pages. This could cause issues with rankings. SSR helps prevent these problems.

Vue.js doesn’t come with SSR by itself, but there are great libraries that make it easy to add SSR to our app. The two most popular are vue-server-renderer and Nuxt.js. You can find more information on SSR from the official SSR guides at https://ssr.vuejs.org/. Instead. we’ll look at how we can create an SSR app using Nuxt.js.

11.2. Introducing Nuxt.js

Nuxt.js is a higher-level framework built on top of the Vue ecosystem that helps create SSR applications without having to worry about all the aspects of delivering a production-ready, server-rendered app.

Nuxt focuses on UI rendering, and much of the client/server layer is abstracted away. It can act as a standalone project or an addition to a Node.js-based project. In addition, it has a built-in static generator that can be used to create Vue.js websites.

When you create a project with Nuxt, you get Vue 2, the Vue router, Vuex, vue-server-renderer, and vue-meta. Under the hood it uses Webpack to help put everything together. It’s an all-in-one package for getting up and running.

Info

We can use Nuxt with an existing Node.js application but we won’t look at that today. If you want more information on creating a Nuxt app with an existing Node.js project, check the official documentation at https://nuxtjs.org/guide/installation.

Nuxt provides a starter template to help us get started. This starter template can be downloaded from the official GitHub repository at http://mng.bz/w0YV. We can also create a project using the starter template with Vue-CLI. (If you haven’t installed Vue-CLI, see appendix A for installation instructions.)

If you’re using Nuxt, you’ll need Node. Nuxt requires version 8 or later to work. Otherwise, you’ll get async errors when you try to start the project.

Info

The project listed in this chapter is working on Nuxt 1.0. But as of this writing, Nuxt 2.0 is in development and in beta. This example should work on both but if you encounter any problems, check the official GitHub repository for this book at https://github.com/ErikCH/VuejsInActionCode. This code will be maintained.

We’ll use the Vue-CLI to create a project. At the command prompt, run the following command:

$ vue init nuxt-community/starter-template <project-name>

This will create a new Nuxt project using the starter template. Next, you’ll need to change into the directory and install the dependencies with the following commands:

$ cd <project-name>
$ npm install

To launch the project, run the npm run dev command:

$ npm run dev

This will start a new project on localhost port 3000. If you open a web browser, you should see the welcome page (figure 11.3). If the welcome page isn’t displayed, double-check to make sure you didn’t skip the npm install step.

Figure 11.3. Nuxt.js starter template page

Let’s look at how to use Nuxt.js in a real app.

11.2.1. Creating a music search app

Server-side rendered apps can be useful and powerful. Let’s look at what Nuxt.js can do for us. Let’s imagine you need to create an app that interacts with the iTunes API. The iTunes API has a list of millions of artists and albums. You want to search for any artist and display their discography.

Note

You can find more information on the iTunes API in the official documentation at http://mng.bz/rm99.

In building our app, we’ll use two different routes. The first route will display an input box for searching the iTunes API. This page will look like figure 11.4.

Figure 11.4. Search page for the iTunes API

The next route will display the artist’s album information.

Info

To make things look nicer, we’ll use a material component framework called Vuetify. We’ll talk more about that later.

To make things more interesting, we’ll pass information from our search route into the results route by using a parameter. After entering an artist’s name in the search box (Taylor Swift), the results page will be displayed (figure 11.5). You can see in the URL box at the top of the page that “Taylor%20Swift” has been passed in.

Figure 11.5. Search results page

The search page will display all the albums associated with the artist. It will display the album name, artist name, and the cover art, and the card will link to the iTunes artist page. In this example we’ll also look at middleware, which will allow us to write code before the route is rendered. We’ll see how we can use the Axios library to communicate with the iTunes API. We’ll wrap everything up by looking at Vuex again.

11.2.2. Creating a project and installing dependencies

Let’s begin creating our music API app by using the Vue-CLI starter template. We’ll then install all our dependencies. Run the following command at the prompt:

$ vue init nuxt-community/starter-template itunes-search

After the application is created, install Vuetify and Axios as an npm package using the npm install command. In addition, Vuetify requires both the stylus and stylus-loader so we can set up our stylus CSS that Vuetify uses.

Note

Vuetify is a material component framework for Vue.js 2.0. It adds many easy-to-use and beautifully crafted components. It has similarities to other UI frameworks, such as Bootstrap. You can find more about Vuetify at the official website at https://vuetifyjs.com.

Run the following commands to install Vuetify, Axios, stylus, and stylus-loader:

$ cd itunes-search
$ npm install
$ npm install vuetify
$ npm install axios
$ npm install stylus –-save-dev
$ npm install stylus-loader –-save-dev

This will install all the dependencies we need to get started, but to get these dependencies to work correctly, we’ll need a little more setup. We’re going to set up Axios and Vuetify in our vendor file, register Vuetify inside our app, set up the Vuetify plugin, and finally set up our CSS and fonts.

The nuxt.config.js file is used to configure the Nuxt app, so navigate to the nuxt.config.js file in the root of the /itunes-search folder. Find the section that begins with extend (config, ctx). This section is used to automatically run ESLint on our code every time we save. (ESLint is a pluggable linting utility that checks our code for style and formatting, among other things.) We could edit the .eslintrc.js file and change the default linting, but for the sake of simplicity, we’ll delete this section instead. This will turn off the automatic linting. Next add a new vendor option under build. We then need to add Axios and Vuetify to the vendor option, as seen in listing 11.1.

Let me explain how this works. Every time we import a module in Nuxt.js, the code is added to a page bundle that Webpack creates. This is a part of something called code splitting. Webpack splits our code into bundles, which then can be loaded on demand or in parallel. When we add the vendor option it makes sure that the code is only added once to the vendor bundle file. Otherwise each import would add to each page bundle and increase the size of the project. It’s good practice to always add your modules to the vendor option so it won’t get duplicated in your project. (Nuxt 2.0 no longer requires the vendor option. This can be removed.) Update the package.json file in the root /itunes-search folder with the new vendor options.

Listing 11.1. Removing ESLint from nuxt.config.js: chapter-11/itunes-search/nuxt.config.js
...
  build: {
    vendor: ['axios', 'vuetify']      1
  }
...

  • 1 Adds Axios and Vuetify to vendor bundle and removes linting.

Although we’ve added Axios and Vuetify as vendors, we’re not done. Vuetify requires a little more setup. We’ll need to add a plugins section to the nuxt.config.js file and add the plugin to the /plugins folder.

Plugins in Nuxt.js are a way of adding external modules to your application; they require a little more setup. Plugins run before the root Vue.js instance is instantiated. Unlike adding a vendor option, a corresponding file runs in the /plugins folder.

The official documentation for Vuetify recommends that we import Vuetify and tell Vue to use it as a plugin. We’ll add this code to our plugin file. Add a new file in the plugins folder, and name it vuetify.js. Inside the file, register Vuetify with Vue, as shown here.

Listing 11.2. Adding the Vuetify plugin: chapter-11/itunes-search/plugins/vuetify.js
import Vue from 'vue'
import Vuetify from 'vuetify'

Vue.use(Vuetify)               1

  • 1 Adds Vuetify to Vue app

Next, we’ll need to add a reference to the plugin in the nuxt.config.js. Open the nuxt.config.js file in the root of the app folder and add the plugins.

Listing 11.3. Adding plugins reference: chapter-11/itunes-search/nuxt.config.js
...
  plugins: ['~plugins/vuetify.js'],      1
...

  • 1 Notes the reference to the plugins file

The last thing we need to do to get Vuetify working is to add CSS. The official documentation recommends that you import the material design icons from Google and add a link to the Vuetify CSS file.

Remember earlier when we imported the stylus loader? Well now we can add a link to our own stylus file in the nuxt.config.js file. In the CSS block at the top, delete the main.css file, if it exists, and add a link to the app.styl file that we’ll create in a moment. Also, add a stylesheet in the head section for the Google material design icons. The completed nuxt.config.js file should look like this.

Listing 11.4. Adding CSS and fonts: chapter-11/itunes-search/nuxt.config.js
  module.exports = {
  /*
  ** Headers of the page
  */
  head: {
    title: 'iTunes Search App',
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
      { hid: 'description', name: 'description', content: 'iTunes search
      project' }
    ],
    link: [
      { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
      {rel: 'stylesheet', href: 'https://fonts.googleapis.com/
      css?family=Roboto:300,400,500,700|Material+Icons'}          1
    ]
  },
  plugins: ['~plugins/vuetify.js'],
  css: ['~assets/app.styl'],                                      2
  /*
  ** Customize the progress bar color
  */
  loading: { color: '#3B8070' },
  /*
  ** Build configuration
  */
  build: {
    vendor: ['axios', 'vuetify']
  }
}

  • 1 Adds a link to material design icons
  • 2 Removes main.css and adds in a link to app.styl

Now we need to create the assets/app.styl file, as shown in the following listing. This will import the Vuetify styles for the app.

Listing 11.5. Adding CSS stylus: chapter-11/itunes-search/assets/app.styl
// Import Vuetify styling
@require '~vuetify/src/stylus/main'       1

  • 1 Imports the main CSS

After this is done, run the npm run dev command and verify that you don’t see any errors in the console. If you do, open the nuxt.config.js file and check for any missing commas or typos. Also, make sure you’ve installed all the dependencies, including stylus and stylus-loader. Those must be installed for Vuetify to work.

11.2.3. Creating our building blocks and components

Components are our building blocks of our application. It’s where we can split our app into distinct parts that we can build back together. Before we build our routes, you may have noticed that there’s a components folder. This folder is where we can put all our normal, plain components.

Note

Nuxt.js gives us two different types of components. One is supercharged and the other is not. Supercharged components have access to special Nuxt-only configurations and are all located in the pages folder. These options let you access server-side data. The pages directory is also where we set up our routes and where our index component is located.

In this section, we’ll discuss using the components in the components folder. We’ll create two components for our iTunes search application: Card, which will hold the information for each artist album that we find, and Toolbar. The Toolbar component will create a simple toolbar that will be displayed at the top of each route. We’ll use Vuetify to help create both components. I’ll show you the HTML and CSS for these using Vuetify, but we won’t go into much detail.

Note

If you’d like to explore all the options of Vuetify, I recommend you read through the quickstart guide at https://vuetifyjs.com/vuetify/quick-start.

Create a file, Toolbar.vue, in the components folder. This file will hold our Toolbar template. Inside this template we’ll use several of Vuetify’s built-in components. We’ll also add scoped CSS to remove the text decorations on the links. When we’re done, the toolbar should look like figure 11.6.

Figure 11.6. iTunes search ToolBar.vue

In Vue.js, we normally use the route-link component to navigate inside the application, but this component doesn’t exist in Nuxt. To navigate between routes, we must use the nuxt-link component instead; it works exactly like the route-link. As you can see in listing 11.6, we’ll use the nuxt-link component to create a link to the root of the application whenever someone clicks the iTunes Search text at the top. Add this code to the Toolbar.vue file.

Listing 11.6. Adding the Toolbar component: chapter-11/itunes-search/components/Toolbar.vue
<template>
  <v-toolbar dark color="blue">                                   1
    <v-toolbar-side-icon></v-toolbar-side-icon>
    <v-toolbar-title class="white--text">
      <nuxt-link class="title" to="/">iTunes Search</nuxt-link>   2
    </v-toolbar-title>
    <v-spacer></v-spacer>
    <v-btn to="/" icon>
      <v-icon>refresh</v-icon>
    </v-btn>
  </v-toolbar>
</template>
<script>
</script>
<style scoped>                                                    3
.title {
  text-decoration: none !important;
}
.title:visited{
  color: white;
}
</style>

  • 1 Adds the v-toolbar Vuetify component
  • 2 The nuxt-link component navigates to “/”.
  • 3 Shows the scoped CSS for this component

The next component we need to create is the Card component. It will be used in the results route, and will display each album from the artist. Once again, we’ll use Vuetify to make this component look nice. When it’s all done, it should look like figure 11.7.

Figure 11.7. Card.vue component with example text

In addition to Vuetify, we’ll also use props. The results route will be responsible for accessing the API and retrieving the album information. We’ll then pass that information into the component using props. We’ll pass in the title, image, artistName, url, and color.

The v-card component accepts an href and a color attribute. We can use a v-on directive to bind our props to them. The v-card-media component accepts an img attribute. Our image prop will bind to it. Finally, the artistName and title will be displayed using a class. This will center the title and artist name in the card. Copy the code from this listing and create a file in the components folder named Card.vue.

Listing 11.7. Adding the Card component: chapter-11/itunes-search/components/Card.vue
<template>
  <div id="e3" style="max-width: 400px; margin: auto;"
  class="grey lighten-3">
        <v-container
        fluid
        style="min-height: 0;"
        grid-list-lg>
        <v-layout row wrap>
          <v-flex xs12>
            <v-card target="_blank"                            1
:href="url"
:color="color"
class="white--text">
              <v-container fluid grid-list-lg>
                <v-layout row>
                  <v-flex xs7>
                    <div>
                      <div class="headline">{{title}}</div>    2
                      <div>{{artistName}}</div>                3
                    </div>
                  </v-flex>
                  <v-flex xs5>
                    <v-card-media
                    :src="image"                               4
                    height="100px"
                    contain>
                  </v-card-media>
                </v-flex>
              </v-layout>
            </v-container>
          </v-card>
        </v-flex>
      </v-layout>
    </v-container>
  </div>
</template>
<script>
export default {
    props: ['title', 'image', 'artistName',                    5
'url', 'color'],
}
</script>

  • 1 The Vuetify v-card component accepts an href and color attribute.
  • 2 A div with a class of headline displays the title.
  • 3 A div shows the artist name
  • 4 The Vuetify v-card-media component that accepts an src image.
  • 5 A list of props being passed into the component.

These components—Toolbar and Card—will come in handy later when we put together our pages and default layout.

11.2.4. Updating the default layout

Now that we have our components in place, we need to update our default layout in the layouts folder. As the name suggests, the default layout is a component that wraps every page in the application. Inside each layout is a <nuxt/> component. This is the entry point for each page. The file default.vue implements the default layout. This can be overridden inside any page component. We’ll look at the page structure in the next section. Pages are components that have special properties, and they help define the routing of the application.

For our simple app, we’ll update the default.vue file and make several minor changes. We want to add the Toolbar.vue file to the top of every route, so we don’t have to keep adding it to every page in our app. All we have to do is add it once to the default layout, and then it will appear on every page of our application. Update the default.vue file and add a new section element with a class named container. Import the Toolbar component in the <script> and add it to components. Then add the new <ToolBar/> component above the <nuxt/> component, in the following listing. Update the default.vue file in the /layouts folder so it matches this.

Listing 11.8. Updating the default layout: chapter-11/itunes-search/layouts/default.vue
<template>
  <section class="container">                   1
    <div>
      <ToolBar/>                                2
      <nuxt/>
    </div>
  </section>
</template>

<script>
import ToolBar from '~/components/Toolbar.vue'; 3
export default {
  components: {
    ToolBar
  }
}
</script>
<style>
...

  • 1 Shows the container for the section that surrounds div
  • 2 Adds the Toolbar component to the template
  • 3 Imports the ToolBar component

Now that we have the layout in place, let’s move on to Vuex.

11.2.5. Adding a store using Vuex

The album information from the iTunes API will reside in the Vuex store. In Nuxt.js, Vuex stores can be accessed anywhere in the application including in the middleware. The middleware allows us to write code before the route loads. We’ll look at middleware in a later section.

Using Vuex, we’ll create a simple store. It will have one property in the state, called albums, and it will have a mutation called add. The add will take the payload and assign it to the state.albums in the store/index.js file, as you can see in the following listing. Create a file in the store folder named index.js. Add this code to it.

Listing 11.9. Adding a Vuex store: chapter-11/itunes-search/store/index.js
import Vuex from 'vuex'

const createStore = () => {
  return new Vuex.Store({
    state: {                        1
      albums: []
    },
    mutations: {
      add (state, payload) {        2
        state.albums = payload;
      }
    }
  })
}

export default createStore

  • 1 The albums property is the only state in the Vuex store.
  • 2 Adds mutation that adds the payload to albums

Now that we have the store in place, we can have our middleware make a call to our API and save it in the store.

11.2.6. Using middleware

Middleware is a term used in Node.js and Express to refer to a function that has access to a request object and a response object. In Nuxt.js, middleware is similar. It’s run on the server and client and can be set on any page in the application. It has access to both the request and response object and it’s run before the route is rendered.

Note

Middleware and asyncData, which we’ll learn more about later, are run on the server and the client. What this means is when a route loads for the first time the asyncData and middleware are run from the server. However, every subsequent time the route loads it’s run on the client. In some instances, you may want to run code purely on the server and not the client. This is where the serverMiddleware property comes in handy. This property is configured in the nuxt.config.js and can be used to run application code on the server. For more information on serverMiddleware check out the official guides at https://nuxtjs.org/api/configuration-servermiddleware/.

Middleware is created in the /middleware directory. Each middleware file has a function that has access to an object called context. Context has many different keys, including request, response, store, params, and environment. You can find the full listing of context object keys in the official documentation at https://nuxtjs.org/api/context.

In our application, we want to send the name of the artist in a route parameter. This is accessible by using the context.params object. We can use that parameter to construct a request to the iTunes search API and retrieve a list of albums. We can then take that list and assign it to our albums property inside our Vuex store.

To make a request to the server, we need to use a library that simplifies the process. Many libraries exist, but I like Axios, the HTTP library that we can use from the browser or Node.js to make HTTP requests. It transforms our JSON data automatically and it supports promises. To learn more about Axios, check out the official GitHub page at https://github.com/axios/axios.

Create a file named search.js in the middleware folder. Add the code in listing 11.10. This code makes an HTTP GET request to the iTunes API and passes in the params.id as the search term in the request. When the promise returns, it calls the add mutation using the store.commit function. You may have noticed that we’re using the ES6 destructuring for {params, store}. Instead of passing in the context, we can use destructuring to pull out the keys we need.

Listing 11.10. Setting up middleware: chapter-11/itunes-search/middleware/search.js
import axios from 'axios'

export default function ( {params, store} ) {              1
  return axios.get(`https://itunes.apple.com/
     search?term=${params.id}&entity=album`)
        .then((response) => {
            store.commit('add', response.data.results)     2
        });
}

  • 1 Shows the default function that has access to the store and params
  • 2 The response from the server request is added to the store.

We have everything in place now, so we can look at pages and the route.

11.2.7. Generating routes using Nuxt.js

Routing in Nuxt.js is a little different from what you see in a normal Vue.js application. You don’t have a VueRouter that you have to set up for all the routes. Instead, routes are derived by the file tree created under the pages directory.

Each directory is a route in your application. Each .vue file in that directory corresponds to the route as well. Let’s imagine you have a pages route and in that route you have a user route. To create these routes, your directory structure will look like figure 11.8.

Figure 11.8. Directory structure to create routes

The directory structure in the pages folder will automatically generate the route shown in this listing.

Listing 11.11. Automatically generated route structure
router: {
  routes: [
    {
      name: 'index',
      path: '/',
      component: 'pages/index.vue'           1
    },
    {
      name: 'user',
      path: '/user',
      component: 'pages/user/index.vue'
    },
    {
      name: 'user-one',
      path: '/user/one',
      component: 'pages/user/one.vue'
    }
  ]
}

  • 1 Notes the pages 3index route

This is a quick example of the type of routing you can do. You can find more information on routing from the official guides at https://nuxtjs.org/guide/routing.

For our app we’ll have something much simpler. We’ll only have two routes, and one of them will be dynamic. To define a dynamic route in Nuxt you must put an underscore before the name. As seen in figure 11.9 the root of the pages folder has an index.vue file. This is the root component and will be loaded when the application starts. You’ll also see a README.md file. This file can be deleted; it’s only there to remind you what should be inside the directory. The _id route is dynamic. The ID will match the artist name and will be passed into the route.

Figure 11.9. Directory structure of iTunes search app

Inside the pages folder create a results directory. Then open the index.vue file. Delete everything and add the fairly simple code from listing 11.12. We have a template at the top with an <h1> tag and a <form> element. A v-on directive is attached to the submit event on the form. We’ll also use the event modifier prevent to stop the form from submitting.

Inside the submit method, we’ll use this.$router.push. This will route the application over to the results/ page. We’ll pass the search results into the route as a parameter. Because we set up the dynamic _id route, the search results will show up as part of the URL. For example, if we search for Taylor Swift, the URL will be /results/taylor%20swift. Don’t worry about the %20, this is added in automatically and represents a space character.

At the bottom of the page component, add a style tag, as shown in this listing. This will center the text and add a little padding.

Listing 11.12. Creating the index page: chapter-11/itunes-search/pages/index.vue
<template>
  <div>
    <h1>Search iTunes</h1>
    <br/>
    <form @submit.prevent="submit">                    1
      <input placeholder="Enter Artist Name"
  v-model="search"
  ref='search' autofocus   />
    </form>
  </div>
</template>
<script>
export default {
  data() {
    return {
      search: ''
    }
  },
  methods: {
      submit(event) {
        this.$router.push(`results/${this.search}`);   2

      }
  }
}
</script>

<style>                                                3
* {
  text-align: center;
}

h1 {
  padding: 20px;
}
</style>

  • 1 Shows the form element with a v-on directive that triggers the submit method on submit
  • 2 Routes the app to the results page
  • 3 Centers and adds padding to the page.

The final piece to this app is the _id page that will display a card for each album in the result. It will also alternate the colors between blue and red on each card.

Earlier in this chapter, I mentioned that pages are supercharged components. In other words, they have certain Nuxt-only options available to them. These options include fetch, scrollToTop, head, transition, layout, and validate. We’ll look at two other options called asyncData and middleware. If you want to learn more about Nuxt options, check out the official documentation at https://nuxtjs.org/guide/views.

The middleware option allows us to define the middleware we want to use in our page. This middleware will run each time the component is loaded. You can see in listing 11.13 that the _id.vue file is using the middleware search we created earlier.

The other option is called asyncData. This is helpful because it allows us to retrieve data and pre-render it on the server without using a store. As you saw in the middleware section, we had to use the Vuex store to save our data so it could be accessed by our components. When using asyncData, you don’t have to do this. Let’s look first at how to access the data using middleware. Then we’ll refactor to use asyncData.

Create a file in the pages/results folder called _id.vue. Inside that new component, add a v-if directive for albumData. This will guarantee the album data is loaded before being displayed. Next, create a v-for directive that iterates through the albumData.

On each iteration, we’ll show a card and pass into it the album data for title, image, artistName, url, and color. The color will be calculated by a method called picker. It will alternate between red and blue based on the index value.

At the top of the file, we’ll access the {{$route.params.id}}. This is the parameter that’s passed in from the search results.

As you can see in the next listing, we’ll add a computed property called albumData. This will retrieve the data from the store. The store is populated by the middleware search that’s triggered as soon as the route loads, as shown here.

Listing 11.13. Creating our dynamic route: chapter-11/itunes-search/pages/results/_id.vue
<template>
  <div>
    <h1>Results for {{$route.params.id}}</h1>           1
    <div v-if="albumData">                              2
      <div v-for="(album, index) in albumData">         3
          <Card :title="album.collectionCensoredName"   4
                :image="album.artworkUrl60"
                :artistName="album.artistName"
                :url="album.artistViewUrl"
                :color="picker(index)"/>
      </div>
    </div>
  </div>
</template>
<script>
import axios from 'axios';
import Card from '~/components/Card.vue'
export default {
    components: {
      Card
    },
    methods: {
      picker(index) {                                   5
          return index % 2 == 0 ? 'red' : 'blue'
      }
    },
    computed: {                                       6
      albumData(){
        return this.$store.state.albums;
      }
    },
    middleware: 'search'                              7
}
</script>

  • 1 The message displays the route parameter passed in from the search.
  • 2 The v-if directive that will only display if albumData is present.
  • 3 The v-for directive that iterates through the albumData.
  • 4 The Card component that’s passed-in album information.
  • 5 The picker method that returns red and blue alternately.
  • 6 The computed property that returns the store property for albums.
  • 7 Specifies which middleware to run for this route

Run the command npm run dev and open a web browser on localhost port 3000. If you already have it running, make sure to close and restart. You should see the iTunes search app open. If not, look in the console for the error. Sometimes it’s as simple as a typo in the component name.

Let’s do one more modification of our app. Like I said earlier, we have access to something called asyncData. This option is used to load data server side, on the initial load of the component. It’s similar to using a middleware, because we’ll have access to the context.

When using asyncData, be cautious. You will not have access to the component through this option because it’s called before the component is initiated. However, it will merge the data you retrieve with the component so you don’t have to use Vuex. You can find more information on asyncData from the official documentation at https://nuxtjs.org/guide/async-data.

Open the _id.vue file again and delete the albumData computed property. We won’t use it. Instead create an asyncData option, shown in listing 11.14. Inside that option, we’ll do an HTTP GET request using Axios. Similar to middleware, asyncData also has access to the context object. We’ll use ES6 destructuring to retrieve the params, and then use them in the iTunes API call. In the response, we’ll set the albumData object. This object will be available to us after the component is initialized, as shown in listing 11.14.

Listing 11.14. The asyncData example: chapter-11/itunes-search/pages/results/_id.vue
...
    asyncData ({ params }) {                               1
      return axios.get(`https://itunes.apple.com/
     search?term=${params.id}&entity=album`)
        .then((response) => {                              2
            return {albumData: response.data.results}      3
        });
    },
...

  • 1 The asyncData option has access to the params key.
  • 2 The iTunes response appears after passing in the params.id to the axios.get command.
  • 3 This will return a new albumData property that can be accessed in the component.

That should be it for asyncData. Save the file and run the npm run dev command again. You should see the page as it was before. As you can see, we have the same results, but we don’t have to use the Vuex store.

11.3. Communicating with a server using Firebase and VuexFire

Firebase is a Google product that helps you create apps quickly for mobile and desktop. It offers several services, including analytics, databases, messaging, crash reporting, cloud storage, hosting, and authentication. Firebase scales automatically and is easy to get up and running. You can find more information about all the Firebase services at the official home page at https://firebase.google.com/.

For our example in this section, we’ll use two of these services: authentication and the Realtime database. We’re going to take our existing pet store application and modify it to include these services.

Let’s imagine we were told that we needed to host our products for our pet store app in the cloud and add authentication. Recall from the previous chapter that our pet store application uses a flat file, products.json. We’ll need to move the products.json contents to Firebase’s Realtime database. We’ll then modify our application so it pulls from Firebase instead of the flat file.

Another important aspect is to add simple authentication using one of Firebase’s built-in cloud providers. We’ll make a new button in our header to sign in and sign out, and we’ll see how we can save our session data into our Vuex store. When all is done, our app will look like figure 11.10.

Figure 11.10. Completed pet store application using Firebase

11.3.1. Setting up Firebase

If you have a Google account, you can go to http://firebase.google.com and log in. If you don’t have a Google account, head over to http://accounts.google.com and create one; it’s free. (Firebase is free for a certain number of transactions a month; after that you’ll have to pay.)

After logging in, you’ll be presented with the Welcome to Firebase page. You’ll then have an opportunity to create a project, as seen in figure 11.11.

Figure 11.11. Creating a Firebase project

After clicking Add project, you’ll need to type in a project name and country region. Click Create Project and you’ll see the Firebase console. This is where we’ll set up our database, authentication, and retrieve the keys we need to get started.

Click Database on the left side. You should see two options: Realtime Database and Cloud Firestore. We’re going to use the Realtime Database. Click Get Started (figure 11.12).

Figure 11.12. Database selection

At this point, we’ll add the products.json file into the Firebase database. We could import the JSON file, but we’ll add it manually so we can understand how everything works. Click the plus (+) symbol next to the name you gave the database. Add a Products child. Before clicking Add, click the plus symbol again. This will create another child. In the Name box, add a number. Click the plus symbol again and create seven children. These will be title, description, price, image, availableInventory, id, and rating. Fill in the information and repeat the process for another product. After you’re done, it should look like figure 11.13.

Figure 11.13. Firebase Realtime Database setup

Click Add and you’ll see the two products in the database. Repeat this process and add a few more into it if you like.

After this is done, we’ll need to set up authentication. Click Authentication in the console on the left side. You’ll see a window that has a button for SET UP SIGN-IN METHOD. Click that button, as you see in figure 11.14.

Figure 11.14. Setting up authentication

On the next page, choose Google. We’ll be using this for our authentication in our app. We could as easily set up Facebook or Twitter, but for this example we’ll assume that anyone who wants to log in to our app must also have a Google account. In the setup window, slide the Enable button and save your work, as shown in figure 11.15. That should be it; that will enable us to sign in with Google.

Figure 11.15. Enable Google sign-in.

Finally, we need to grab configuration information. Head back to the project overview console page by clicking Project Overview on the left side. You’ll see an Add Firebase to your web app button. Click this button and a window will open with your Firebase keys and initialization information. Record this information for later; we’ll need it when setting up Firebase in our app.

11.3.2. Setting up our pet store app with Firebase

Now that we’ve set up Firebase, we need to update our pet store app to use it. The last time we used our pet store was in chapter 10, when we added in Vuex. Copy the pet store app from the previous chapter or download the code for the chapter. We’ll use this code as a starting point.

For Firebase to work correctly with Vue, we’ll need to use a library called VueFire. This will help us communicate with Firebase and set up the bindings we need. You can find more information on VueFire on their official GitHub page at https://github.com/vuejs/vuefire.

Open your console and change directories to the location of your pet store app. Install VueFire and Firebase with the following commands:

$ cd petstore
$ npm install firebase vuefire –save

This will install and save all the dependencies we need.

Create a file named firebase.js in the src folder in the root of the pet store app. Remember when you copied the initialization information from the Firebase console? We’ll need that now. At the top of the file, write import {initializeApp} from Firebase. After the import, create a const variable named app and paste in the initialization information you recorded earlier.

Create two exports, one called db and the other called productsRef. This will allow us to connect to the Firebase database and retrieve the product information we created earlier. If you’d like more information on the Firebase API, check the official API documentation at https://firebase.google.com/docs/reference/js/. Copy the code from listing 11.15 into the src/firebase.js file.

Listing 11.15. Setting up Firebase and initializing files: chapter-11/petstore/src/firebase.js
import { initializeApp } from 'firebase';            1

const app = initializeApp({                          2
    apiKey: "<API KEY>",
    authDomain: "<AUTH DOMAIN>",
    databaseURL: "<DATABASE URL>",
    projectId: "<PROJECT ID>",
    storageBucket: "<STORAGE BUCKET>",
    messagingSenderId: "<SENDER ID>"
});

export const db = app.database();                    3

export const productsRef = db.ref('products');       4

  • 1 Imports initializeApp into the file
  • 2 Shows the keys for Firebase received from the Firebase console
  • 3 Uses ES6 export for the database
  • 4 Uses ES6 export for the products reference

We now need to set up our main.js file so it can see the VueFire library we installed earlier. We’ll also need to make sure we import Firebase and the firebase.js we created earlier. The Vue.use(VueFire) line will set VueFire as a plugin for the app. This is required by the VueFire installation. Update the src/main.js file with this code.

Listing 11.16. Setting up main file: chapter-11/petstore/src/main.js
import Vue from 'vue'
import App from './App'
import router from './router'
require('./assets/app.css')
import { store } from './store/store';
import firebase from 'firebase';         1
import './firebase';                     2
import VueFire from 'vuefire';           3

Vue.use(VueFire);                        4
Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  store,
  template: '<App/>',
  components: { App }
})

  • 1 Imports Firebase into the app
  • 2 Imports the firebase.js file.
  • 3 Imports vuefire.
  • 4 Sets up vuefire as a plugin.

It’s a good idea at this point to make sure we don’t have any errors. Save all your files and run the npm run dev command in your console. This will start your server on localhost. Make sure you don’t see any errors in the console. It’s easy to forget an import, so make sure you didn’t forget any inside the main.js file. Because we have everything set up, let’s look at how to set up authentication in our app.

11.3.3. Updating Vuex with authentication state

Earlier I mentioned that we’ll use authentication in our app. To save this information, we’ll need to update the Vuex store. To make things easy, we’ll create a state property called session. After a user is authenticated, Firebase returns a user object which holds session information. It’s a good practice to save that information so it’s available anywhere in the app.

Open the store/modules/products.js file, and add a new session property in state. The same way we did in the last chapter, we’ll add in a getter and a mutation. We’ll name the mutation SET_SESSION. Update the store/modules/products.js file so it matches this listing.

Listing 11.17. Updating Vuex: chapter-11/petstore/store/modules/products.js
const state = {
    products: {},
    session: false                             1
};

const getters = {
    products: state => state.products,
    session: state => state.session            2
};

const actions = {
    initStore: ({commit}) => {
      axios.get('static/products.json')
      .then((response) =>{
        console.log(response.data.products);
        commit('SET_STORE', response.data.products )
      });
    }
};

const mutations = {
    'SET_STORE' (state, products) {
      state.products = products;
    },
    'SET_SESSION' (state, session) {           3
      state.session = session;
    }
};

export default {
    state,
    getters,
    actions,
    mutations,
}

  • 1 The session state property defaults to false.
  • 2 The getter for the session
  • 3 The mutation called SET_SESSION sets session data.

Now that we have a place to set the session data in Vuex, we can add the code to retrieve it from Firebase.

11.3.4. Updating the header component with authentication

Inside the header, we display the site name and Checkout button. Let’s update the header so it shows a Sign In and Sign Out button.

When the header is complete, it will look like figure 11.16 after someone is signed in. Notice how in figure 11.16 a picture is shown next to the Sign Out text. This is retrieved from the user object from Firebase.

Figure 11.16. User is signed in.

After the user signs out, the button changes to Sign In, as figure 11.17 shows.

Figure 11.17. User is signed out.

Open the src/components/Header.vue file. In this file, we’ll update the template with the new buttons. We’ll also need to add two new methods for signing in and out. Under the navbar-header, add a new div section for the sign in (see listing 11.18). Below that, add another div section for sign out. Inside the sign out div, we’ll also add an image that will be retrieved from the mySession property.

Surrounded by both divs will be a v-if directive. If the mySession property is false it will show the Sign In button. We’ll use a v-else directive to show the Sign Out button if mySession is true. If the session is signed in, we’ll see a Sign Out button; if the session is signed out, we’ll see a Sign In button.

Because the code for the header component is so large, I’ve broken it into three listings (listing 11.18, 11.19, and 11.20). Make sure to take each of these listings and combine them. Take the combined code from the listing and overwrite the file for src/components/Header.vue, as shown next.

Listing 11.18. Updating header component: chapter-11/header-temp.html
<template>
  <header>
    <div class="navbar navbar-default">
      <div class="navbar-header">
        <h1><router-link :to="{name: 'iMain'}">
{{ sitename }}
</router-link></h1>
      </div>
      <div class="nav navbar-nav navbar-right cart">
        <div v-if="!mySession">                         1
          <button type="button"
class="btn btn-default btn-lg"
v-on:click="signIn">                                    2
            Sign In
          </button>
        </div>
        <div v-else>                                    3
          <button type="button"
class="btn btn-default btn-lg"
v-on:click="signOut">                                   4
            <img class="photo"
:src="mySession.photoURL" />
            Sign Out
          </button>
        </div>
      </div>
      <div class="nav navbar-nav navbar-right cart">
         <router-link
active-class="active"
tag="button"
class="btn btn-default btn-lg"
:to="{name: 'Form'}">
          <span class="glyphicon glyphicon-shopping-cart">
{{cartItemCount}}
             </span>
Checkout
        </router-link>
      </div>
    </div>
  </header>
</template>

  • 1 The mySession property will display the Sign In button if it’s false.
  • 2 Shows the button with the v-on directive for sign in
  • 3 If the mySession property is true, the Sign Out button will be displayed.
  • 4 Displays an image from mySession

In the template, we created two methods, signIn and signOut. We also created a new property called mySession. Let’s go ahead and create the script section of our component with these new methods and a computed property. Make sure to import firebase from 'firebase' at the top of the script (listing 11.19).

The first thing we need to do is add a lifecycle hook called beforeCreate. This hook fires before the component is created. In this hook, we want to set our Vuex store with the current session. Firebase conveniently has an observer that will do this called onAuthStateChanged. This observer is triggered whenever a user signs in or out. We can use this to update our store with the session information using SET_STORE. For more information on onAuthStateChanged, check out the official documentation at http://mng.bz/4F31.

Now that we can track when a user signs in and out, we can create those methods. Create a method named signIn. Inside that method create a provider firebase.auth.GoogleAuthProvider(). Pass that provider into firebase.auth().signInWithPopup. This will create a popup asking the user to sign into their Google account. The signInWithPopup will create a promise. If the login is successful, we display “signed in” in the console. If it isn’t successful, we see “error” in the console.

Remember, because we set up an observer for onAuthStateChanged inside the beforeCreate hook, we don’t have to set up any other variable after a user logs in. The observer will update the store automatically after we sign in or sign out.

The signOut method works the same way. When the user signs out, a message “signed out” shows in the console. If there is an error, “error in sign out!” is displayed.

For our computed property mySession, we’ll return the Vuex getter for session. If the session doesn’t exist, it will be set to false. It’s worth mentioning that we could have used mapGetters with Vuex. This would automatically map the getters session with the name session in our component. However, because we’re dealing with one getter, I decided to return this.$store.getters.session instead.

Copy the code from the following listing and add it to the bottom of the new combined file that you’ll be using for src/components/Header.vue.

Listing 11.19. Updating header component 2: chapter-11/header-script.js
<script>
import firebase from 'firebase';
export default {
  name: 'Header',
  data () {
    return {
      sitename: "Vue.js Pet Depot"
    }
  },
  props: ['cartItemCount'],
  beforeCreate() {
      firebase.auth().onAuthStateChanged((user)=> {                 1
          this.$store.commit('SET_SESSION', user || false)
      });
  },
  methods: {
    showCheckout() {
      this.$router.push({name: 'Form'});
    },
    signIn() {                                                      2
      let provider = new firebase.auth.GoogleAuthProvider();
      firebase.auth().signInWithPopup(provider).then(function(result) {
        console.log('signed in!');
      }).catch(function(error){
        console.log('error ' + error)
      });
    },
    signOut() {                                                     3
      firebase.auth().signOut().then(function() {
        // Sign-out successful.
        console.log("signed out!")
      }).catch(function(error) {
        console.log("error in sign out!")
        // An error happened.
      });
    }
  },
  computed: {
    mySession() {                                                   4
      return this.$store.getters.session;
    }
  }
}
</script>

  • 1 The onAuthStateChanged observer is set inside the beforeCreate hook.
  • 2 The signIn method signs the user in.
  • 3 The signOut method signs the user out.
  • 4 The mySession computed property gets the session information.

Finally, we’ll need to add a new photo class to our CSS that will size the photo in our button. Take the code from the following listing and combine it with the previous listings to create the new Header.vue file in the src/components folder.

Listing 11.20. Updating header styles: chapter-11/header-style.html
<style scoped>
a {
  text-decoration: none;
  color: black;
}

.photo {               1

  width: 25px;
  height: 25px;
}

.router-link-exact-active {
  color: black;
}
</style>

  • 1 The photo class that sets the width and height of image

After adding all the code for the new Header.vue file, make sure to run the npm run dev command and check for errors. It’s easy to make a mistake on the onAuthStateChanged observer and not commit it to the Vuex store. Look out for that.

11.3.5. Updating Main.vue to use Firebase Realtime database

With all the authentication out of the way, let’s start retrieving information from the database. By default, we left the database configuration in Firebase to read only. That will work for us.

First, update the mapGetters in the src/components/Main.vue file. You’ll notice that we’re retrieving the products getter. Remove that and add the session getter. We won’t use this now, but it’s nice to know we can use the session inside the main component.

To use the Realtime database with Firebase, all we need to do is import the productsRef from the firebase.js file. Then we’ll need to create a Firebase object that maps productsRef to products. That should be it! All the other code in our Main.vue file can remain the same. Take the code in this listing and update the src/components/Main.vue file.

Listing 11.21. Updating Main.vue file: chapter-11/update-main.js
...
import { productsRef } from '../firebase';    1
export default {
  name: 'imain',
  firebase: {                                 2
      products: productsRef
  },
...
  computed: {
    ...mapGetters([
        'session'                             3
    ])
...

  • 1 Imports the productsRef from firebase/.js file
  • 2 Maps productsRef to products
  • 3 Updates mapGetters that only retrieves session not products

Save all the files and run npm run dev. Inside the browser, you’ll notice a slight delay before the products show up. This indicates the products are being downloaded from Firebase. You could always go into Firebase and add a new product, and it should show up on your list of products.

One thing you may wonder is what we could do next. With the session property, we could set up different sections of the app that are accessible only if a user is logged in. We could do this with the v-if directive or through the router. With the router we can add a meta tag to a route. Then we could use the router.beforeEach to navigate to certain routes if a user is logged in. This concept is called navigation guards. You can read up on the navigation guards section in the official documentation at https://router.vuejs.org/guide/advanced/navigation-guards.html. In the next chapter, we’ll look at testing and how we can use it to make sure our app is doing what we expect.

Exercise

Use your knowledge from this chapter to answer the following question:

  • What’s one advantage of using asycData in your Nuxt apps versus using middleware?

See the solution in appendix B.

Summary

  • You can use libraries like Axios to talk to web APIs.
  • Fast-loading sites can be created with server-side rendered Nuxt.js apps.
  • You can grab information from an online datastore using Firebase.
  • Users can be authenticated inside your application.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset