How to Create a Vue.js component library

How to Create a Vue.js component library

How to Build Vue Components like a Pro. n this post, I will portray some of the best practices that you may want to keep in mind when building an advanced Vue App. App Setup. Build Vue Components with vue-class-component.

Vue is one of the most rapidly growing JavaScript frameworks in today’s world. Described as an “Intuitive, Fast, and Composable” MVVM for building interactive interfaces, Vue has become every developer’s favorite JavaScript framework for developing interactive web apps and interfaces. This is quite obvious with its 97K GitHub ⭐️ since its release in 2014.

Unlike Angular, which is based on the good old Model-View-Controller architecture, Vue follows a Model-View-View-Model system.

Another great thing about Vue is that it has single file components. This means that you can write the template, the script, and the style in a single .vue file.

<template>
  <p>({greeting })</p>
</template>
<script>
  module.exports = {
   data: function () {
      return {
        greeting: 'Hello'
      }
    }
  }
</script>
<style scoped>
  p {
    font-size: 2em;
    text-align: center;
  }
</style>

In this post, I will portray some of the best practices that you may want to keep in mind when building an advanced Vue App.

App Setup

There are many different ways that you can choose from for creating a Vue App. If you are a newbie, you can check out out this JS Fiddle Hello World Example to try out Vue.

In this post I am going to use the Vue CLI to create a Vue Project. First, open a command terminal in your system and write the following command to install the Vue CLI:

$ npm install --global @vue/cli

You can now create a Vue project! So lets go ahead and do that:

$ vue create vue-app
$ cd vue-app

This will create a new Vue project called vue-app. You can name it something else if you want.

Build Vue Components with vue-class-component

Take a look at the code snippet at the top of and you will see that you have a data function that returns an object. If you want to pass any handlers, you would have to write a method object.

vue-class-component reduces the component development process by allowing developers to add data properties and handlers directly as properties to the class.

Open your project directory in a code editor (I ❤️ VS Code). In your src folder, you will notice two files: App.vue and main.js.

Developers with prior experience in libraries like React can say that the main.js file is equivalent to React’s index.js. Meaning this the actual file that gets launched when you run commands like yarn serve or yarn build.

Re-write the main.js file with the following code:

import 'tailwindcss/dist/tailwind.css';
import Vue from 'vue';
import App from './App';

new Vue ({
  el: '#app',
  render: h => <App />,
});

main.js

Basically, we are first grabbing a div element that has “app” as the id from the public/index.html file and then we are rendering the App component inside it.

You now need to create this App component inside the App.vue file. Open the App.vue file and replace the default code with this:

<template>
  <h1 @click="onClick">
    {{message}}
  </h1>
</template>

<script>
import Vue from 'vue';
import Component from 'vue-class-component';
export default Component({})(
  class App extends Vue {
    message = 'Batman';
    onClick() {
      this.message = 'Bruce Wayne';
    }
  }
);
</script>

App.vue

Here, I first have created a simple template that has a div with a message inside it. The script imports the Vue package and the Component function from vue-class-component. You also need to install this package in your project.

$ yarn add vue-class-component

Next, I am decorating the App class with the Component function. This function has an object that you can pass in options.

If you are using VS Code, you will see that you are now getting an error on App. This is because VS Code by default does not accept experimental decorator. To solve this issue, create a new file called jsconfig.json in the project’s root directory. Here, you are telling the Vue Compiler to allow experimental decorators in your code:

{
  "compilerOptions": {
    "experimentalDecorators": true
  }
}

Reload the editor and you will see the error disappear!

Now we answer the question, “Why should I use vue-class-component instead of Vue’s traditional components?”

In a traditional component, you are required to write a data function that returns an object. To change anything in your component, you will then write methods such an onClick.

const TraditionalComp = {
  data() {
    return {message: "Batman"}
  },
  methods:{
    onClick() {
      this.message = "Bruce Wayne"
    }
  }
}

But in vue-class-component, you directly write the onClick method. All that’s left to do is link it to the template. You can do this by writing @click="onClick" inside the template’s h1 tag.

Run the Vue app using the yarn serve command, and you will see it work like this:

Define your Props with vue-property-decorator

You can also use vue-property-decorator to define the properties directly on the class. And that too is done with a simple @Prop() decorator. This method of prop declaration lets us keep our classes simple.

First, install the vue-property-decorator package in your project:

$ yarn add vue-property-decorator

Another great thing about this package is that it also includes the vue-class-component inside it. So you can instead import the Component from the vue-property-decorator package.

Inside the App.vue file, re-write the code as shown below:

<template>
  <h1 @click="onClick">
    {{message}}
  </h1>
</template>

<script>
import Vue from 'vue';
import {Component, Prop} from 'vue-property-decorator';
export default Component({})(
  class App extends Vue {
    @Prop({default: 'Batman'})
    message;
    onClick() {
      this.message = 'Bruce Wayne';
    }
  }
);
</script>

App.vue

Here, I am first importing the Component and Prop from the vue-property-decorator package. Then inside the App class, I am using the @Prop decorator to set the default value of the message.

And that’s it! You are now passing the message as prop to our code, and it also has a default value.

Define the Content’s Place in Components with Vue Slots

Vue’s slots lets you tell your code where you want your content to be in the component. This may sound confusing now, so let’s see what slots actually do for us with some coding.

Back in the main.js file, re-write the code inside render like this:

render: h => (
  <App>
    <h1>Superman</h1>
  </App>
)

If you take a look at the app in your browser, you will see that nothing has changed. This is because you have not specified any place for the new text inside the template in App.vue file. This is where slots come into play.

Inside App.vue‘s template, write two new slots that wrap the original h1 tag.

<template>
  <div>
    <slot name="header"></slot>
    <h1 @click="onClick">
      {{message}}
    </h1>
    <slot name="footer"></slot>
  </div>
</template>

Your app still won’t render the new text. That’s because the compiler now doesn’t know which slot to put the new text in. I want the text to be in the bottom slot. I have given this slot a name of footer.

Go to main.js file, and add an attribute of slot="footer" to the new text. This will then render the new text.

But your render method is a bit unwieldy now. Let’s see how you can use slots to take care of that.

Use Slots to Create Layouts

You can also use slots to create custom layouts that can be used to specify where each piece of your app or component should be placed.

Create a new file called Layout.vue inside the src/components folder. Inside this file, write a template element as shown below:

<template>
  <div>
    <slot name="header"></slot>
    <slot name="body"></slot>
    <slot name="footer"></slot>
  </div>
</template>

Next, go into the App.vue file and delete everything inside the template tag. Then, inside the script tag, import the Layout.vue that you just created, like this:

import Layout from './components/Layout';

You also need to tell the Component decorator that you are using the Layout as a component.

export default Component({
  components: {Layout},
})

You can now use the Layout as a component inside the template tag. I am going to also add a few text tags.

<template>
  <Layout>
    <h1 slot="header">How To Build Vue Apps Like A Pro 😎</h1>
    <h2 slot="body"> by Rajat S</h2>
    <h3 slot="footer">Technical Content Writer</h3>
  </Layout>
</template>

Make sure that you have added a slot attribute with the appropriate name, else the text tags will not get rendered.

Passing Props using Vue Slot Scope

By combining your components with slots, you can pass the component’s data into slot using slot-scope. You can pass props down from Parent components to Child components without coupling them together.

Go to the App.vue file and inside the template tag, wrap the Layout inside a Settings tag.

<template>
  <Settings>
    <Layout slot-scope="{header}">
      <h1 slot="header">{{header}}</h1>
      <h2 slot="body"> by Rajat S</h2>
      <h3 slot="footer">Technical Content Writer</h3>
    </Layout>
  </Settings>
</template>

Import the Settings inside the script tag,

import Settings from './components/Settings.vue';

Tell the Component decorator that you are using Settings as a component.

export default Component({
  components: {Layout, Settings},
})

Now, you actually need to create the Settings component. So go to src/components and create a file named Settings.vue. Write this code inside it:

<template>
  <div>
    <slot :header="header"></slot>
  </div>
</template>
<script>
import Vue from 'vue';
import Component from 'vue-class-component';
export default Component({})(
  class Settings extends Vue {
    header = 'How To Build Vue Apps Like A Pro 😎';
  }
);
</script>

Settings.vue

Inside the template tag, I have a root div that has slot inside it. Inside this slot, I’m binding :header to header.

Moving on to the script tag, I am first importing the Vue package and the Component decorator from vue-class-component package.

Then I create a Header class inside the Component decorator. This class contains the data that I want to pass on to the header prop.

Passing Props to Functional Templates

Functional templates allows you to create a component that only has a template tag and exposes the props into the template.

Inside the src/components folder, create a new file called Header.vue write this code inside it:

<template functional>
  <h1 slot="header">{{props.header}}</h1>
</template>

The term functional here is used to indicate that this file only has a simple template. Repeat this for Body.vue and Footer.vue files.

Return to the App.vue file and re-write the template like this:

<template>
  <Settings>
    <Layout slot-scope="{header, body, footer}">
      <Header slot="header" :header="header"></Header>
      <Body slot="body" :body="body"></Body>
      <Footer slot="footer" :footer="footer"></Footer>
    </Layout>
  </Settings>
</template>

You also need to write the import statements for the Header, Body, Footer inside the file’s script tag:

import {Header, Body, Footer} from './components';

And tell the Component decorator that you are using these as components.

export default Component({
  components: {
    Settings,
    Layout,
    Header,
    Body,
    Footer,
  },
})

But the code still won’t work. Because the import statement that you just wrote is not entirely correct. To solve this issue, create a new file called index.js inside src/components.

export {default as Header} from ‘./Header.vue’;
export {default as Body} from ‘./Body.vue’;
export {default as Footer} from ‘./Footer.vue’;

With this, you have created Functional Templates in Vue that allows us to pass our props from a Parent component to a Child component, without having to write much code.

Conclusion…

Vue is a software technology that is being widely used across the world for web development. Vue is actually a JavaScript framework with various optional tools for building user interfaces.

One of the main factor’s behind Vue’s success is that it is very easy to learn and it’s even easier to build awesome apps with it.

I hope this post helped you understand Vue better and how you can build better apps with it. Stay tuned for similar posts on Vue, React, and other popular frameworks/libraries. Feel free to comment and ask anything!

Vue 3 - A Look At Vue-Next (Alpha) - And What You Can Do

Vue 3 - A Look At Vue-Next (Alpha) - And What You Can Do

Vue 3 is coming soon, but what if wanted to add Vue.js 3.0 to an existing application? Let's see and do it! Also we'll look at the composition API at the same time!

Vue 3 is coming soon, but what if wanted to add Vue.js 3.0 to an existing application? Let's see and do it! Also we'll look at the composition API at the same time!

How to build Vue.js JWT Authentication with Vuex and Vue Router

How to build Vue.js JWT Authentication with Vuex and Vue Router

In this tutorial, we’re gonna build a Vue.js with Vuex and Vue Router Application that supports JWT Authentication

In this tutorial, we’re gonna build a Vue.js with Vuex and Vue Router Application that supports JWT Authentication. I will show you:

  • JWT Authentication Flow for User Signup & User Login
  • Project Structure for Vue.js Authentication with Vuex & Vue Router
  • How to define Vuex Authentication module
  • Creating Vue Authentication Components with Vuex Store & VeeValidate
  • Vue Components for accessing protected Resources
  • How to add a dynamic Navigation Bar to Vue App

Let’s explore together.

Contents

Overview of Vue JWT Authentication example

We will build a Vue application in that:

  • There are Login/Logout, Signup pages.
  • Form data will be validated by front-end before being sent to back-end.
  • Depending on User’s roles (admin, moderator, user), Navigation Bar changes its items automatically.

Screenshots

– Signup Page:

– Login Page & Profile Page (for successful Login):

– Navigation Bar for Admin account:

Demo

This is full Vue JWT Authentication App demo (with form validation, check signup username/email duplicates, test authorization with 3 roles: Admin, Moderator, User). In the video, we use Spring Boot for back-end REST APIs.

Flow for User Registration and User Login

For JWT Authentication, we’re gonna call 2 endpoints:

  • POST api/auth/signup for User Registration
  • POST api/auth/signin for User Login

You can take a look at following flow to have an overview of Requests and Responses Vue Client will make or receive.

Vue Client must add a JWT to HTTP Authorization Header before sending request to protected resources.

Vue App Component Diagram with Vuex & Vue Router

Now look at the diagram below.

Let’s think about it.

– The App component is a container with Router. It gets app state from Vuex store/auth. Then the navbar now can display based on the state. App component also passes state to its child components.

Login & Register components have form for submission data (with support of vee-validate). We call Vuex store dispatch() function to make login/register actions.

– Our Vuex actions call auth.service methods which use axios to make HTTP requests. We also store or get JWT from Browser Local Storage inside these methods.

Home component is public for all visitor.

Profile component get user data from its parent component and display user information.

BoardUser, BoardModerator, BoardAdmin components will be displayed by Vuex state user.roles. In these components, we use user.service to get protected resources from API.

user.service uses auth-header() helper function to add JWT to HTTP Authorization header. auth-header() returns an object containing the JWT of the currently logged in user from Local Storage.

Technology

We will use these modules:

  • vue: 2.6.10
  • vue-router: 3.0.3
  • vuex: 3.0.1
  • axios: 0.19.0
  • vee-validate: 2.2.15
  • bootstrap: 4.3.1
  • vue-fontawesome: 0.1.7
Project Structure

This is folders & files structure for our Vue application:

With the explaination in diagram above, you can understand the project structure easily.

Setup Vue App modules

Run following command to install neccessary modules:

npm install vue-router
npm install vuex
npm install [email protected]
npm install axios
npm install bootstrap jquery popper.js
npm install @fortawesome/fontawesome-svg-core @fortawesome/free-solid-svg-icons @fortawesome/vue-fontawesome

After the installation is done, you can check dependencies in package.json file.

"dependencies": {
  "@fortawesome/fontawesome-svg-core": "^1.2.25",
  "@fortawesome/free-solid-svg-icons": "^5.11.2",
  "@fortawesome/vue-fontawesome": "^0.1.7",
  "axios": "^0.19.0",
  "bootstrap": "^4.3.1",
  "core-js": "^2.6.5",
  "jquery": "^3.4.1",
  "popper.js": "^1.15.0",
  "vee-validate": "^2.2.15",
  "vue": "^2.6.10",
  "vue-router": "^3.0.3",
  "vuex": "^3.0.1"
},

Open src/main.js, add code below:

import Vue from 'vue';
import App from './App.vue';
import { router } from './router';
import store from './store';
import 'bootstrap';
import 'bootstrap/dist/css/bootstrap.min.css';
import VeeValidate from 'vee-validate';
import { library } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
import {
  faHome,
  faUser,
  faUserPlus,
  faSignInAlt,
  faSignOutAlt
} from '@fortawesome/free-solid-svg-icons';

library.add(faHome, faUser, faUserPlus, faSignInAlt, faSignOutAlt);

Vue.config.productionTip = false;

Vue.use(VeeValidate);
Vue.component('font-awesome-icon', FontAwesomeIcon);

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app');

You can see that we import and apply in Vue object:
store for Vuex (implemented later in src/store)
router for Vue Router (implemented later in src/router.js)
bootstrap with CSS
vee-validate
vue-fontawesome for icons (used later in nav)

Create Services

We create two services in src/services folder:


services

auth-header.js

auth.service.js (Authentication service)

user.service.js (Data service)


Authentication service

The service provides three important methods with the help of axios for HTTP requests & reponses:

  • login(): POST {username, password} & save JWT to Local Storage
  • logout(): remove JWT from Local Storage
  • register(): POST {username, email, password}
import axios from 'axios';

const API_URL = 'http://localhost:8080/api/auth/';

class AuthService {
  login(user) {
    return axios
      .post(API_URL + 'signin', {
        username: user.username,
        password: user.password
      })
      .then(this.handleResponse)
      .then(response => {
        if (response.data.accessToken) {
          localStorage.setItem('user', JSON.stringify(response.data));
        }

        return response.data;
      });
  }

  logout() {
    localStorage.removeItem('user');
  }

  register(user) {
    return axios.post(API_URL + 'signup', {
      username: user.username,
      email: user.email,
      password: user.password
    });
  }

  handleResponse(response) {
    if (response.status === 401) {
      this.logout();
      location.reload(true);

      const error = response.data && response.data.message;
      return Promise.reject(error);
    }

    return Promise.resolve(response);
  }
}

export default new AuthService();

If login request returns 401 status (Unauthorized), that means, JWT was expired or no longer valid, we will logout the user (remove JWT from Local Storage).

Data service

We also have methods for retrieving data from server. In the case we access protected resources, the HTTP request needs Authorization header.

Let’s create a helper function called authHeader() inside auth-header.js:

export default function authHeader() {
  let user = JSON.parse(localStorage.getItem('user'));

  if (user && user.accessToken) {
    return { Authorization: 'Bearer ' + user.accessToken };
  } else {
    return {};
  }
}

It checks Local Storage for user item.
If there is a logged in user with accessToken (JWT), return HTTP Authorization header. Otherwise, return an empty object.

Now we define a service for accessing data in user.service.js:

import axios from 'axios';
import authHeader from './auth-header';

const API_URL = 'http://localhost:8080/api/test/';

class UserService {
  getPublicContent() {
    return axios.get(API_URL + 'all');
  }

  getUserBoard() {
    return axios.get(API_URL + 'user', { headers: authHeader() });
  }

  getModeratorBoard() {
    return axios.get(API_URL + 'mod', { headers: authHeader() });
  }

  getAdminBoard() {
    return axios.get(API_URL + 'admin', { headers: authHeader() });
  }
}

export default new UserService();

You can see that we add a HTTP header with the help of authHeader() function when requesting authorized resource.

Define Vuex Authentication module

We put Vuex module for authentication in src/store folder.


store

auth.module.js (authentication module)

index.js (Vuex Store that contains also modules)


Now open index.js file, import auth.module to main Vuex Store here.

import Vue from 'vue';
import Vuex from 'vuex';

import { auth } from './auth.module';

Vue.use(Vuex);

export default new Vuex.Store({
  modules: {
    auth
  }
});

Then we start to define Vuex Authentication module that contains:

  • state: { status, user }
  • actions: { login, logout, register }
  • mutations: { loginSuccess, loginFailure, logout, registerSuccess, registerFailure }

We use AuthService which is defined above to make authentication requests.

auth.module.js

import AuthService from '../services/auth.service';

const user = JSON.parse(localStorage.getItem('user'));
const initialState = user
  ? { status: { loggedIn: true }, user }
  : { status: {}, user: null };

export const auth = {
  namespaced: true,
  state: initialState,
  actions: {
    login({ commit }, user) {
      return AuthService.login(user).then(
        user => {
          commit('loginSuccess', user);
          return Promise.resolve(user);
        },
        error => {
          commit('loginFailure');
          return Promise.reject(error.response.data);
        }
      );
    },
    logout({ commit }) {
      AuthService.logout();
      commit('logout');
    },
    register({ commit }, user) {
      return AuthService.register(user).then(
        response => {
          commit('registerSuccess');
          return Promise.resolve(response.data);
        },
        error => {
          commit('registerFailure');
          return Promise.reject(error.response.data);
        }
      );
    }
  },
  mutations: {
    loginSuccess(state, user) {
      state.status = { loggedIn: true };
      state.user = user;
    },
    loginFailure(state) {
      state.status = {};
      state.user = null;
    },
    logout(state) {
      state.status = {};
      state.user = null;
    },
    registerSuccess(state) {
      state.status = {};
    },
    registerFailure(state) {
      state.status = {};
    }
  }
};

You can find more details about Vuex at Vuex Guide.

Create Vue Authentication Components

Define User model

To make code clear and easy to read, we define the User model first.
Under src/models folder, create user.js like this.

export default class User {
  constructor(username, email, password) {
    this.username = username;
    this.email = email;
    this.password = password;
  }
}

Let’s continue with Authentication Components.
Instead of using axios or AuthService directly, these Components should work with Vuex Store:
– getting status with this.$store.state.auth
– making request by dispatching an action: this.$store.dispatch()


views

Login.vue

Register.vue

Profile.vue


Vue Login Page

In src/views folder, create Login.vue file with following code:

<template>
  <div class="col-md-12">
    <div class="card card-container">
      <img
        id="profile-img"
        src="//ssl.gstatic.com/accounts/ui/avatar_2x.png"
        class="profile-img-card"
      />
      <form name="form" @submit.prevent="handleLogin">
        <div class="form-group">
          <label for="username">Username</label>
          <input
            type="text"
            class="form-control"
            name="username"
            v-model="user.username"
            v-validate="'required'"
          />
          <div
            class="alert alert-danger"
            role="alert"
            v-if="errors.has('username')"
          >Username is required!</div>
        </div>
        <div class="form-group">
          <label for="password">Password</label>
          <input
            type="password"
            class="form-control"
            name="password"
            v-model="user.password"
            v-validate="'required'"
          />
          <div
            class="alert alert-danger"
            role="alert"
            v-if="errors.has('password')"
          >Password is required!</div>
        </div>
        <div class="form-group">
          <button class="btn btn-primary btn-block" :disabled="loading">
            <span class="spinner-border spinner-border-sm" v-show="loading"></span>
            <span>Login</span>
          </button>
        </div>
        <div class="form-group">
          <div class="alert alert-danger" role="alert" v-if="message">{{message}}</div>
        </div>
      </form>
    </div>
  </div>
</template>

<script>
import User from '../models/user';

export default {
  name: 'login',
  computed: {
    loggedIn() {
      return this.$store.state.auth.status.loggedIn;
    }
  },
  data() {
    return {
      user: new User('', ''),
      loading: false,
      message: ''
    };
  },
  mounted() {
    if (this.loggedIn) {
      this.$router.push('/profile');
    }
  },
  methods: {
    handleLogin() {
      this.loading = true;
      this.$validator.validateAll();

      if (this.errors.any()) {
        this.loading = false;
        return;
      }

      if (this.user.username && this.user.password) {
        this.$store.dispatch('auth/login', this.user).then(
          () => {
            this.$router.push('/profile');
          },
          error => {
            this.loading = false;
            this.message = error.message;
          }
        );
      }
    }
  }
};
</script>

<style scoped>
label {
  display: block;
  margin-top: 10px;
}

.card-container.card {
  max-width: 350px !important;
  padding: 40px 40px;
}

.card {
  background-color: #f7f7f7;
  padding: 20px 25px 30px;
  margin: 0 auto 25px;
  margin-top: 50px;
  -moz-border-radius: 2px;
  -webkit-border-radius: 2px;
  border-radius: 2px;
  -moz-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
  -webkit-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
  box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
}

.profile-img-card {
  width: 96px;
  height: 96px;
  margin: 0 auto 10px;
  display: block;
  -moz-border-radius: 50%;
  -webkit-border-radius: 50%;
  border-radius: 50%;
}
</style>

This page has a Form with username & password. We use [VeeValidate 2.x](http://<a href=) to validate input before submitting the form. If there is an invalid field, we show the error message.

We check user logged in status using Vuex Store: this.$store.state.auth.status.loggedIn. If the status is true, we use Vue Router to direct user to Profile Page:

created() {
  if (this.loggedIn) {
    this.$router.push('/profile');
  }
},

In the handleLogin() function, we dispatch 'auth/login' Action to Vuex Store. If the login is successful, go to Profile Page, otherwise, show error message.

Vue Register Page

This page is similar to Login Page.

For form validation, we have some more details:

  • username: required|min:3|max:20
  • email: required|email|max:50
  • password: required|min:6|max:40

For form submission, we dispatch 'auth/register' Vuex Action.

src/views/Register.vue

<template>
  <div class="col-md-12">
    <div class="card card-container">
      <img
        id="profile-img"
        src="//ssl.gstatic.com/accounts/ui/avatar_2x.png"
        class="profile-img-card"
      />
      <form name="form" @submit.prevent="handleRegister">
        <div v-if="!successful">
          <div class="form-group">
            <label for="username">Username</label>
            <input
              type="text"
              class="form-control"
              name="username"
              v-model="user.username"
              v-validate="'required|min:3|max:20'"
            />
            <div
              class="alert-danger"
              v-if="submitted && errors.has('username')"
            >{{errors.first('username')}}</div>
          </div>
          <div class="form-group">
            <label for="email">Email</label>
            <input
              type="email"
              class="form-control"
              name="email"
              v-model="user.email"
              v-validate="'required|email|max:50'"
            />
            <div
              class="alert-danger"
              v-if="submitted && errors.has('email')"
            >{{errors.first('email')}}</div>
          </div>
          <div class="form-group">
            <label for="password">Password</label>
            <input
              type="password"
              class="form-control"
              name="password"
              v-model="user.password"
              v-validate="'required|min:6|max:40'"
            />
            <div
              class="alert-danger"
              v-if="submitted && errors.has('password')"
            >{{errors.first('password')}}</div>
          </div>
          <div class="form-group">
            <button class="btn btn-primary btn-block">Sign Up</button>
          </div>
        </div>
      </form>

      <div
        class="alert"
        :class="successful ? 'alert-success' : 'alert-danger'"
        v-if="message"
      >{{message}}</div>
    </div>
  </div>
</template>

<script>
import User from '../models/user';

export default {
  name: 'register',
  computed: {
    loggedIn() {
      return this.$store.state.auth.status.loggedIn;
    }
  },
  data() {
    return {
      user: new User('', '', ''),
      submitted: false,
      successful: false,
      message: ''
    };
  },
  mounted() {
    if (this.loggedIn) {
      this.$router.push('/profile');
    }
  },
  methods: {
    handleRegister() {
      this.message = '';
      this.submitted = true;
      this.$validator.validate().then(valid => {
        if (valid) {
          this.$store.dispatch('auth/register', this.user).then(
            data => {
              this.message = data.message;
              this.successful = true;
            },
            error => {
              this.message = error.message;
              this.successful = false;
            }
          );
        }
      });
    }
  }
};
</script>

<style scoped>
label {
  display: block;
  margin-top: 10px;
}

.card-container.card {
  max-width: 350px !important;
  padding: 40px 40px;
}

.card {
  background-color: #f7f7f7;
  padding: 20px 25px 30px;
  margin: 0 auto 25px;
  margin-top: 50px;
  -moz-border-radius: 2px;
  -webkit-border-radius: 2px;
  border-radius: 2px;
  -moz-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
  -webkit-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
  box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
}

.profile-img-card {
  width: 96px;
  height: 96px;
  margin: 0 auto 10px;
  display: block;
  -moz-border-radius: 50%;
  -webkit-border-radius: 50%;
  border-radius: 50%;
}
</style>

Profile Page

This page gets current User from Vuex Store and show information. If the User is not logged in, it directs to Login Page.

src/views/Profile.vue

<template>
  <div class="container">
    <header class="jumbotron">
      <h3>
        <strong>{{currentUser.username}}</strong> Profile
      </h3>
    </header>
    <p>
      <strong>Token:</strong>
      {{currentUser.accessToken.substring(0, 20)}} ... {{currentUser.accessToken.substr(currentUser.accessToken.length - 20)}}
    </p>
    <p>
      <strong>Id:</strong>
      {{currentUser.id}}
    </p>
    <p>
      <strong>Email:</strong>
      {{currentUser.email}}
    </p>
    <strong>Authorities:</strong>
    <ul>
      <li v-for="(role,index) in currentUser.roles" :key="index">{{role}}</li>
    </ul>
  </div>
</template>

<script>
export default {
  name: 'profile',
  computed: {
    currentUser() {
      return this.$store.state.auth.user;
    }
  },
  mounted() {
    if (!this.currentUser) {
      this.$router.push('/login');
    }
  }
};
</script>

Create Vue Components for accessing Resources

These components will use UserService to request data.


views

Home.vue

BoardAdmin.vue

BoardModerator.vue

BoardUser.vue


Home Page

This is a public page.

src/views/Home.vue

<template>
  <div class="container">
    <header class="jumbotron">
      <h3>{{content}}</h3>
    </header>
  </div>
</template>

<script>
import UserService from '../services/user.service';

export default {
  name: 'home',
  data() {
    return {
      content: ''
    };
  },
  mounted() {
    UserService.getPublicContent().then(
      response => {
        this.content = response.data;
      },
      error => {
        this.content = error.response.data.message;
      }
    );
  }
};
</script>

Role-based Pages

We have 3 pages for accessing protected data:

  • BoardUser page calls UserService.getUserBoard()
  • BoardModerator page calls UserService.getModeratorBoard()
  • BoardAdmin page calls UserService.getAdminBoard()

This is an example, other Page are similar to this Page.

src/views/BoardUser.vue

<template>
  <div class="container">
    <header class="jumbotron">
      <h3>{{content}}</h3>
    </header>
  </div>
</template>

<script>
import UserService from '../services/user.service';

export default {
  name: 'user',
  data() {
    return {
      content: ''
    };
  },
  mounted() {
    UserService.getUserBoard().then(
      response => {
        this.content = response.data;
      },
      error => {
        this.content = error.response.data.message;
      }
    );
  }
};
</script>

Define Routes for Vue Router

Now we define all routes for our Vue Application.

src/router.js

import Vue from 'vue';
import Router from 'vue-router';
import Home from './views/Home.vue';
import Login from './views/Login.vue';
import Register from './views/Register.vue';

Vue.use(Router);

export const router = new Router({
  mode: 'history',
  routes: [
    {
      path: '/',
      name: 'home',
      component: Home
    },
    {
      path: '/home',
      component: Home
    },
    {
      path: '/login',
      component: Login
    },
    {
      path: '/register',
      component: Register
    },
    {
      path: '/profile',
      name: 'profile',
      // lazy-loaded
      component: () => import('./views/Profile.vue')
    },
    {
      path: '/admin',
      name: 'admin',
      // lazy-loaded
      component: () => import('./views/BoardAdmin.vue')
    },
    {
      path: '/mod',
      name: 'moderator',
      // lazy-loaded
      component: () => import('./views/BoardModerator.vue')
    },
    {
      path: '/user',
      name: 'user',
      // lazy-loaded
      component: () => import('./views/BoardUser.vue')
    }
  ]
});

Add Navigation Bar to Vue App

This is the root container for our application that contains navigation bar. We will add router-view here.

src/App.vue

<template>
  <div id="app">
    <nav class="navbar navbar-expand navbar-dark bg-dark">
      <a href="#" class="navbar-brand">bezKoder</a>
      <div class="navbar-nav mr-auto">
        <li class="nav-item">
          <a href="/home" class="nav-link">
            <font-awesome-icon icon="home" /> Home
          </a>
        </li>
        <li class="nav-item" v-if="showAdminBoard">
          <a href="/admin" class="nav-link">Admin Board</a>
        </li>
        <li class="nav-item" v-if="showModeratorBoard">
          <a href="/mod" class="nav-link">Moderator Board</a>
        </li>
        <li class="nav-item">
          <a href="/user" class="nav-link" v-if="currentUser">User</a>
        </li>
      </div>

      <div class="navbar-nav ml-auto" v-if="!currentUser">
        <li class="nav-item">
          <a href="/register" class="nav-link">
            <font-awesome-icon icon="user-plus" /> Sign Up
          </a>
        </li>
        <li class="nav-item">
          <a href="/login" class="nav-link">
            <font-awesome-icon icon="sign-in-alt" /> Login
          </a>
        </li>
      </div>

      <div class="navbar-nav ml-auto" v-if="currentUser">
        <li class="nav-item">
          <a href="/profile" class="nav-link">
            <font-awesome-icon icon="user" />
            {{currentUser.username}}
          </a>
        </li>
        <li class="nav-item">
          <a href class="nav-link" @click="logOut">
            <font-awesome-icon icon="sign-out-alt" /> LogOut
          </a>
        </li>
      </div>
    </nav>

    <div class="container">
      <router-view />
    </div>
  </div>
</template>

<script>
export default {
  computed: {
    currentUser() {
      return this.$store.state.auth.user;
    },
    showAdminBoard() {
      if (this.currentUser) {
        return this.currentUser.roles.includes('ROLE_ADMIN');
      }

      return false;
    },
    showModeratorBoard() {
      if (this.currentUser) {
        return this.currentUser.roles.includes('ROLE_MODERATOR');
      }

      return false;
    }
  },
  methods: {
    logOut() {
      this.$store.dispatch('auth/logout');
      this.$router.push('/login');
    }
  }
};
</script>

Our navbar looks more professional when using font-awesome-icon.
We also make the navbar dynamically change by current User’s roles which are retrieved from Vuex Store state.

Handle Unauthorized Access

If you want to check Authorized status everytime a navigating action is trigger, just add router.beforeEach() at the end of src/router.js like this:

router.beforeEach((to, from, next) => {
  const publicPages = ['/login', '/home'];
  const authRequired = !publicPages.includes(to.path);
  const loggedIn = localStorage.getItem('user');

  // try to access a restricted page + not logged in
  if (authRequired && !loggedIn) {
    return next('/login');
  }

  next();
});

Conclusion

Congratulation!

Today we’ve done so many interesting things. I hope you understand the overall layers of our Vue application, and apply it in your project at ease. Now you can build a front-end app that supports JWT Authentication with Vue.js, Vuex and Vue Router.

Happy learning, see you again!

Things I love about Vue

Things I love about Vue

10 things I love about Vue. Minimal Template Syntax. The template syntax which you are given by default from Vue is minimal, succinct and extendable. Single File Components. Vue as the new jQuery. Easily extensible. Virtual DOM. Vuex is great. Vue CLI. Re-rendering optimizations worked out for you.

I’d already had experience with Backbone, Angular, React, among others and I wasn’t overly enthusiastic to try a new framework. It wasn’t until I read a comment on hacker news describing Vue as the ‘new jquery’ of JavaScript, that my curiosity was piqued. Until that point, I had been relatively content with React — it is a good framework based on solid design principles centred around view templates, virtual DOM and reacting to state, and Vue also provides these great things. In this blog post, I aim to explore why Vue is the framework for me. I choose it above any other that I have tried. Perhaps you will agree with some of my points, but at the very least I hope to give you some insight into what it is like to develop modern JavaScript applications with Vue.

1. Minimal Template Syntax

The template syntax which you are given by default from Vue is minimal, succinct and extendable. Like many parts of Vue, it’s easy to not use the standard template syntax and instead use something like JSX (there is even an official page of documentation about how to do this), but I don’t know why you would want to do that to be honest. For all that is good about JSX, there are some valid criticisms: by blurring the line between JavaScript and HTML, it makes it a bit too easy to start writing complex code in your template which should instead be separated out and written elsewhere in your JavaScript view code.

Vue instead uses standard HTML to write your templates, with a minimal template syntax for simple things such as iteratively creating elements based on the view data.

<template>
  <div id="app">
    <ul>
      <li v-for='number in numbers' :key='number'>{{ number }}</li>
    </ul>
    <form @submit.prevent='addNumber'>
      <input type='text' v-model='newNumber'>
      <button type='submit'>Add another number</button>
    </form>
  </div>
</template>

<script>
export default {
  name: 'app',
  methods: {
    addNumber() {
      const num = +this.newNumber;
      if (typeof num === 'number' && !isNaN(num)) {
        this.numbers.push(num);
      }
    }
  },
  data() {
    return {
      newNumber: null,
      numbers: [1, 23, 52, 46]
    };
  }
}
</script>

<style lang="scss">
ul {
  padding: 0;
  li {
    list-style-type: none;
    color: blue;
  }
}
</style>

I also like the short-bindings provided by Vue, ‘:’ for binding data variables into your template and ‘@’ for binding to events. It’s a small thing, but it feels nice to type and keeps your components succinct.

2. Single File Components

When most people write Vue, they do so using ‘single file components’. Essentially it is a file with the suffix .vue containing up to 3 parts (the css, html and javascript) for each component.

This coupling of technologies feels right. It makes it easy to understand each component in a single place. It also has the nice side effect of encouraging you to keep your code short for each component. If the JavaScript, CSS and HTML for your component is taking up too many lines then it might be time to modularise further.

When it comes to the tag of a Vue component, we can add the ‘scoped’ attribute. This will fully encapsulate the styling to this component. Meaning if we had a .name CSS selector defined in this component, it won’t apply that style in any other component. I much prefer this approach of styling view components to the approaches of writing CSS in JS which seems popular in other leading frameworks.

Another very nice thing about single file components is that they are actually valid HTML5 files. , , are all part of the official w3c specification. This means that many tools you use as part of your development process (such as linters) can work out of the box or with minimal adaptation.

3. Vue as the new jQuery

Really these two libraries are not similar and are doing different things. Let me provide you with a terrible analogy that I am actually quite fond of to describe the relationship of Vue and jQuery: The Beatles and Led Zeppelin. The Beatles need no introduction, they were the biggest group of the 1960s and were supremely influential. It gets harder to pin the accolade of ‘biggest group of the 1970s’ but sometimes that goes to Led Zeppelin. You could say that the musical relationship between the Beatles and Led Zeppelin is tenuous and their music is distinctively different, but there is some prior art and influence to accept. Maybe 2010s JavaScript world is like the 1970s music world and as Vue gets more radio plays, it will only attract more fans.

Some of the philosophy that made jQuery so great is also present in Vue: a really easy learning curve but with all the power you need to build great web applications based on modern web standards. At its core, Vue is really just a wrapper around JavaScript objects.

4. Easily extensible

As mentioned, Vue uses standard HTML, JS and CSS to build its components as a default, but it is really easy to plug in other technologies. If we want to use pug instead of HTML or typescript instead of JS or sass instead of CSS, it’s just a matter of installing the relevant node modules and adding an attribute to the relevant section of our single file component. You could even mix and match components within a project — e.g. some components using HTML and others using pug — although I’m not sure doing this is the best practice.

5. Virtual DOM

The virtual DOM is used in many frameworks these days and it is great. It means the framework can work out what has changed in our state and then efficiently apply DOM updates, minimizing re-rendering and optimising the performance of our application. Everyone and their mother has a Virtual DOM these days, so whilst it’s not something unique, it’s still very cool.

6. Vuex is great

For most applications, managing state becomes a tricky issue which using a view library alone can not solve. Vue’s solution to this is the vuex library. It’s easy to setup and integrates very well with vue. Those familiar with redux will be at home here, but I find that the integration between vue and vuex is neater and more minimal than that of react and redux. Soon-to-be-standard JavaScript provides the object spread operator which allows us to merge in state or functions to manipulate state from vuex into the components that need it.

7. Vue CLI

The CLI provided by Vue is really great and makes it easy to get started with a webpack project with Vue. Single file components support, babel, linting, testing and a sensible project structure can all be created with a single command in your terminal.

There is one thing, however I miss from the CLI and that is the ‘vue build’.

It looked so simple to build and run components and test them in the browser. Unfortunately this command was later removed from vue, instead the recommendation is to now use poi. Poi is basically a wrapper around webpack, but I don’t think it quite gets to the same point of simplicity as the quoted tweet.

8. Re-rendering optimizations worked out for you

In Vue, you don’t have to manually state which parts of the DOM should be re-rendered. I never was a fan of the management on react components, such as ‘shouldComponentUpdate’ in order to stop the whole DOM tree re-rendering. Vue is smart about this.

9. Easy to get help

Vue has reached a critical mass of developers using the framework to build a wide variety of applications. The documentation is very good. Should you need further help, there are multiple channels available with many active users: stackoverflow, discord, twitter etc. — this should give you some more confidence in building an application over some other frameworks with less users.

10. Not maintained by a single corporation

I think it’s a good thing for an open source library to not have the voting rights of its direction steered too much by a single corporation. Issues such as the react licensing issue (now resolved) are not something Vue has had to deal with.

In summary, I think Vue is an excellent choice for whatever JavaScript project you might be starting next. The available ecosystem is larger than I covered in this blog post. For a more full-stack offering you could look at Nuxt.js. And if you want some re-usable styled components you could look at something like Vuetify.

Thank you for reading !