Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Creating a Real-Time Data Application Using Vue.js

DZone's Guide to

Creating a Real-Time Data Application Using Vue.js

In this article, we will show you how to add real-time functionality to your Vue.js-based web application using Pusher. Let's get to it!

· Web Dev Zone ·
Free Resource

Jumpstart your Angular applications with Indigo.Design, a unified platform for visual design, UX prototyping, code generation, and app development.

Vue.js is gaining popularity in the JavaScript community because it is simple and easy to get started with. Vue.js is powerful and with a few lines of code you can achieve a lot.

Pusher makes building real-time applications super easy, and we can easily plug it into a Vue.js app to give our users a great experience.

Vue.js is among the top frameworks for web application development and also one of the most famous JavaScript frameworks. Let us create a real-time data application using Vue.JS.

With this post, we will go through how to add real-time functionality using Pusher in Vue.js 2.0. The sample application can be called samplevue.

Image title

A: Setting Up Vue-CLI

Vue-CLI is a great command line tool for Vue.js projects, therefore without spending too much time on configuration, we can simply dive into writing code.

Install vue-cli onto your system with this command-line:

npm install -g vue-cli

With webpack template, create a project and install the dependencies with the set of commands mentioned below:

vue init webpack samplevue
cd samplevue
npm install

Note: Webpack helps you to do a bunch of things like converting ES6 code to ES5 and parsing Vue single file components, therefore, you do not have to worry about browser compatibility.

For running the app:

npm run dev

B: Start Creating a Movie Review App

Next, we will get started on creating the components of the app.

touch ./src/components/Movie.vue
touch ./src/components/Reviews.vue

Some of the important things to know are that the thing which makes Vue.js very powerful are the components which are more like modern JavaScript frameworks. These series of components help to make different parts of the application reusable.

B1: Searching for and Retrieving a Movie

For reviewing a movie, we will be creating a simple form which will be used to fetch a movie from the Netflix Roulette public database API:

<!-- ./src/components/Movie.vue -->
<template>
  <div class="container">
    <div class="row">
      <form @submit.prevent="fetchMovie()">
        <div class="columns large-8">
          <input type="text" v-model="title">
        </div>
        <div class="columns large-4">
          <button type="submit" :disabled="!title" class="button expanded">
            Search titles
          </button>
        </div>
      </form>
    </div>
    <!-- /search form row -->
  </div>
  <!-- /container -->
</template>

We will create a form in the above code and will specify a custom fetchMovie() event handler on form submit.

The @submit directive is shorthand for v-on:submit. It is used to listen to DOM events and run actions or handlers whenever they’re triggered. The .prevent  modifier helps in writing  event.preventDefault() into the handler logic.

For binding the value of text input to the title, we use the v-model directive. Finally, we can bind the disabled attribute of the button so that it can be set to true if the title is absent and vice versa. Also, note :disabled is shorthand for v-bind:disabled.

Now let us define the methods and data values for the component.

<!-- ./src/components/Movie.vue -->
<script>
// define the external API URL
const API_URL = 'https://netflixroulette.net/api/api.php'
// Helper function to help build urls to fetch movie details from title
function buildUrl (title) {
  return `${API_URL}?title=${title}`
}

export default {
  name: 'movie', // component name
  data () {
    return {
      title: '',
      error_message: '',
      loading: false, // to track when app is retrieving data
      movie: {}
    }
  },
  methods: {
    fetchMovie () {
      let title = this.title
      if (!title) {
        alert('please enter a title to search for')
        return
      }
      this.loading = true 
      fetch(buildUrl(title))
      .then(response => response.json())
      .then(data => {
        this.loading = false
        this.error_message = ''
        if (data.errorcode) {
          this.error_message = `Sorry, movie with title '${title}' not found. Try searching for "Fairy tail" or "The boondocks" instead.`
          return
        }
        this.movie = data
      }).catch((e) => {
        console.log(e)
      })
    }
  }
}
</script>

Once we have defined the external URL we want to query to get the movies, we have to specify the key Vue options which we may be required for the components:

  • data: it specifies properties which we may require in our component.

  • methods: it specifies methods which we will be using in the component. As of now, only one method is defined to retrieve the movies — fetchMovie().

After this, we have to add code for displaying the movie and showing a notice whenever a movie title is not found in the <template>:

<!-- ./src/components/Movie.vue -->
<template>
<!-- // ... -->
<div v-if="loading" class="loader">
  <img src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/0.16.1/images/loader-large.gif" alt="loader">
</div>

<div v-else-if="error_message">
  <h3>{{ error_message }}</h3>
</div>

<div class="row" v-else-if="Object.keys(movie).length !== 0" id="movie">
  <div class="columns large-7">
    <h4> {{ movie.show_title }}</h4>
    <img :src="movie.poster" :alt="movie.show_title">
  </div>
  <div class="columns large-5">
    <p>{{ movie.summary }}</p>
    <small><strong>Cast:</strong> {{ movie.show_cast }}</small>
  </div>
</div>
</template>

We can also add some styling at the bottom of the component which is optional.

<!-- ./src/components/Movie.vue -->
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
#movie {
  margin: 30px 0;
}
.loader {
  text-align: center;
}
</style>

B2: Retrieving and Writing Movie Reviews

Edit the review component, which contains the logic and view for reviews, using the same Single File Component approach.

Use a v-for directive to loop through the available reviews for a movie. Then display it in the template:

<!-- ./src/components/Review.vue -->
<template>
  <div class="container">
    <h4 class="uppercase">reviews</h4>
    <div class="review" v-for="review in reviews">
      <p>{{ review.content }}</p>
      <div class="row">
        <div class="columns medium-7">
          <h5>{{ review.reviewer }}</h5>
        </div>
        <div class="columns medium-5">
          <h5 class="pull-right">{{ review.time }}</h5>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
const MOCK_REVIEWS = [
  {
    movie_id: 7128,
    content: 'Great show! I loved every single scene. Defintiely a must watch!',
    reviewer: 'Jane Doe',
    time: new Date().toLocaleDateString()
  }
]
export default {
  name: 'reviews',
  data () {
    return {
      mockReviews: MOCK_REVIEWS,
      movie: null,
      review: {
        content: '',
        reviewer: ''
      }
    }
  },
  computed: {
    reviews () {
      return this.mockReviews.filter(review => {
        return review.movie_id === this.movie
      })
    }
  }
}
</script>

Create MOCK_REVIEWS to mock the available reviews. Use a computed property to filter out the reviews for a particular movie.

Now, add a form and method for adding a new review:

<!-- ./src/components/Review.vue -->
<template>
  <div class="container">
    <!-- //... -->
    <div class="review-form" v-if="movie">
      <h5>add new review.</h5>
      <form @submit.prevent="addReview">
        <label>
          Review
          <textarea v-model="review.content" cols="30" rows="5"></textarea>
        </label>
        <label>
          Name
          <input v-model="review.reviewer" type="text">
        </label>
        <button :disabled="!review.reviewer || !review.content" type="submit" class="button expanded">Submit</button>
      </form>
    </div>
    <!-- //... -->   
  </div> 
</template>

<script>
export default {
  // ..
  methods: {
    addReview () {
      if (!this.movie || !this.review.reviewer || !this.review.content) {
        return
      }
      let review = {
        movie_id: this.movie, 
        content: this.review.content, 
        reviewer: this.review.reviewer, 
        time: new Date().toLocaleDateString()
      }
      this.mockReviews.unshift(review)
    }
  },
  //...
}
</script>

Again, we can add some optional styling at the bottom of the component:

<!-- ./src/components/Review.vue -->
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
  .container {
    padding: 0 20px;
  }
  .review {
    border:1px solid #ddd;
    font-size: 0.95em;
    padding: 10px;
    margin: 15px 0 5px 0;
  }
  .review h5 {
    text-transform: uppercase;
    font-weight: bolder;
    font-size: 0.7em
  }
  .pull-right {
    float: right;
  }
  .review-form {
    margin-top: 30px;
    border-top: 1px solid #ddd;
    padding: 15px 0 0 0;
  }
</style>

From the Movie component, use the movie identifier in order to fetch and post reviews.

B3: Component-to-Component Communication

We can also create a new Vue instance and use it as a message bus. A message bus is an object on whose components can emit and listen to events.

Message bus creation:

touch ./src/bus.js

// ./src/bus.js
import Vue from 'vue'
const bus = new Vue()

export default bus

For emitting an event once a movie is found, update the fetchMovies() method:

<!-- ./src/components/Movie.vue -->
import bus from '../bus'

export default {
  // ...
  methods: {
    fetchMovie (title) {
      this.loading = true
      fetch(buildUrl(title))
      .then(response => response.json())
      .then(data => {
        this.loading = false
        this.error_message = ''
        bus.$emit('new_movie', data.unit) // emit `new_movie` event
        if (data.errorcode) {
          this.error_message = `Sorry, movie with title '${title}' not found. Try searching for "Fairy tail" or "The boondocks" instead.`
          return
        }
        this.movie = data
      }).catch(e => { console.log(e) })
    }
  }
}

In the created hook, listen for the event in the Review component.

<!-- ./src/components/Review.vue -->
<script>
import bus from '../bus'
export default {
  // ...
  created () {
    bus.$on('new_movie', movieId => {
      this.movie = movieId
    })
  },
  // ...
}
</script>

In the mentioned code, we have specified that whenever a new_movie event is fired, we set the movie property to be the value of the movieId which is broadcasted by the event.

So, for completing the base application, register components in App.vue, and display the templates:

<!-- ./src/App.vue -->
<template>
  <div id="app">
    <div class="container">
      <div class="heading">
        <h2>samplevue.</h2>
        <h6 class="subheader">realtime movie reviews with Vue.js and Pusher.</h6>
      </div>
      <div class="row">
        <div class="columns small-7">
          <movie></movie>
        </div>
        <div class="columns small-5">
          <reviews></reviews>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
  import Movie from './components/Movie'
  import Reviews from './components/Reviews'

  export default {
    name: 'app',
    components: {
      Movie, Reviews
    }
  }
</script>

<style>
  #app .heading {
    font-family: 'Avenir', Helvetica, Arial, sans-serif;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    text-align: center;
    color: #2c3e50;
    margin: 60px 0 30px;
    border-bottom: 1px solid #eee;
  }
</style>

Now the run the application and check the basic functionalities of retrieving movies and adding reviews.

npm run dev

Note: For retrieving movies from te public API, the movie titles must be typed in full.

C: Adding Real-Time Updates With Pusher

Now we will look at how to add real-time functionality to our application. Adding real-time functionality will help in getting updated in real-time to all the users viewing that movie, whenever any review is added.

Set up a simple backend where you can process post requests with new reviews, and, using Pusher, you can broadcast an event whenever a review is added.

C1: Pusher Setup

Register for a free account. Create an application on the dashboard and then copy the app credentials. This is quite an easy task.

C2: Backend Setup and Broadcasting an Event

Let’s build simple server with Node.js. Add some dependencies which you will be needing to package.json and pull them in:

npm install -S express body-parser pusher

Create a server.js file, where we build an Express app:

// ./server.js
/*
 * Initialise Express
 */
const express = require('express');
const path = require('path');
const app = express();
const bodyParser = require('body-parser');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.static(path.join(__dirname)));

/*
 * Initialise Pusher
 */
const Pusher = require('pusher');
const pusher = new Pusher({
  appId:'YOUR_PUSHER_APP_ID',
  key:'YOUR_PUSHER_APP_KEY',
  secret:'YOUR_PUSHER_SECRET',
  cluster:'YOUR_CLUSTER'
});

/*
 * Define post route for creating new reviews
 */
app.post('/review', (req, res) => {
  pusher.trigger('reviews', 'review_added', {review: req.body});
  res.status(200).send();
});

/*
 * Run app
 */
const port = 5000;
app.listen(port, () => { console.log(`App listening on port ${port}!`)});

Initialize an express app and then initialize Pusher with the desired credentials. Replace YOUR_PUSHER_APP_ID, YOUR_PUSHER_APP_KEY,YOUR_PUSHER_SECRET and YOUR_CLUSTER with actual details from the Pusher dashboard.

Define a route for creating reviews: /review. At this endpoint, utilize Pusher to trigger a review_added event on the reviews channel. Then broadcast the entire payload as the review.

trigger method syntax: pusher.trigger(channels, event, data, socketId, callback);.

C3: Creating an API Proxy

We can create a proxy in config/index.js, in order to access our API server from the front-end server created by Vue Webpack. Then we can run the dev server and the API backend simultaneously.

// config/index.js
module.exports = {
  // ...
  dev: {
    // ...
    proxyTable: {
        '/api': {
        target: 'http://localhost:5000', // you should change this, depending on the port your server is running on
        changeOrigin: true,
        pathRewrite: {
          '^/api': ''
        }
      }
    },
    // ...
  }
}

Adjust our addReview method to post to the API in /src/components/Reviews.vue:

<!-- ./src/components/Review.vue -->
<script>
// ...
export default {
  // ...
  methods: {
    addReview () {
      if (!this.movie || !this.review.reviewer || !this.review.content) {
        alert('please make sure all fields are not empty')
        return
      }
      let review = {
        movie_id: this.movie, content: this.review.content, reviewer: this.review.reviewer, time: new Date().toLocaleDateString()
      }
      fetch('/api/review', {
        method: 'post',
        body: JSON.stringify(review)
      }).then(() => {
        this.review.content = this.review.reviewer = ''
      })
    }
    // ...
  },
  // ...
}
</script>

C4: Listening For Events

Whenever a new review is published, listen for events broadcast by Pusher, and update it with details. Add the pusher-js library:

npm install -S pusher-js

Updating Review.vue:

<!-- ./src/components/Review.vue -->
<script>
import Pusher from 'pusher-js' // import Pusher

export default {
  // ...
  created () {
    // ...
    this.subscribe()
  },
  methods: {
    // ...
    subscribe () {
      let pusher = new Pusher('YOUR_PUSHER_APP_KEY', { cluster: 'YOUR_CLUSTER' })
      pusher.subscribe('reviews')
      pusher.bind('review_added', data => {
        this.mockReviews.unshift(data.review)
      })
    }
  },
  // ...
}
</script>

As mentioned above, import the Pusher object from the pusher-js library. Now, create a subscribe method which performs the below mention tasks:

  • Subscribe to reviews channel with pusher.subscribe('reviews').

  • Listens for the review_added event, with pusher.bind, which receives a callback function as its second argument. It triggers the callback function with the data broadcast as a function parameter, whenever it receives a broadcast.

D: Combining Everything Together

Add node server.js to the application’s development or start script so the API server starts along with the server provided by the webpack template: 

{
  // ...
  "scripts": {
    "dev": "node server.js & node build/dev-server.js",
    "start": "node server.js & node build/dev-server.js",
    // ...
  }
}

Compile and run the complete app:

npm run dev

E: Parting Notes

So, we have learned how to make a Vue.js app real-time, by using simplicity and power of Pusher. Vue.js is really robust and simple framework which provides a great base for building robust real-time applications.

You comments and questions are welcome! Feel free to Contact Us in case of any query :)

Take a look at the Indigo.Design sample applications to learn more about how apps are created with design to code software.

Topics:
javascript application ,vue.js ,web dev ,real-time data

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}