Vue.js 2 Authentication Tutorial, Part 2
In Part 2 of our series, we'll dive into the coding of our app, creating the front-end, the back-end, and building a router.
Join the DZone community and get the full member experience.
Join For FreeOur App: The Ultimate Startup Battle Ground
Following our introduction to Vue.js in Part 1, today we will build an app called The Ultimate Startup Battle Ground
. Several startups are springing up all over the world. These startups are coming up with innovative technology but have limited funds. Our app hopes to alleviate the issue of funding by providing an up-to-date list of startup battles all over the world with details of sponsors and seed fund amounts. The Ultimate Startup Battle Ground app will display a list of startup battles to the general public.
Interested startup founders can get hold of this list and ensure their team does not miss out on it. However, the app will also provide a list of secret startup battles. This list will only be accessible to registered members.
Note: The secret startup battles have bigger sponsors.
Build The Back-End
Let's build an API to serve the list of startup battles to our app. We'll quickly build the API with Node.js. The API is simple. This is what we need:
- An endpoint to serve public startup battles -
/api/battles/public
. - An endpoint to serve secret startup battles -
/api/battles/private
. - Secure the endpoint that serves secret startup battles, so that it can only be accessed by registered users.
Go ahead and fetch the Node.js backend from GitHub.
Note: We'll be securing the backend with Auth0, so make sure you have an account already or sign up for one.
Your server.js
should look like this:
'use strict';
const express = require('express');
const app = express();
const jwt = require('express-jwt');
const jwks = require('jwks-rsa');
const cors = require('cors');
const bodyParser = require('body-parser');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cors());
const authCheck = jwt({
secret: jwks.expressJwtSecret({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 5,
jwksUri: "https:///.well-known/jwks.json"
}),
// This is the identifier we set when we created the API
audience: '{YOUR-API-AUDIENCE-ATTRIBUTE}',
issuer: "https://{YOUR-AUTH0-DOMAIN}.auth0.com/",
algorithms: ['RS256']
});
app.get('/api/battles/public', (req, res) => {
let publicBattles = [
// Array of public battles
];
res.json(publicBattles);
})
app.get('/api/battles/private', authCheck, (req,res) => {
let privateBattles = [
// Array of private battles
];
res.json(privateBattles);
})
app.listen(3333);
console.log('Listening on localhost:3333');
Check out the full server.js file here.
Note: Your YOUR-AUTH0-DOMAIN
should be replaced with your auth0 domain.
Your package.json
file should look like this:
{
"name": "startup-battle",
"version": "0.0.1",
"description": "",
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node server.js",
"dev": "nodemon server.js"
},
"author": "Auth0",
"license": "MIT",
"dependencies": {
"body-parser": "^1.15.2",
"cors": "^2.8.1",
"express": "^4.14.0",
"express-jwt": "^3.4.0",
"jwks-rsa": "^1.1.1"
}
}
Once you have cloned the project, run an npm install
, then use postman to serve your routes like so:
API serving public startup battles
API serving private startup battles
The public startup battles endpoint should be http://localhost:3333/api/battles/public
.
The private startup battles endpoint should be http://localhost:3333/api/battles/private
.
Don't worry about the middleware in charge of securing our endpoint for now. We'll deal with that later. Now, let's build our frontend with Vue.js 2.
Build The Front-End With Vue.js 2
In the early days of Vue.js, there was no particular tool recommended or common way to set up a Vue.js app. However, there is a tool now for scaffolding your Vue.js apps. It's called the Vue.js CLI tool. It's being maintained by the Vue.js team.
Go ahead and install the vue-cli tool globally like so:
npm install -g vue-cli
You will be greeted with a series of questions
After installing globally, go ahead and scaffold a new Vue.js 2 app like so:
vue init webpack ultimate-startup-battle
Note: In this context, webpack
is a template. You can actually select the template you want vue-cli
to scaffold your app with. Another alternative is to use browserify
. Check out the list of templates here.
Move into the new directory, ultimate-startup-battle
, and run npm install
to install all the dependencies required for your app.
Now run npm run dev
from your terminal to start up your app. It should automatically open up your web browser at http://localhost:8080
and serve your new app.
Let's check out the structure of our newly scaffolded app.
ultimate-startup-battle/
build/ - All build files are here
config/ - All environment config files are here
node_modules/ - All the packages required for the vuejs app resides here
src/
- assets - All assets reside here
- components - All our Vue components resides here
- router - Our router is defined here
- App.vue - The Parent component
- main.js - Starting point for our app where the router, template and App component are bound to the root app div
static/ - contains static files
.babelrc
.editorconfig
.eslintignore
.eslintrc.js
.gitignore
.postcssrc.js
index.html - Index file that declares the root div where the App component is been bound to
package.json - File that contains the names of all the packages residing in node_modules folder
README.md
node_modules/ - All the packages required for the react app resides here
package.json - File that contains the names of all the packages residing in node_modules folder
We will work with this structure but make some few modifications.
Note: We are not writing any tests for this application. It's out of the scope of this tutorial. So during the installation, I opted out by choosing the no option.
Make the following modifications like so:
- Create a
privateBattles.vue
file inside thecomponents
directory. This component will take care of fetching the private startup battles and displaying them to the user. - Create a
publicBattles.vue
file inside thecomponents
directory. This component will take care of fetching the public startup battles and displaying them to the user. - Create a
AppNav.vue
file inside thecomponents
directory. This component will be in charge of our navigation throughout the app. - Create a folder called
utils
. This will house our helper functions.
Fetch the API Data
The first thing we need to do is to fetch the API data from our Node backend to display in our app. Make sure the Node server is running.
Let's create a helper file to handle fetching the API. Create a battles-api.js
file inside the utils
directory.
Open up the file and add code to it like so:
import axios from 'axios';
const BASE_URL = 'http://localhost:3333';
export {getPublicStartupBattles, getPrivateStartupBattles};
function getPublicStartupBattles() {
const url = `${BASE_URL}/api/battles/public`;
return axios.get(url).then(response => response.data);
}
function getPrivateStartupBattles() {
const url = `${BASE_URL}/api/battles/private`;
return axios.get(url).then(response => response.data);
}
battles-api.js
Note: Install axios
in your app by running npm install axios --save
.
We are using a very good promise based HTTP client, axios. An alternative for this is superagent.
In the getPublicStartupBattles
and getPrivateStartupBattles
functions, axios fetches data from the API endpoints. Then we do this: export {getPublicStartupBattles, getPrivateStartupBattles};
to make them ready for use in our components.
Build the Nav Component
The AppNav.vue
file is our Nav component. Go ahead and add code to it like so:
<template>
<nav class="navbar navbar-default">
<div class="navbar-header">
<router-link to="/" class="navbar-brand"> The Ultimate Startup Battle Ground</router-link>
</div>
<ul class="nav navbar-nav navbar-right">
<li>
<button class="btn btn-danger log" @click="handleLogout()">Log out </button>
<button class="btn btn-info log" @click="handleLogin()">Log In</button>
</li>
</ul>
</nav>
</template>
<script>
import { isLoggedIn, login, logout } from '../../utils/auth';
export default {
name: 'app-nav',
methods: {
handleLogin() {
login();
},
handleLogout() {
logout();
},
isLoggedIn() {
return isLoggedIn();
},
},
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.navbar-right { margin-right: 0px !important}
.log {
margin: 5px 10px 0 0;
}
</style>
The router-link
Component from vue-router
enables seamless client-side transition between routes without any page reload.
Build the Publicbattles and Privatebattles Component
By default, these two components will look similar in functionalities. They both display data from different endpoints. Let's start with the PublicBattles
component.
<template>
<div>
<app-nav></app-nav>
<h3 class="text-center">Daily Startup Battles</h3>
<hr/>
<div class="col-sm-4" v-for="battle in publicBattles">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"> </h3>
</div>
<div class="panel-body">
<p><span class="badge alert-info"> Sponsor: </span> </p>
<p><span class="badge alert-danger"> SeedFund: </span><strong> $ </strong></p>
</div>
</div>
</div>
<div class="col-sm-12">
<div class="jumbotron text-center">
<h2>View Private Startup Battles</h2>
<router-link class="btn btn-lg btn-success" to="/private-battles">Private Startup Battles</router-link>
</div>
</div>
</div>
</template>
<script>
import AppNav from './AppNav';
import { isLoggedIn } from '../../utils/auth';
import { getPublicStartupBattles } from '../../utils/battles-api';
export default {
name: 'publicBattles',
components: {
AppNav,
},
data() {
return {
publicBattles: '',
};
},
methods: {
isLoggedIn() {
return isLoggedIn();
},
getPublicStartupBattles() {
getPublicStartupBattles().then((battles) => {
this.publicBattles = battles;
});
},
},
beforeMount() {
this.getPublicStartupBattles();
},
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>
publicBattles.vue
Let's analyze the code above. The publicBattles
component is pulling data from an API, so it needs a way of holding that data. Vue.js has a data
method where you can define properties to hold your data as some form of state. In the code above, we declared a publicBattles
property.
The methods
property also comes by default with Vue.js. In this property, you can define custom logic as functions within this property. So we defined isLoggedIn
and getPublicStartupBattles
functions.
In the getPublicStartupBattles
method, we call the getPublicStartupBattles
method we exported from the battles-api.js
helper file and set state as seen below:
...
getPublicStartupBattles() {
getPublicStartupBattles().then((battles) => {
this.publicBattles = battles;
});
},
...
Now, we took advantage of one of the Vue.js 2 lifecycle hooks, beforeMount
. Whatever is defined in this method is applied just after a component is mounted on the browser screen. So, we invoked the getPublicStartupBattles
method in the hook as seen below:
...
beforeMount() {
this.getPublicStartupBattles();
}
...
All we are trying to do is tell Vue.js to load the data from the API just before the publicBattles
component gets rendered.
We imported the AppNav
component and registered it under the components
property. The name
property has a value of publicBattles
. What that simply means is this: if we need to use this component in a template, then we would have it as <publicBattles></publicBattles>
.
Let's take a good look at what is enclosed in the <template>
tag. This is what is rendered on the screen.
We looped through the publicBattles
property, which is now an array, with the help of the v-for
inbuilt directive to display the contents on the screen.
<div class="col-sm-4" v-for="battle in publicBattles">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"> {{ battle.name }} </h3>
</div>
<div class="panel-body">
<p><span class="badge alert-info"> Sponsor: </span> {{ battle.sponsor }} </p>
<p><span class="badge alert-danger"> SeedFund: </span><strong> ${{ battle.seedFund }} </strong></p>
</div>
</div>
</div>
...
data() {
return {
publicBattles: '',
};
},
...
That's the publicBattles
property right there. Vue.js automatically binds it to the DOM. So, we can just use it in the <template>
tag.
Now, let's build the PrivateBattles
component in the same way:
<template>
<div>
<app-nav></app-nav>
<h3 class="text-center">Secret Startup Battles</h3>
<hr/>
<div class="col-sm-4" v-for="battle in privateBattles">
<div class="panel panel-danger">
<div class="panel-heading">
<h3 class="panel-title"> {{ battle.name }} </h3>
</div>
<div class="panel-body">
<p><span class="badge alert-info"> Sponsor: </span> {{ battle.sponsor }} </p>
<p><span class="badge alert-danger"> SeedFund: </span><strong> ${{ battle.seedFund }} </strong></p>
</div>
</div>
</div>
<div class="col-sm-12">
<div class="jumbotron text-center">
<h2>View Public Startup Battles</h2>
<router-link class="btn btn-lg btn-success" to="/"> Public Startup Battles </router-link>
</div>
</div>
</div>
</template>
<script>
import AppNav from './AppNav';
import { isLoggedIn } from '../../utils/auth';
import { getPrivateStartupBattles } from '../../utils/battles-api';
export default {
name: 'privateBattles',
components: {
AppNav,
},
data() {
return {
privateBattles: '',
};
},
methods: {
isLoggedIn() {
return isLoggedIn();
},
getPrivateStartupBattles() {
getPrivateStartupBattles().then((battles) => {
this.privateBattles = battles;
});
},
},
beforeMount() {
this.getPrivateStartupBattles();
},
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>
privateBattles.vue
Give yourself a pat on the back because you have successfully created the AppNav
, PublicBattles
, and PrivateBattles
components. Whoop! Whoop!
We need to take care of one more thing so that our app can function. Routing!!!
Build the Router
Open up the src/router/index.js
file, this is where the vue router is defined. So modify the code like so:
import Vue from 'vue';
import Router from 'vue-router';
import PrivateBattles from '@/components/privateBattles';
import PublicBattles from '@/components/publicBattles';
Vue.use(Router);
export default new Router({
mode: 'history',
routes: [
{
path: '/',
name: 'PublicBattles',
component: PublicBattles,
},
{
path: '/private-battles',
name: 'PrivateBattles',
component: PrivateBattles,
},
],
});
index.js
Each route has a path, name, and the component to be rendered when that route is invoked by the user. By the way, we already imported the components at the top of the file.
import Vue from 'vue';
import Router from 'vue-router';
import PrivateBattles from '@/components/privateBattles';
import PublicBattles from '@/components/publicBattles';
Just a few things before we check our application in the browser:
- Open up
index.html
in the root directory and add bootstrap. Now the content of the HTML file should look like this:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>startupbattle</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
</head>
<body>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
Feel free to check out your application in the browser. Right now, you should have something like this:
Homepage
Private Battles Page
Tune in tomorrow for Part 3!
Published at DZone with permission of Prosper Otemuyiwa, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Trending
-
How to Load Cypress Chrome Extension
-
Which Is Better for IoT: Azure RTOS or FreeRTOS?
-
SRE vs. DevOps
-
AWS Multi-Region Resiliency Aurora MySQL Global DB With Headless Clusters
Comments