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!
Join the DZone community and get the full member experience.
Join For FreeVue.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.
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, withpusher.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 :)
Opinions expressed by DZone contributors are their own.
Comments