Wisdom Ekpot A student of Ibom Metropolitan Polytechnic studying computer engineering, Wisdom has been writing JavaScript for two years, focusing on Vue.js, Angular, and Express.js.

Building a JavaScript router using History API

4 min read 1234

JavaScript Router History API

In this article, we will build a client-side routing system. Client-side routing is a type of routing where users navigate through an application where no full page reload occurs even when the page’s URL changes — instead, it displays new content.

To build this, we’ll need a simple server that will serve our index.html file. Ready? Let’s begin.

First, set up a new node.js application and create the project structure:

npm init -y
npm install express morgan nodemon --save
touch server.js
mkdir public && cd public
touch index.html && touch main.js file
cd ..

The npm init command will create a package.json file for our application. We’ll install Express and Morgan , which will be used in running our server and logging of our routes.

We’ll also create a server.js file and a public directory where we will be writing our views. Nodemon will restart our application once we make any change in our files.

Setting up the server

Let’s create a simple server using Express by modifying the server.js file:

const express = require('express');
const morgan = require('morgan');
const app = express();

app.use(morgan('dev'));
app.use(express.static('public'))

app.get('*', (req, res) => {
    res.sendFile(__dirname + '/public/index.html')
})
app.listen(7000, () => console.log("App is listening on port 7000"))

Now we can start our application by running nodemon server.js. Let’s create a simple boilerplate for our HTML:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>Javascript Routing</h1>
    <div id="app">
    </div>

    <script src="main.js"></script>
</body>
</html>

Here, we’ll link the main.js file so that we can manipulate the DOM at any point in time.

Implementing the routing system

Let’s head over to the main.js file and write all of our router logic. All our codes will be wrapped in the window.onload so that they only execute the script once the webpage has completely loaded all of its content.

Next, we’ll create a router instance that’s a function with two parameters. The first parameter will be the name of the route and the second will be an array which comprises all our defined routes. This route will have two properties: the name of the route and the path of the route.

window.onload = () => {
// get root div for rendering
    let root = document.getElementById('app');

  //router instance
    let Router = function (name, routes) {
        return {
            name,
            routes
        }
    };

 //create the route instance
    let routerInstance = new Router('routerInstance', [{
            path: "/",
            name: "Root"
        },
        {
            path: '/about',
            name: "About"
        },
        {
            path: '/contact',
            name: "Contact"
        }
    ])

}

We can get the current route path of our page and display a template based on the route.location.pathname returns the current route of a page, and we can use this code for our DOM:

 let currentPath = window.location.pathname;
    if (currentPath === '/') {
        root.innerHTML = 'You are on Home page'
    } else {
        // check if route exist in the router instance 
        let route = routerInstance.routes.filter(r => r.path === currentPath)[0];
        if (route) {
            root.innerHTML = `You are on the ${route.name} path`
        } else {
            root.innerHTML = `This route is not defined`
        }
    }

We’ll use the currentPath variable to check if a route is defined in our route instance. If the route exists, we’ll render a simple HTML template. If it doesn’t, we’ll display This route is not defined on the page.

Feel free to display any form of error of your choice. For example, you could make it redirect back to the homepage if a route does not exist.

Adding router links

For navigation through the pages, we can add router links. Just like with Angular, you can pass a routerLink that will have a value of the path you want to navigate to. To implement this, let’s add some links to our index.html file :

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <nav>
        <button router-link="/">Home</button>
        <button router-link="/about">About</button>
        <button router-link="/contact">Contact</button>
        <button router-link="/unknown">Error</button>
    </nav>
    <h1>Javascript Routing</h1>
    <div id="app">
    </div>

    <script src="main.js"></script>
</body>
</html>

Notice the router-link attribute that we passed in — this is what we will be using for our routing.

We’ll create a variable store all of router-links and store it in an array:

let definedRoutes = Array.from(document.querySelectorAll('[router-link]'));

After storing our router-links in an array, we can iterate through them and add a click event listener that calls the navigate() function:

 //iterate over all defined routes
    definedRoutes.forEach(route => {
        route.addEventListener('click', navigate, false)
    })

Defining the navigate function

The navigate function will be using Javascript History API to navigate. The history.pushState() method adds a state to the browser’s session history stack.

When the button is clicked, we’ll receive the router link attribute of that button and then use the history.pushState() to navigate to that path, then change the HTML template rendered:

  // method to navigate
    let navigate = e => {
        let route = e.target.attributes[0].value;

        // redirect to the router instance
        let routeInfo = routerInstance.routes.filter(r => r.path === route)[0]
        if (!routeInfo) {
            window.history.pushState({}, '', 'error')
            root.innerHTML = `This route is not Defined`
        } else {
            window.history.pushState({}, '', routeInfo.path)
            root.innerHTML = `You are on the ${routeInfo.name} path`
        }
    }

If a nav link has a router link that has not been defined in the routeInstance, it will set the push state to error and render This route is not Defined on the template.

Next, you should consider storing routes in a separate file, which makes codes neater and easier to debug if there are any errors. Now, create a routes.js file and extract the route constructor and router instance into this new file:

//router instance
let Router = function (name, routes) {
    return {
        name,
        routes
    }
};
let routerInstance = new Router('routerInstance', [{
        path: "/",
        name: "Root"
    },
    {
        path: '/about',
        name: "About"
    },
    {
        path: '/contact',
        name: "Contact"
    }
])

export default routerInstance

Exporting this file makes it accessible to other JavaScript files. We can import it into our main.js file:

import routerInstance from './routes.js'

This will throw an error. To fix it, modify the script tag in the index.html file to this:

<script type="module" src="main.js"></script>

Adding the type of module specifies which variables and functions can be accessed outside the modules.



Conclusion

Understanding how to implement a routing system in Vanilla JavaScript makes it easier for developers to work with a framework routing library such as the Vue.js Router. Our code here can be reused in a single page application, which is perfect when you’re working without a framework. To get the source code, check out GitHub.

: Debug JavaScript errors more easily by understanding the context

Debugging code is always a tedious task. But the more you understand your errors the easier it is to fix them.

LogRocket allows you to understand these errors in new and unique ways. Our frontend monitoring solution tracks user engagement with your JavaScript frontends to give you the ability to find out exactly what the user did that led to an error.

LogRocket records console logs, page load times, stacktraces, slow network requests/responses with headers + bodies, browser metadata, and custom logs. Understanding the impact of your JavaScript code will never be easier!

.
Wisdom Ekpot A student of Ibom Metropolitan Polytechnic studying computer engineering, Wisdom has been writing JavaScript for two years, focusing on Vue.js, Angular, and Express.js.

6 Replies to “Building a JavaScript router using History API”

Leave a Reply