HTTP and WebSocket Communication
In thischapter,we'll be looking at how we can interface with our server-side APIs usingHTTP. We will create an application using our own offline API withHTTP GET,POST,PUT,PATCH, andDELETE. As well as this, we'll be creating an in-memory, real-time chat application that takes advantage of WebSockets with theSocket.iolibrary.
By the end of this chapter, you will know how to:
- Create a mock database APIs with json-server
- Create HTTP requests with Axios
- Use WebSockets and Socket.io for real-time communication across clients
HTTP
Let's start off by creating a new Vue.js project that we can use as a playground project. Type the following in your Terminal:
# Create a new Vue project
$ vue init webpack-simple vue-http
# Navigate to directory
$ cd vue-http
# Install dependencies
$ npm install
# Run application
$ npm run dev
There are many ways to create HTTP requests within JavaScript. We'll be using theAxioslibrary to use a simplified promise-based approach within our project. Let's install it by typing the following in our Terminal:
# Install Axios to our project
$ npm install axios --save
We now have the ability to create HTTP requests; we just need an API to pointAxiostowards. Let's create a mock API.
Installing JSON server
In order to create a mock API, we can use thejson-serverlibrary. This allows us to get up-and-running globally quickly by just creating adb.jsonfile inside of our project. It effectively creates a GET, POST, PUT, PATCH, and DELETE API and stores the data in a file, appended to our original JSON file.
We can install it by running the following in our Terminal:
# Install the json-server module globally
$ npm install json-server -g
As we've added the-gflag, we'll be able to access thejson-servermodule globally throughout our Terminal.
The next thing we need to do is create ourdb.jsonfile inside the root of our project. Be as creative as you want with your dataset; we just simply have a list of courses that we may or may not be interested in:
{
"courses": [
{
"id": 1,
"name": "Vue.js Design Patterns"
},
{
"id": 2,
"name": "Angular: From Beginner to Advanced"
},
{
"id": 3,
"name": "Cross Platform Native Applications with Fuse"
}
]
}
We can then run our database by running the following in the Terminal:
# Run the database based on our db.json file
$ json-server db.json --watch
If we've done everything successfully, we should then be able to access our database athttp://localhost:3000, as seen by the following success message:
Awesome. We're all set up and now we can get a list of courses.
HTTP GET
The first thing we need to do is importAxiosinto ourApp.vuecomponent. We can also set aROOT_URLin this instance as we'll only be looking for the/coursesendpoint:
<script>
import axios from 'axios'
export default {
data() {
return {
ROOT_URL: 'http://localhost:3000/courses',
courses: []
}
}
}
</script>
This then gives us the ability to hook into a lifecycle hook such ascreated()and call a method that requests the courses from our API:
export default {
data() {
return {
ROOT_URL: 'http://localhost:3000/courses',
courses: []
}
},
created() {
this.getCourseList();
},
methods: {
getCourseList() {
axios
.get(this.ROOT_URL)
.then(response => {
this.courses = response.data;
})
.catch(error => console.log(error));
}
}
}
What's happening here? We're calling thegetCoursesListfunction, which makes an HTTPGETrequest to ourhttp://localhost:3000/coursesendpoint. This then either sets the courses array equal to the data (that is, everything inside of ourdb.json) or merely logs out an error if there was one.
We can then display this on screen using thev-for directive:
<template>
<div class="course-list">
<h1>Courses</h1>
<div v-for="course in courses" v-bind:key="course.id">
<p>
{{course.name}}
</p>
</div>
</div>
</template>
Together with a little bit of styling, we get:
<style>
.course-list {
background-color: rebeccapurple;
padding: 10px;
width: 50%;
text-align: center;
margin: 0 auto;
color: white;
}
</style>
Let's move on to HTTP POST!
HTTP POST
We can add an input box andbuttonfollowing thecourseNamediv, allowing the user to input a new course to their learning list:
<
div
>
<
input type="text" v-model="courseName" placeholder="Course name"
>
<
button @click="addCourse(courseName)"
>
Add
<
/button
>
<
/div
>
This requires us to add thecourseNamevariable to ourdataobject:
data() {
return {
ROOT_URL: 'http://localhost:3000/courses/',
courses: [],
courseName: '',
};
},
We can then create a similar method namedaddCoursethat takes thecourseNameas a parameter:
methods: {
// Omitted
addCourse(name) {
axios
.post(this.ROOT_URL, { name })
.then(response =
>
{
this.courses.push(response.data);
this.courseName = '';
})
.catch(error =
>
console.log(error));
}
}
You may notice that it's quite similar to the previous HTTP call, but this time instead of.getwe're using.post, and passing an object with the key and value ofname.
After sending the POST request, we then usethis.courses.push(response.data)to update the client side array as, whilst the server side (our clientdb.jsonfile) is updated, the client state is not.
HTTP PUT
The next thing we'd like to be able to do is change items in our list. Perhaps we've made a mistake when submitting the item and we therefore want to edit it. Let's add that functionality.
Firstly, let's tell Vue to keep track of whenever we're editing a course. A user's intention to edit will be whenever they click a course name; we can then add the editing Boolean to ourdataobject:
data() {
return {
ROOT_URL: 'http://localhost:3000/courses/',
courses: [],
courseName: '',
editing: false,
};
},
Our template can then be changed to reflect this:
<
template
>
<
div class="course-list"
>
<
h1
>
Courses
<
/h1
>
<
div v-for="course in courses" v-bind:key="course.id"
>
<
p @click="setEdit(course)" v-if="!editing"
>
{{course.name}}
<
/p
>
<
div v-else
>
<
input type="text" v-model="course.name"
>
<
button @click="saveCourse(course)"
>
Save
<
/button
>
<
/div
>
<
/div
>
<
div v-if="!editing"
>
<
input type="text" v-model="courseName" placeholder="Course name"
>
<
button @click="addCourse(courseName)"
>
Add
<
/button
>
<
/div
>
<
/div
>
<
/template
>
What exactly is happening here? Well, we've changed ourcourseNameto instead only display when we're not editing (that is, we haven't clicked the name of the course). Instead, using thev-elsedirective, we're displaying an input box andbuttonthat allows us to save the newCourseName.
We're also hiding the add course button at this point to keep things simple.
Here's what the code looks like:
setEdit(course) {
this.editing = !this.editing;
},
saveCourse(course) {
this.setEdit();
axios
.put(`${this.ROOT_URL}/${course.id}`, { ...course })
.then(response =
>
{
console.log(response.data);
})
.catch(error =
>
console.log(error));
}
Here we're using the.putmethod on ouraxiosinstance pointed at the endpoint of our selected course. As a data parameter we're using the spread operator with{ ...course }to destructure the course variable to work with our API.
After this, we merely log the results to the console. Here's what it looks like when we edit the "Vue.js Design Patterns" string to simply sayVue.js:
Yay! The last thing we'll be looking at is DELETE and removing items from our database.
HTTP DELETE
To delete items from our list, let's add abuttonso that, when the user enters edit mode (by clicking an item), they can remove that specific course:
<
div v-else
>
<
input type="text" v-model="course.name"
>
<
button @click="saveCourse(course)"
>
Save
<
/button
>
<
button @click="removeCourse(course)"
>
Remove
<
/button
>
<
/div
>
OurremoveCoursefunction then looks as follows:
removeCourse(course) {
axios
.delete(`${this.ROOT_URL}/${course.id}`)
.then(response =
>
{
this.setEdit();
this.courses = this.courses.filter(c =
>
c.id != course.id);
})
.catch(error =
>
console.error(error));
},
We're calling theaxios.deletemethod and then filtering ourcourseslist for every course but the one we've deleted. This then updates our client state and makes it consistent with the database.
In this section of the chapter, we've created ourselves a simple "courses I want to study" list based on our REST API. It could have certainly been abstracted to multiple components, but as this wasn't the core focus of the application we've simply did it all in one.
Coming up next, let's make a real-time chat application with Node andSocket.io.
Real-time chat application with Node and Socket.io
In this section, we'll be creating a real-time chat application using Node andSocket.io. We'll be writing a small amount of code with Node.js and the Express framework, but it's all the same JavaScript that you know and love.
Run the following in your Terminal to create a new project:
# Create a new Vue project
$ vue init webpack-simple vue-chat
# Navigate to directory
$ cd vue-chat
# Install dependencies
$ npm install
# Run application
$ npm run dev
We can then create a server folder and initialize apackage.jsonfor server-specific dependencies like so:
# Create a new folder named server
$ mkdir server
# Navigate to directory
$ cd server
# Make a server.js file
$ touch server.js
# Initialise a new package.json
$ npm init -y
# Install dependencies
$ npm install socket.io express --save
What is Socket.io?
In our previous example, if we wanted to get new data from the server we'd need to make another HTTP request, whereas with WebSockets we can simply have a consistent event-listener that reacts whenever the event is fired.
To take advantage of this in our chat application, we'll be usingSocket.io. This is a client and server-side library that allows us to work with WebSockets quickly and easily. It allows us to define and submit events that we can listen to and subsequently perform actions.
Server setup
We can then create a new HTTP server using Express and listen for application connections withSocket.ioby adding the following toserver.js:
const app = require('express')();
const http = require('http').Server(app);
const io = require('socket.io')(http);
const PORT = 3000;
http.listen(PORT, () => console.log(`Listening on port: ${PORT}`));
io.on('connection', socket => {
console.log('A user connected.');
});
If we then runnode server.jsfrom within our Terminal inside theserverfolder, we should see the messageListening on port: 3000. This means that once we implementSocket.ioinside of our client application we'll be able to monitor whenever somebody connects to the application.
Client connections
To capture a client connection, we need to install theSocket.iointo our Vue application. We'll also be using another dependency namedvue-socket.io, which provides us with a smoother implementation within our Vue applications.
Run the following in your Terminal, ensuring you're in the root directory (that is, not in theserverfolder):
# Install socket.io-client and vue-socket.io
$ npm install socket.io-client vue-socket.io --save
Setting up Vue and Socket.io
Let's head on over to ourmain.jsfile so we can registerSocket.ioand theVue-Socket.ioplugin. You may remember how to do this from previous chapters:
import Vue from 'vue';
import App from './App.vue';
import SocketIo from 'socket.io-client';
import VueSocketIo from 'vue-socket.io';
export const Socket = SocketIo(`http://localhost:3000`);
Vue.use(VueSocketIo, Socket);
new Vue({
el: '#app',
render: h => h(App),
});
In the preceding code block, we're importing the necessary dependencies and creating a reference to our Socket.io server, which is currently running on port3000. We're then adding the Vue plugin by usingVue.use.
If we've done everything correctly, our client and server should be talking to each other. We should get the following inside of our Terminal:
Determining connection status
Now that we've added the Vue-Socket.io plugin, we have access to a sockets object inside of our Vue instance. This allows us to listen to particular events as well as determine whether the user is connected or disconnected from the WebSocket.
Inside ofApp.vue, let's display a message on screen if we're connected/disconnected from the server:
<
template
>
<
div
>
<
h1 v-if="isConnected"
>
Connected to the server.
<
/h1
>
<
h1 v-else
>
Disconnected from the server.
<
/h1
>
<
/div
>
<
/template
>
<
script
>
export default {
data() {
return {
isConnected: false,
};
},
sockets: {
connect() {
this.isConnected = true;
},
disconnect() {
this.isConnected = false;
},
},
};
<
/script
>
There shouldn't be much new here other than the sockets object. Whenever we're connected to the socket, we can run any code we want inside of theconnect()hook, and the same goes fordisconnect(). We're simply flipping a Boolean to display a different message on screen with thev-ifandv-elsedirective(s).
Initially, we getConnected to the serveras our server is running. If we stop the server withCTRL+_C_in our Terminal window, our heading will change to reflect the fact that we no longer have a WebSocket connection. Here's the result:
Creating a connection status bar
Let's have some fun with this concept. We can make a components folder and subsequently a new component namedConnectionStatus.vue. Inside of this file we can create a status bar that is shown to the user whenever they're online or offline:
<template>
<div>
<span v-if="isConnected === true" class="bar connected">
Connected to the server.
</span>
<span v-else class="bar disconnected">
Disconnected from the server.
</span>
</div>
</template>
<script>
export default {
props: ['isConnected'],
};
</script>
<style>
.bar {
position: absolute;
bottom: 0;
left: 0;
right: 0;
text-align: center;
padding: 5px;
}
.connected {
background: greenyellow;
color: black;
}
.disconnected {
background: red;
color: white;
}
</style>
Whilst we only have one screen inside of our current application, we may want to use this component across multiple components, so we can register it globally inside ofmain.js:
import App from './App.vue';
import ConnectionStatus from './components/ConnectionStatus.vue';
Vue.component('connection-status', ConnectionStatus);
We can then edit ourApp.vuetemplate to use this component and pass the current connection state as a prop:
<template>
<div>
<connection-status :isConnected="isConnected" />
</div>
</template>
Here's our result:
Next, we can make a navigation bar component to make our UI more complete.
Navigation bar
The navigation bar component won't have many uses other than to simply display the name of our application. You could change this to include other functionality such as log in/out, adding new chat channels, or any other chat-specific user actions.
Let's make a new component namedNavbar.vuein thecomponentsfolder:
<template>
<div v-once>
<nav class="navbar">
<span>Socket Chat</span>
</nav>
</div>
</template>
<script>
export default {};
</script>
<style>
.navbar {
background-color: blueviolet;
padding: 10px;
margin: 0px;
text-align: center;
color: white;
}
</style>
You may notice that thev-oncedirective was added on thisdiv. This is the first time we've looked at it, but as this component is entirely static we can tell Vue to not listen for any changes and only render it once.
We then have to remove any default padding or margin inside of the HTML body. Create a file namedstyles.cssinside of the root directory that contains these attributes:
body {
margin: 0px;
padding: 0px;
}
We can then add this to ourindex.htmlfile like so:
<head>
<meta charset="utf-8">
<title>vue-chat</title>
<link rel="stylesheet" href="styles.css">
</head>
Next, we'll need to register this component globally. Try and do this by yourself insidemain.jsif you feel you can.
This requires us to importNavbarand register it like so:
import Navbar from './components/Navbar.vue'
Vue.component('navigation-bar', Navbar);
We can then add this to ourApp.vuefile:
<template>
<div>
<navigation-bar />
<connection-status :isConnected="isConnected" />
</div>
</template>
Next up, let's create ourMessageListcomponent to hold a list of messages.