How to use VueJS with NodeJS?

Vue.js Authentication System with Node.js Backend

Vue.js Authentication System with Node.js Backend

Vue.js Authentication System with Node.js Backend - In this tutorial, we'll explore different authentication types for JavaScript applications and build a Vue authentication system with a Node.js ...

Vue.js Authentication System with Node.js Backend - In this tutorial, we'll explore different authentication types for JavaScript applications and build a Vue authentication system with a Node.js ...

When building a Vue.js authentication system, there are two primary scenarios involved. Scenario one: one party controls both the front-end and back-end; scenario two: a third-party controls the back-end. If it is the latter case, we have to adapt the front-end. It has to deal with whatever authentication type becomes available from the back-end.

The finished code for this tutorial is available at these GitHub repositories:

Front-end JavaScript Authentication Types

In our application, we have the flexibility to choose between various authentication types. This is because we will be in charge of both front-end and back-end. Let us identify the two types. The first one is local or same-domain authentication — this is the case when the front-end and the back-end are both running on the same domain. The second is cross-domain authentication — it is when the front-end and back-end are running on different domains.

These are the two main categories but there are many sub-categories under them. In light of the above, we will use local authentication since we are in charge of the whole system. We will be using many Node.js libraries. But the two main ones are Passport.js and Express.js. Passport.js is an authentication library. It provides several features like local authentication, OAuth authentication and Single Sign-On authentication. Express.js is a server framework for Node.js used for building web applications.

The Application Paradigm

Our application front-end will have two main pages: a login page and a dashboard page. Both authenticated and anonymous users will have access to the login page. The dashboard page will only be accessible to authenticated users. The login page will have a form which will submit data through Ajax to our back-end API. Then, the back-end will check if the credentials are correct and reply back to the front-end with a cookie. This cookie is what the front-end will use to gain access to any locked pages.

Revalidation of the cookie happens on every request to a locked page. If the cookie becomes invalid or the user is not logged in, they cannot access the dashboard. The back-end will send an error response and the front-end will know to redirect the user back to the login page.

We will not be setting up a real database — we will use an array of users in the back-end to mimic some form of a database. Finally, we will have a logout link. This will send a request to our server to invalidate our current session and hence log out the current user.

So, let’s begin building our Vue.js authentication system using Node.js as a back-end.

Vue.js Front-End Setup

To begin with, we first need to have the latest version of Node.js and vue-cli setup. At the time of this article, the latest version of vue-cli is version 3. If the installed version is 2, we want to upgrade — we first need to remove the old version by running:

npm uninstall vue-cli -g


Then install the latest version by running:

npm install -g @vue/cli


followed by

npm install -g @vue/cli-init


After setting up the above, go to any folder in the terminal and run:

vue init webpack vueauthclient


This will create a new application in vueauthclient using the webpack folder organization.

We should get some prompts on the command line. It is safe to select all the defaults — but for this article, we can select “no” for the tests. Next, navigate to this folder using cd vueauthclient and run the application using:

npm run dev


This will launch a development server which is accessible at the URL localhost:8080. After visiting this URL, the Vue.js logo with some text should be visible on the page. The Vue.js component responsible for displaying this page lives in the file:

vueauthclient/src/components/HelloWorld.vue


Main Login Screen

Let us set up our login page. Then, we will change the homepage to default to the login page screen which we are yet to create. From now on, we will leave out the main application folder vueauthclient, when referring to files.

Let us install the Ajax library called Axios using:

npm install axios --save


This is a library which makes it easier to do HTTP Ajax calls to any back-end server. It is available for both front-end and back-end applications but here, we will only use it on the front-end.

Next, create a login component file in src/components/Login.vue. In this file, paste the following:

<template>
    <div>    
        <h2>Login</h2>    
        <form v-on:submit="login">    
            <input type="text" name="email" /><br>    
            <input type="password" name="password" /><br>    
            <input type="submit" value="Login" />    
        </form>    
    </div>
</template>

<script>
    import router from "../router"    
    import axios from "axios"    
    export default {    
        name: "Login",    
        methods: {    
            login: (e) => {    
                e.preventDefault()    
                let email = "[email protected]"   
                let password = "password"    
                let login = () => {    
                    let data = {    
                        email: email,    
                        password: password    
                    }    
                    axios.post("/api/login", data)    
                        .then((response) => {    
                            console.log("Logged in")    
                            router.push("/dashboard")    
                        })    
                        .catch((errors) => {    
                            console.log("Cannot log in")    
                        })    
                }    
                login()    
            }    
        }    
    }
</script>


Let’s break down this code to see what is happening.

The template part below is a form with two input fields: email and password. The form has a submit event handler attached to it. Using the Vue.js syntax v-on:submit="login", this will submit the field data to the login component method.

<template>
    <div>
        <h2>Login</h2>
        <form v-on:submit="login">
            <input type="text" name="email" /><br>
            <input type="password" name="password" /><br>    
            <input type="submit" value="Login" />    
        </form>    
    </div>
</template>


In the script part of the code, as shown below, we are importing our router file. This lives in src/router/index.js. We are also importing the Axios ajax library for the front-end. Then, we are storing the user credentials and making a login request to our back-end server:

<script>
    import router from "../router"        
    import axios from "axios"    
    export default {    
        name: "Login",    
        methods: {    
            login: (e) => {    
                e.preventDefault()   
                let email = "[email protected]"
                let password = "password"
                let login = () => {
                    let data = {
                        email: email,
                        password: password
                    }
                    axios.post("/api/login", data)
                        .then((response) => {
                            console.log("Logged in")
                            router.push("/dashboard")
                        })
                        .catch((errors) => {
                            console.log("Cannot login")
                        })
                }
                login()
            }
        }
    }
</script>


In the script area below,

e.preventDefault()
let email = "[[email protected]](mailto:[email protected])"
let password = "password"


We are storing hard-coded username and password in variables for now. This helps speed up development by preventing us from retyping the same thing. Later, we will switch those out and get the real data from the form submission.

In the final part of the code below, we are making an ajax call using the credentials above. In the case of an ok response from the server, we redirect the user to the dashboard. If the response is not ok, we stay on the same page and log an error in the console.

let login = () => {
  let data = {
    email: email,
    password: password
  }
  axios.post("/api/login", data)
    .then(response => {
      console.log("Logged in")
      router.push("/dashboard")
    })
    .catch(errors => {
      console.log("Cannot login")
    })
}
login()


Now that we have our login component set up, let’s change the router to make sure it recognizes the new page. In the file src/router/index.js, change the existing router to this:

import Vue from "vue"
import Router from "vue-router"
import Login from "@/components/Login"
import HelloWorld from "@/components/HelloWorld"
Vue.use(Router)
export default new Router({
  routes: [
    {
      path: "/",
      name: "HelloWorld",
      component: HelloWorld
    },
    {
      path: "/login",
      name: "Login",
      component: Login
    }
  ]
})


What we’ve done is import our new component, then add an object to the routes array. Remove the HelloWorld route registration, as we won’t be needing it anymore.

Finally, for the login, page, let’s make sure it is the default page of our application. Change the current path of the login route registration from

path: "/login",


to

path: "/",


Don’t forget to delete the route registration for the HelloWorld route or else an error might occur. Navigating to localhost:8080 again in the browser, we should see our new login form. Submitting it at this stage will not do anything except complain that the back-end URL localhost:8080/api/login does not exist.

Setup First Secure Page - The Dashboard

Now onto the dashboard page. Create a component for it by making a file at src/components/Dashboard.vue. In there, paste the following:

<template>
    <div>    
        <h2>Dashboard</h2>    
        <p>Name: {{ user.name }}</p>    
    </div>
</template>
<script>
    import axios from "axios"    
    import router from "../router"    
    export default {    
        name: "Login",    
        data() {    
            return {    
                user: {    
                    name: “Jesse”    
                }    
            }    
        },    
        methods: {    
            getUserData: function() {    
                let self = this    
                axios.get("/api/user")    
                    .then((response) => {    
                        console.log(response)    
                        self.$set(this, "user", response.data.user)    
                    })    
                    .catch((errors) => {    
                        console.log(errors)    
                        router.push("/")    
                    })    
            }    
        },    
        mounted() {    
            this.getUserData()    
        }    
    }
</script>


In the template section, we are displaying the current username. Before setting up the back-end, we will hardcode a user in the front-end. This is so that we can work with this page or else we will get an error.

In the script section, we are importing Axios library and our router. Then, we have a data function for our component where we return an object with a user property. As we can see, we currently have some hardcoded user data.

We also have two methods called getUserData and mounted. The Vue.js engine calls the mounted method when the component has opened. We only have to declare it. The second method, getUserData is called in the mounted method. In there, we are making a call to the back-end server to fetch the data for the currently logged in user.

During the call to the back-end, we get a response from the server. We will have to handle two possible scenarios depending on the response type.

First, if the call was successful, we set the user property with the data returned from the back-end using:

self.$set(this, "user", response.data.user)


Secondly, if there was a login issue, the server responds with an error. Then, the front-end redirects the user back to the login page with this line:

router.push("/")


We use the push method above for redirection and it is available in the package called vue-router, the default router for Vue.js. Let’s add in the route config for this page by adding this to the route file, like we did for the login page. Import the component:

import Dashboard from "@/components/Dashboard"


And add the route definition:

{
    path: "/dashboard",
    name: "Dashboard",
    component: Dashboard
}


Setup Front-End Data Layer with Axios

Now that we have our front-end pages in place, let’s configure Axios and Vue.js. We will make them ready to communicate with our back-end. Because we are in the development phase, the front-end is running on port 8080. Once we start developing our back-end server, it will be running on a different port number 3000. This will be the case until we are ready for production.

There is nothing stopping us from running them on the same port. In fact, will eventually be the case in the end. If we recollect, we are going for the same-domain approach. We will run the back-end and front-end on different ports for now. This is because we want to take advantage of the many useful features of the Vue.js development server. We will touch on how to merge the two (front and back-end) in a later chapter.

Before moving on, let’s highlight one issue here. There is a drawback to developing our application on different ports. It is called Cross-Origin Request Sharing, shortly named CORS. By default, it will not allow us to make cross-domain Ajax requests to our back-end. There is a Node.js library to find a way around that but we will leave that for another tutorial.

The Vue.js development server has something called proxying. It allows our back-end server to think that the front-end is running on the same port as itself. To enable that feature, open up the config file in config/index.js. Under the dev property, add in an object like so:

proxyTable: {

"/api": "http://localhost:3000"

},


In the above code, we are rerouting Ajax requests that begin with /api to the URL [http://localhost:3000](http://localhost:3000 "http://localhost:3000"). Notice that this is different from the URL our front-end application is running on. If we did not have this code, the Ajax requests by default are sent to [http://localhost:8080](http://localhost:8080 "http://localhost:8080"), which is not what we want. When ready for production, we can remove it:

Finally, install the front-end cookie library using:

npm install vue-cookies --save


Securing our Back-End API

Let’s now move onto setting up a Node.js back-end. First of all, we need to have Node.js installed on your system for this part as well. Head over to a terminal window. Create an empty folder called vueauthclient-backend. Navigate to the folder using:

cd vueauthclient-backend


Then initialize a new Node.js application using the command:

npm init


There will be several prompts. Let’s accept the defaults and specify values where required. We should end up with a file called package.json. Create a file called index.js in the project’s root directory. This is where our main code will live. Install several libraries using the command:

npm install --save body-parser cookie-session express passport passport-local


At the top of the index.js file, import the libraries using the code:

const express = require('express')

// creating an express instance
const app = express()
const cookieSession = require('cookie-session')
const bodyParser = require('body-parser')
const passport = require('passport')

// getting the local authentication type
const LocalStrategy = require('passport-local').Strategy


First, let’s initialize the cookie-session and the body-parser libraries using:

app.use(bodyParser.json())

app.use(cookieSession({
    name: 'mysession',
    keys: ['vueauthrandomkey'],
    maxAge: 24 * 60 * 60 * 1000 // 24 hours
}))


We are setting the cookie to expire after 24 hours. Next, let’s instruct our Node.js app that we want to use Passport.js. Do that by adding the line:

app.use(passport.initialize());


Next, tell Passport.js to start its session management system:

app.use(passport.session());


Since we won’t be using a real database for managing users, for brevity’s sake we will use an array for that. Add in the following lines:

let users = [
  {
    id: 1,
    name: "Jude",
    email: "[email protected]",
    password: "password"
  },
  {
    id: 2,
    name: "Emma",
    email: "[email protected]",
    password: "password2"
  }
]


Next, let’s set up the URLs for logging in, logging out and getting user data. These will be found at POST /api/login, GET /api/logout and GET /api/user, respectively. For the login part, paste in the following:

app.post("/api/login", (req, res, next) => {
  passport.authenticate("local", (err, user, info) => {
    if (err) {
      return next(err);
    }

    if (!user) {
      return res.status(400).send([user, "Cannot log in", info]);
    }

    req.login(user, err => {
      res.send("Logged in");
    });
  })(req, res, next);
});


In here, we are instructing Express.js to authenticate the user using the supplied credentials. If an error occurs or if it fails, we return an error message to the front-end. If the user gets logged in, we will respond with a success message. Passport.js handles the checking of credentials. We will set that up shortly. Note that the method passport.authenticate resides in the Passport.js library.

The next URL we will set up is logout. This invalidates our cookie if one exists. Add this to achieve the functionality:

app.get("/api/logout", function(req, res) {
  req.logout();

  console.log("logged out")

  return res.send();
});


Finally, the URL to get the currently logged in users’ data. When logged in, Passport.js adds a user object to the request using the cookie from the front-end as an identifier. We have to use the id from that object to get the required user data from our array of data in the back-end. Paste in the following:

app.get("/api/user", authMiddleware, (req, res) => {
  let user = users.find(user => {
    return user.id === req.session.passport.user
  })

  console.log([user, req.session])

  res.send({ user: user })
})


Notice that, this time, we have a second variable we are passing in before the callback. This is because we want to protect this URL, so we are passing a middleware filter. This filter will check if the current session is valid before allowing the user to proceed with the rest of the operation. Let’s create the middleware using:

const authMiddleware = (req, res, next) => {
  if (!req.isAuthenticated()) {
    res.status(401).send('You are not authenticated')
  } else {
    return next()
  }
}


We have to make sure to declare it before creating the API route for /api/user.

Next, let’s configure Passport.js so it knows how to log us in. After logging in, it will store the user object data in a cookie-session, and retrieve the data on later requests. To configure Passport.js using the local strategy, add in the following:

passport.use(
  new LocalStrategy(
    {
      usernameField: "email",
      passwordField: "password"
    },

    (username, password, done) => {
      let user = users.find((user) => {
        return user.email === username && user.password === password
      })

      if (user) {
        done(null, user)
      } else {
        done(null, false, { message: 'Incorrect username or password'})
      }
    }
  )
)


In here, we are instructing Passport.js to use the LocalStrategy we created above. We are also specifying which fields to expect from the front-end as it needs a username and password. Then, we are using those values to query the user. If these are valid, we call the done callback, which will store the user object in the session. If it is not valid, we will call the done callback with a false value and return with an error. One thing to note is that the above code works in conjunction with the login URL. The call to passport.authenticate in that URL callback triggers the above code.

Next, let’s tell Passport.js how to handle a given user object. This is necessary if we want to do some work before storing it in session. In this case, we only want to store the id as it is enough to identify the user when we extract it from the cookie. Add in the following to achieve that:

passport.serializeUser((user, done) => {
  done(null, user.id)
})


Next, let’s set up the reverse. When a user makes a request for a secured URL. We tell passport how to retrieve the user object from our array of users. It will use the id we stored using the serializeUser method to achieve this. Add this:

passport.deserializeUser((id, done) => {
  let user = users.find((user) => {
    return user.id === id
  })

  done(null, user)
})


Now, let’s add the code which boots up the Node.js server using the following:

app.listen(3000, () => {
  console.log("Example app listening on port 3000")
})


Run the command:

node index.js


This actually starts the server. There will be a message in the console with the text Example app listening on port 3000.

Getting Ready for Production

Now, when we visit the page localhost:8080, we should see a login form. When we submit the form, we get redirected to the dashboard page. We achieve this using the proxy we set up earlier.

This is acceptable for development — but it defeats the purpose of having a same-domain application. To have a same-domain scenario, we need to compile our application for production.

Before that, let’s test that the proxy is working. Comment out the proxy URL code in config/index.js. We may need to restart the development server because we changed a config file.

Now, let’s revisit the login page and submit the form. We will get an error saying that we aren’t allowed access to the back-end server. To get around this, we need to configure our Node.js back-end server. The back-end will now serve our front-end application instead of the development server.

In the console for the front-end, run the command:

npm run build


This will generate all the necessary files needed for production. We can find all the created files from this command in the dist folder. From this point onwards, we have two options: we can either copy this folder over so it is part of our Node.js application or we can tell the Node.js server to refer directly to it on our file system. The latter is useful if we still want them as separate repositories. We will use the latter method.

Navigate to the folder dist. Run the command pwd to get the absolute path of the dist folder, assuming we are on a Linux based system or Mac. If we are on Windows, we can get the absolute path to the folder using an equivalent command.

Copy the absolute path but do not forget to restart the Node.js server after any modification. Since we do not want to keep restarting the server, let’s install nodemon. It can handle that for us when our code changes.

Next, paste in the following after the import statements:

const publicRoot = '/absolute/path/to/dist'

app.use(express.static(publicRoot))


This is telling the server where to look for files.

The final step will be to add a route to the root of our Node.js application. This is so it serves the production-ready code we had compiled. Do that by adding:

app.get("/", (req, res, next) => {
  res.sendFile("index.html", { root: publicRoot })
})


Now, even with the proxy disabled, let’s visit the server root localhost:3000. We will see the login form. Submit this and we should see the dashboard page with the username displayed.

Logout Functionality and Login Data

Note that our application is still using hardcoded data, we want to get that from the submitted form. Change these lines in the login component from:

let email = "[email protected]"

let password = "password"


to:

let email = e.target.elements.email.value

let password = e.target.elements.password.value


Now, we are using the data from the form. Next, let’s set up a link to log us out. In the component src/App.vue, change the template to this:

<template>
    <div id="app">    
        <img src="./assets/logo.png">    
        <div>    
            <router-link :to="{ name: 'Dashboard'}">Dashboard</router-link>    
            <router-link :to="{ name: 'Login'}">Login</router-link>    
            <a href="#" v-on:click="logout">Logout</a>    
        </div>    
        <router-view/>    
    </div>
</template>


Here, we have created links to the login page, the dashboard page, and a logout link. The logout link does not have a corresponding method currently, so let’s create that. In src/App.vue add a logout method in the scripts section:

logout: function (e) {
    axios
      .get("/api/logout")
      .then(() => {
        router.push("/")
      })
}


Here, we are making an Ajax request to the Node.js back-end. Then, we redirect the user to the login page when the response has returned. The logout will not work with our deployed app because we need to redeploy it again for production using:

npm run build


Now, we can revisit the URL localhost:3000. We can log in, log out and visit the dashboard page.

Conclusion

After this tutorial, we should be able to add as many authenticated pages as we want.

If this is the first time using Vue.js, please refer to our introductory blog post here. It will help in setting up and building a basic Vue.js application.

Also, don’t forget to protect you Vue.js application from code theft and reverse engineering. See our handy guide on protecting Vue.js apps with Jscrambler.

*Originally published by Jscrambler at *blog.jscrambler.com

=========================================================

Thanks for reading :heart: If you liked this post, share it with all of your programming buddies! Follow me on Facebook | Twitter

Learn More

☞ Nuxt.js - Vue.js on Steroids

☞ Vue JS 2 - The Complete Guide (incl. Vue Router & Vuex)

☞ Master Vuejs from scratch (incl Vuex, Vue Router)

☞ Vue JS 2.0 - Mastering Web Apps

☞ Vue.js Essentials - 3 Course Bundle

☞ MEVP Stack Vue JS 2 Course: MySQL + Express.js + Vue.js +PHP

Node.js, ExpressJs, MongoDB and Vue.js (MEVN Stack) Application Tutorial

Node.js, ExpressJs, MongoDB and Vue.js (MEVN Stack) Application Tutorial

In this tutorial, you'll learn how to integrate Vue.js with Node.js backend (using Express framework) and MongoDB and how to build application with Node.js, ExpressJs, MongoDB and Vue.js

In this tutorial, you'll learn how to integrate Vue.js with Node.js backend (using Express framework) and MongoDB and how to build application with Node.js, ExpressJs, MongoDB and Vue.js

Vue.js is a JavaScript framework with growing number of users. Released 4 years ago, it’s now one of the most populare front-end frameworks. There are some reasons why people like Vue.js. Using Vue.js is very simple if you are already familiar with HTML and JavaScript. They also provide clear documentation and examples, makes it easy for starters to learn the framework. Vue.js can be used for both simple and complex applications. If your application is quite complex, you can use Vuex for state management, which is officially supported. In addition, it’s also very flexible that yu can write template in HTML, JavaScript or JSX.

This tutorial shows you how to integrate Vue.js with Node.js backend (using Express framework) and MongoDB. As for example, we’re going to create a simple application for managing posts which includes list posts, create post, update post and delete post (basic CRUD functionality). I divide this tutorial into two parts. The first part is setting up the Node.js back-end and database. The other part is writing Vue.js code including how to build .vue code using Webpack.

Dependencies

There are some dependencies required for this project. Add the dependencies below to your package.json. Then run npm install to install these dependencies.

  "dependencies": {
    "body-parser": "~1.17.2",
    "dotenv": "~4.0.0",
    "express": "~4.16.3",
    "lodash": "~4.17.10",
    "mongoose": "~5.2.9",
    "morgan": "~1.9.0"
  },
  "devDependencies": {
    "axios": "~0.18.0",
    "babel-core": "~6.26.3",
    "babel-loader": "~7.1.5",
    "babel-preset-env": "~1.7.0",
    "babel-preset-stage-3": "~6.24.1",
    "bootstrap-vue": "~2.0.0-rc.11",
    "cross-env": "~5.2.0",
    "css-loader": "~1.0.0",
    "vue": "~2.5.17",
    "vue-loader": "~15.3.0",
    "vue-router": "~3.0.1",
    "vue-style-loader": "~4.1.2",
    "vue-template-compiler": "~2.5.17",
    "webpack": "~4.16.5",
    "webpack-cli": "^3.1.0"
  },

Project Structure

Below is the overview of directory structure for this project.

  app
    config
    controllers
    models
    queries
    routes
    views
  public
    dist
    src

The app directory contains all files related to server-side. The public directory contains two sub-directories: dist and src. dist is used for the output of build result, while src is for front-end code files.

Model

First, we define a model for Post using Mongoose. To make it simple, it only has two properties: title and content.

app/models/Post.js

  const mongoose = require('mongoose');

  const { Schema } = mongoose;

  const PostSchema = new Schema(
    {
      title: { type: String, trim: true, index: true, default: '' },
      content: { type: String },
    },
    {
      collection: 'posts',
      timestamps: true,
    },
  );

  module.exports = mongoose.model('Post', PostSchema);

Queries

After defining the model, we write some queries that will be needed in the controllers.

app/queries/posts.js

  const Post = require('../models/Post');

  /**
   * Save a post.
   *
   * @param {Object} post - Javascript object or Mongoose object
   * @returns {Promise.}
   */
  exports.save = (post) => {
    if (!(post instanceof Post)) {
      post = new Post(post);
    }

    return post.save();
  };

  /**
   * Get post list.
   * @param {object} [criteria] - Filter options
   * @returns {Promise.<Array.>}
   */
  exports.getPostList = (criteria = {}) => Post.find(criteria);

  /**
   * Get post by ID.
   * @param {string} id - Post ID
   * @returns {Promise.}
   */
  exports.getPostById = id => Post.findOne({ _id: id });

  /**
   * Delete a post.
   * @param {string} id - Post ID
   * @returns {Promise}
   */
  exports.deletePost = id => Post.findByIdAndRemove(id);

Controllers

We need API controllers for handling create post, get post listing, get detail of a post, update a post and delete a post.

app/controllers/api/posts/create.js

  const postQueries = require('../../../queries/posts');

  module.exports = (req, res) => postQueries.save(req.body)
    .then((post) => {
      if (!post) {
        return Promise.reject(new Error('Post not created'));
      }

      return res.status(200).send(post);
    })
    .catch((err) => {
      console.error(err);

      return res.status(500).send('Unable to create post');
    });

app/controllers/api/posts/delete.js

  const postQueries = require('../../../queries/posts');

  module.exports = (req, res) => postQueries.deletePost(req.params.id)
    .then(() => res.status(200).send())
    .catch((err) => {
      console.error(err);

      return res.status(500).send('Unable to delete post');
    });

app/controllers/api/posts/details.js

  const postQueries = require('../../../queries/posts');

  module.exports = (req, res) => postQueries.getPostById(req.params.id)
    .then((post) => {
      if (!post) {
        return Promise.reject(new Error('Post not found'));
      }

      return res.status(200).send(post);
    })
    .catch((err) => {
      console.error(err);

      return res.status(500).send('Unable to get post');
    });

app/controllers/api/posts/list.js

  const postQueries = require('../../../queries/posts');

  module.exports = (req, res) => postQueries.getPostList(req.params.id)
    .then(posts => res.status(200).send(posts))
    .catch((err) => {
      console.error(err);

      return res.status(500).send('Unable to get post list');
    });

app/controllers/api/posts/update.js

  const _ = require('lodash');

  const postQueries = require('../../../queries/posts');

  module.exports = (req, res) => postQueries.getPostById(req.params.id)
    .then(async (post) => {
      if (!post) {
        return Promise.reject(new Error('Post not found'));
      }

      const { title, content } = req.body;

      _.assign(post, {
        title, content
      });

      await postQueries.save(post);

      return res.status(200).send({
        success: true,
        data: post,
      })
    })
    .catch((err) => {
      console.error(err);

      return res.status(500).send('Unable to update post');
    });

Routes

We need to have some pages for user interaction and some API endpoints for processing HTTP requests. To make the app scalable, it’s better to separate the routes for pages and APIs.

app/routes/index.js

  const express = require('express');

  const routes = express.Router();

  routes.use('/api', require('./api'));
  routes.use('/', require('./pages'));

  module.exports = routes;


Below is the API routes.

app/routes/api/index.js

  const express = require('express');

  const router = express.Router();

  router.get('/posts/', require('../../controllers/api/posts/list'));
  router.get('/posts/:id', require('../../controllers/api/posts/details'));
  router.post('/posts/', require('../../controllers/api/posts/create'));
  router.patch('/posts/:id', require('../../controllers/api/posts/update'));
  router.delete('/posts/:id', require('../../controllers/api/posts/delete'));

  module.exports = router;


For the pages, in this tutorial, we use plain HTML file. You can easily replace it with any HTML template engine if you want. The HTML file contains a div whose id is app. Later, in Vue.js application, it will use the element with id app for rendering the content. What will be rendered on each pages is configured on Vue.js route on part 2 of this tutorial.

app/routes/pages/index.js

  const express = require('express');

  const router = express.Router();

  router.get('/posts/', (req, res) => {
    res.sendFile(`${__basedir}/views/index.html`);
  });

  router.get('/posts/create', (req, res) => {
    res.sendFile(`${__basedir}/views/index.html`);
  });

  router.get('/posts/:id', (req, res) => {
    res.sendFile(`${__basedir}/views/index.html`);
  });

  module.exports = router;

Below is the HTML file

app/views/index.html

  <!DOCTYPE html>
  <html>
    <head>
      <meta charset="utf-8">
      <title>VueJS Tutorial by Woolha.com</title>
      <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css" type="text/css" media="all" />
      <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
      <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
    </head>
    <body>
      <div id="app"></div>
      <script src="/dist/js/main.js"></script>
    </body>
  </html>

Below is the main script of the application, you need to run this for starting the server-side application.

app/index.js

  require('dotenv').config();

  const bodyParser = require('body-parser');
  const express = require('express');
  const http = require('http');
  const mongoose = require('mongoose');
  const morgan = require('morgan');
  const path = require('path');

  const dbConfig = require('./config/database');
  const routes = require('./routes');

  const app = express();
  const port = process.env.PORT || 4000;

  global.__basedir = __dirname;

  mongoose.Promise = global.Promise;

  mongoose.connect(dbConfig.url, dbConfig.options, (err) => {
    if (err) {
      console.error(err.stack || err);
    }
  });

  /* General setup */
  app.use(morgan('dev'));
  app.use(bodyParser.json());
  app.use(bodyParser.urlencoded({ extended: true }));
  app.use(morgan('dev'));

  app.use('/', routes);

  const MAX_AGE = 86400000;

  // Select which directories or files under public can be served to users
  app.use('/', express.static(path.join(__dirname, '../public'), { maxAge: MAX_AGE }));

  // Error handler
  app.use((err, req, res, next) => { // eslint-disable-line no-unused-vars
    res.status(err.status || 500);

    if (err.status === 404) {
      res.locals.page = {
        title: 'Not Found',
        noIndex: true,
      };

      console.error(`Not found: ${req.url}`);

      return res.status(404).send();
    }

    console.error(err.stack || err);

    return res.status(500).send();
  });

  http
    .createServer(app)
    .listen(port, () => {
      console.info(`HTTP server started on port ${port}`);
    })
    .on('error', (err) => {
      console.error(err.stack || err);
    });

  process.on('uncaughtException', (err) => {
    if (err.name === 'MongoError') {
      mongoose.connection.emit('error', err);
    } else {
      console.error(err.stack || err);
    }
  });

  module.exports = app;

That’s all for the server side preparation. On the next part, we’re going to set up the Vue.js client-side application and build the code into a single JavaScript file ready to be loaded from HTML.

Then, we build the code using Webpack, so that it can be loaded from HTML. In this tutorial, we’re building a simple application with basic CRUD functionality for managing posts.

Create Vue.js Components

For managing posts, there are three components we’re going to create. The first one is for creating a new post. The second is for editing a post. The other is for managing posts (displaying list of posts and allow post deletion)

First, this is the component for creating a new post. It has one method createPost which validate data and send HTTP request to the server. We use axios for sending HTTP request.

public/src/components/Posts/Create.vue

  <template>
    <b-container>
      <h1 class="d-flex justify-content-center">Create a Post</h1>
      <p v-if="errors.length">
        <b>Please correct the following error(s):</b>
        <ul>
          <li v-for="error in errors">{{ error }}</li>
        </ul>
      </p>
      <b-form @submit.prevent>
        <b-form-group>
          <b-form-input type="text" class="form-control" placeholder="Title of the post" v-model="post.title"></b-form-input>
        </b-form-group>
        <b-form-group>
          <b-form-textarea class="form-control" placeholder="Write the content here" v-model="post.content"></b-form-textarea>
        </b-form-group>
        <b-button variant="primary" v-on:click="createPost">Create Post</b-button>
      </b-form>
    </b-container>
  </template>

  <script>
    import axios from 'axios';

    export default {
      data: () => ({
        errors: [],
        post: {
          title: '',
          content: '',
        },
      }),
      methods: {
        createPost(event) {
          if (event) {
            event.preventDefault();
          }

          if (!this.post.title) {
            this.errors = [];

            if (!this.post.title) {
              this.errors.push('Title required.');
            }

            return;
          }

          const url = 'http://localhost:4000/api/posts';
          const param = this.post;

          axios
            .post(url, param)
            .then((response) => {
              console.log(response);
              window.location.href = 'http://localhost:4000/posts';
            }).catch((error) => {
              console.log(error);
            });
        },
      }
    }
  </script>


Below is the component for editing a post. Of course, we need the current data of the post before editing it. Therefore, there’s fetchPost method called when the component is created. There’s also updatePost method which validate data and call the API for updating post.

public/src/components/Posts/Edit.vue

  <template>
    <b-container>
      <h1 class="d-flex justify-content-center">Edit a Post</h1>
      <p v-if="errors.length">
        <b>Please correct the following error(s):</b>
        <ul>
          <li v-for="error in errors">{{ error }}</li>
        </ul>
      </p>
      <b-form @submit.prevent>
        <b-form-group>
          <b-form-input type="text" class="form-control" placeholder="Title of the post" v-model="post.title"></b-form-input>
        </b-form-group>
        <b-form-group>
          <b-form-textarea class="form-control" placeholder="Write the content here" v-model="post.content"></b-form-textarea>
        </b-form-group>
        <b-button variant="primary" v-on:click="updatePost">Update Post</b-button>
      </b-form>
    </b-container>
  </template>

  <script>
    import axios from 'axios';

    export default {
      data: () => ({
        errors: [],
        post: {
          _id: '',
          title: '',
          content: '',
        },
      }),
      created: function() {
        this.fetchPost();
      },
      methods: {
        fetchPost() {
          const postId = this.$route.params.id;
          const url = `http://localhost:4000/api/posts/${postId}`;

          axios
            .get(url)
            .then((response) => {
              this.post = response.data;
              console.log('this.post;');
              console.log(this.post);
          });
        },
        updatePost(event) {
          if (event) {
            event.preventDefault();
          }

          if (!this.post.title) {
            this.errors = [];

            if (!this.post.title) {
              this.errors.push('Title required.');
            }

            return;
          }

          const url = `http://localhost:4000/api/posts/${this.post._id}`;
          const param = this.post;

          axios
            .patch(url, param)
            .then((response) => {
                console.log(response);
              window.alert('Post successfully saved');
            }).catch((error) => {
              console.log(error);
            });
        },
      }
    }
  </script>


For managing posts, we need to fetch the list of post first. Similar to the edit component, in this component, we have fetchPosts method called when the component is created. For deleting a post, there’s also a method deletePost. If post successfully deleted, the fetchPosts method is called again to refresh the post list.

public/src/components/Posts/List.vue

  <template>
    <b-container>
      <h1 class="d-flex justify-content-center">Post List</h1>
      <b-button variant="primary" style="color: #ffffff; margin: 20px;"><a href="/posts/create" style="color: #ffffff;">Create New Post</a></b-button>
      <b-container-fluid v-if="posts.length">
        <table class="table">
          <thead>
            <tr class="d-flex">
              <td class="col-8">Titleqqqqqqqqq</td>
              <td class="col-4">Actions</td>
            </tr>
          </thead>
          <tbody>
            <tr v-for="post in posts" class="d-flex">
              <td class="col-8">{{ post.title }}</td>
              <td class="col-2"><a v-bind:href="'http://localhost:4000/posts/' + post._id"><button type="button" class="btn btn-primary"><i class="fa fa-edit" aria-hidden="true"></i></button></a></td>
              <td class="col-2"><button type="button" class="btn btn-danger" v-on:click="deletePost(post._id)"><i class="fa fa-remove" aria-hidden="true"></i></button></td>
            </tr>
          </tbody>
        </table>
      </b-container-fluid>
    </b-container>
  </template>

  <script>
    import axios from 'axios';

    export default {
      data: () => ({
        posts: [],
      }),
      created: () => {
        this.fetchPosts();
      },
      methods: {
        fetchPosts() {
          const url = 'http://localhost:4000/api/posts/';

          axios
            .get(url)
            .then((response) => {
              console.log(response.data);
              this.posts = response.data;
          });
        },
        deletePost(id) {
          if (event) {
            event.preventDefault();
          }

          const url = `http://localhost:4000/api/posts/${id}`;
          const param = this.post;

          axios
            .delete(url, param)
            .then((response) => {
              console.log(response);
              console.log('Post successfully deleted');

              this.fetchPosts();
            }).catch((error) => {
              console.log(error);
            });
        },
      }
    }
  </script>


All of the components above are wrapped into a root component which roles as the basic template. The root component renders the navbar which is same across all components. The component for each routes will be rendered on router-view.

public/src/App.vue

  <template>
    <div>
      <b-navbar toggleable="md" type="dark" variant="dark">
        <b-navbar-toggle target="nav_collapse"></b-navbar-toggle>
        <b-navbar-brand to="/">My Vue App</b-navbar-brand>
        <b-collapse is-nav id="nav_collapse">
          <b-navbar-nav>
            <b-nav-item to="/">Home</b-nav-item>
            <b-nav-item to="/posts">Manage Posts</b-nav-item>
          </b-navbar-nav>
        </b-collapse>
      </b-navbar>
      <!-- routes will be rendered here -->
      <router-view />
    </div>
  </template>

  <script>

  export default {
    name: 'app',
    data () {},
    methods: {}
  }
  </script>


For determining which component should be rendered, we use Vue.js’ router. For each routes, we need to define the path, component name and the component itself. A component will be rendered if the current URL matches the path.

public/src/router/index.js

  import Vue from 'vue'
  import Router from 'vue-router'

  import CreatePost from '../components/Posts/Create.vue';
  import EditPost from '../components/Posts/Edit.vue';
  import ListPost from '../components/Posts/List.vue';

  Vue.use(Router);

  let router = new Router({
    mode: 'history',
    routes: [
      {
        path: '/posts',
        name: 'ListPost',
        component: ListPost,
      },
      {
        path: '/posts/create',
        name: 'CreatePost',
        component: CreatePost,
      },
      {
        path: '/posts/:id',
        name: 'EditPost',
        component: EditPost,
      },
    ]
  });

  export default router;


Lastly, we need a main script as the entry point which imports the main App component and the router. Inside, it creates a new Vue instance

webpack.config.js

  import BootstrapVue from 'bootstrap-vue';
  import Vue from 'vue';

  import App from './App.vue';
  import router from './router';

  Vue.use(BootstrapVue);
  Vue.config.productionTip = false;
  new Vue({
    el: '#app',
    router,
    render: h => h(App),
  });

Configure Webpack

For building the code into a single JavaSript file. Below is the basic configuration for Webpack 4.

webpack.config.js

  const { VueLoaderPlugin } = require('vue-loader');

  module.exports = {
    entry: './public/src/main.js',
    output: {
      path: `${__dirname}/public/dist/js/`,
      filename: '[name].js',
    },
    resolve: {
      modules: [
        'node_modules',
      ],
      alias: {
        // vue: './vue.js'
      }
    },
    module: {
      rules: [
        {
          test: /\.css$/,
          use: [
            'vue-style-loader',
            'css-loader'
          ]
        },
        {
          test: /\.vue$/,
          loader: 'vue-loader',
          options: {
            loaders: {
            }
            // other vue-loader options go here
          }
        },
        {
          test: /\.js$/,
          loader: 'babel-loader',
          exclude: /node_modules/
        },
      ]
    },
    plugins: [
      new VueLoaderPlugin(),
    ]

After that, run ./node_modules/webpack/bin/webpack.js. You can add the command to the scripts section of package.json, so you can run Webpack with a shorter command npm run build, as examplified below.

  "dependencies": {
    ...
  },
  "devDependencies": {
    ...
  },
  "scripts": {
    "build": "./node_modules/webpack/bin/webpack.js",
    "start": "node app/index.js"
  },

Finally, you can start to try the application. This code is also available on Woolha.com’s Github.

How to implement server-side pagination in Vue.js with Node.js

How to implement server-side pagination in Vue.js with Node.js

This is a simple example of how to implement server-side pagination in Vue.js with a Node.js backend API.

Originally published at https://jasonwatmore.com
This is a simple example of how to implement pagination in Vue.js with a Node.js backend API.

The example contains a hard coded array of 150 objects split into 30 pages (5 items per page) to demonstrate how the pagination logic works. Styling of the example is done with Bootstap 4.

The tutorial code is available on GitHub at https://github.com/cornflourblue/vue-node-server-side-pagination.

Here it is in action (may take a few seconds for the container to startup):

(See on CodeSandbox at https://codesandbox.io/s/vuejs-node-server-side-pagination-example-0l40x)

Running the Vue.js + Node Pagination Example Locally
  1. Install NodeJS and NPM from https://nodejs.org.
  2. Download or clone the tutorial project source code from https://github.com/cornflourblue/vue-node-server-side-pagination.
  3. Install required npm packages of the backend Node API by running the npm install command in the /server folder.
  4. Start the backend Node API by running npm start in the /server folder,
  5. this will start the API on the URL http://localhost:4000.
  6. Install required npm packages of the frontend Vue.js app by running the npm install command in the /client folder.
  7. Start the Vue.js frontend app by running npm start in the /client folder,
  8. this will build the app with webpack and automatically launch it in a
  9. browser on the URL http://localhost:8080.
Server-Side (Node.js) Pagination Logic

Pagination is handled by the backend Node API with the help of the jw-paginate npm package, for more info on how the pagination logic works see JavaScript - Pure Pagination Logic in Vanilla JS / TypeScript.

Below is the code for the paged items route (/api/items) in the node server file (/server/server.js) in the example, it creates a hardcoded list of 150 items to be paged, in a real application you would replace this with real data (e.g. from a database). The route accepts an optional page parameter in the url query string, if the parameter isn't set it defaults to the first page.

The paginate() function is from the jw-paginate package and accepts the following parameters:

  • totalItems (required) - the total number of items to be paged
  • currentPage (optional) - the current active page, defaults to the first page
  • pageSize (optional) - the number of items per page, defaults to 10
  • maxPages (optional) - the maximum number of page navigation links to display, defaults to 10

The output of the paginate function is a pager object containing all the information needed to get the current pageOfItems out of the items array, and to display the pagination controls in the Vue.js frontend, including:

  • startIndex - the index of the first item of the current page (e.g. 0)
  • endIndex - the index of the last item of the current page (e.g. 9)
  • pages - the array of page numbers to display (e.g. [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ])
  • currentPage - the current active page (e.g. 1)
  • totalPages - the total number of pages (e.g. 30)

I've set the pageSize to 5 in the CodeSandbox example above so the pagination links aren't hidden below the terminal console when the container starts up. In the code on GitHub I didn't set the page size so the default 10 items are displayed per page in that version.

The current pageOfItems is extracted from the items array using the startIndex and endIndex from the pager object. The route then returns the pager object and current page of items in a JSON response.

// paged items route
app.get('/api/items', (req, res, next) => {
   // example array of 150 items to be paged
   const items = [...Array(150).keys()].map(i => ({ id: (i + 1), name: 'Item ' + (i + 1) }));

   // get page from query params or default to first page
   const page = parseInt(req.query.page) || 1;

   // get pager object for specified page
   const pageSize = 5;
   const pager = paginate(items.length, page, pageSize);

   // get page of items from items array
   const pageOfItems = items.slice(pager.startIndex, pager.endIndex + 1);

   // return pager object and current page of items
   return res.json({ pager, pageOfItems });
});

Client-Side (Vue.js) Pagination Component

Since the pagination logic is handled on the server, the only thing the Vue.js client needs to do is fetch the pager information and current page of items from the backend, and display them to the user.

Vue.js Home Page Component

Below is the Vue home page component (/client/src/home/HomePage.vue) from the example. The template renders the current page of items as a list of divs with the v-for directive, and renders the pagination controls using the data from the pager object. Each pagination link sets the page query parameter in the url with the <router-link> component and :to="{ query: { page: ... }}" property.

The Vue component contains a watcher function on the page url query parameter '$route.query.page', the handler function is triggered by Vue whenever the page variable in the url querystring changes, the immediate: true flag tells Vue to also run the function when the component first loads. The watcher function checks if the page has changed and fetches the pager object and pageOfItems for the current page from the backend API with an HTTP request.

The CSS classes used are all part of Bootstrap 4.3, for more info see https://getbootstrap.com/docs/4.3/getting-started/introduction/.

<template>
   <div class="card text-center m-3">
       <h3 class="card-header">Vue.js + Node - Server Side Pagination Example</h3>
       <div class="card-body">
           <div v-for="item in pageOfItems" :key="item.id">{{item.name}}</div>
       </div>
       <div class="card-footer pb-0 pt-3">
           <ul v-if="pager.pages && pager.pages.length" class="pagination">
               <li :class="{'disabled':pager.currentPage === 1}" class="page-item first-item">
                   <router-link :to="{ query: { page: 1 }}" class="page-link">First</router-link>
               </li>
               <li :class="{'disabled':pager.currentPage === 1}" class="page-item previous-item">
                   <router-link :to="{ query: { page: pager.currentPage - 1 }}" class="page-link">Previous</router-link>
               </li>
               <li v-for="page in pager.pages" :key="page" :class="{'active':pager.currentPage === page}" class="page-item number-item">
                   <router-link :to="{ query: { page: page }}" class="page-link">{{page}}</router-link>
               </li>
               <li :class="{'disabled':pager.currentPage === pager.totalPages}" class="page-item next-item">
                   <router-link :to="{ query: { page: pager.currentPage + 1 }}" class="page-link">Next</router-link>
               </li>
               <li :class="{'disabled':pager.currentPage === pager.totalPages}" class="page-item last-item">
                   <router-link :to="{ query: { page: pager.totalPages }}" class="page-link">Last</router-link>
               </li>
           </ul>
       </div>
   </div>
</template>

<script>
export default {
   data () {
       return {
           pager: {},
           pageOfItems: []
       }
   },
   watch: {
       '$route.query.page': {
           immediate: true,
           handler(page) {
               page = parseInt(page) || 1;
               if (page !== this.pager.currentPage) {
                   fetch(`/api/items?page=${page}`, { method: 'GET' })
                       .then(response => response.json())
                       .then(({pager, pageOfItems}) => {
                           this.pager = pager;
                           this.pageOfItems = pageOfItems;
                       });
               }
           }
       }
   }
}
</script>