How to build a Router with vanilla JavaScript
Tags: router, javascript, spa, firstpost
Published: 2019-05-16 10:29:30

---
title: How to build a Router with vanilla JavaScript
published: true
description: 
tags: router, JavaScript, spa, firstpost
---

So many times, we might want to write our JavaScript code in a single file but we want the code to be executed only if a particular route is matched. You can achieve this with the help of a router by downloading a <a href="https://github.com/kodnificent/sparouter" target="_blank">router library</a> or writing the code yourself.

Today, i'll walk you through building a very basic router function with vanilla JavaScript. I'll be using some [es6 features](https://www.w3schools.com/js/js_es6.asp) and <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions" target="_blank">javascript regular expressions</a> for building this router, so you have to be familiar with them for better understanding.
## The Concept
The good thing about programming is that you can solve a problem using any method or style you wish to but you have to avoid bad practices.  
Here's the approach we'll take to building this router.
- Create a router class
- Create a method that stores the route logics and its corresponding callback function in an array.
- Create a method that processes these logics and return the corresponding callback function if the logic is true.  

Here's a picture of what we want.
```javascript
const router = new RouterClass();

// the get() method would store the '/' logic and callback in an array;
router.get('/', function(){
   // code to be executed if '/' is matched
});

// here get() method would push '/another-page' and the callback to the existing array
router.get('/another-page', function(){
   // code to be executed if '/another-page' is matched
); 

router.init(); // this method will process the logics
```
## Building our Router
### Step 1 - create a router class
We'll create a class named Router that will be called with the `new` keyword.
```js
class Router {

}
```
### Step 2 - add a constructor
The constructor is the method that is executed when our Router class is instantiated with the new keyword. In the constructor method, we'll create a property named `routes` and assign an empty array to it.  
The routes property will store all routes and their callback functions in an array.
```js
class Router {
    constructor(){
       this.routes = [];
    }
}
```
You can also pass an `options` parameter to the constructor method and set some options for the router class but we'll skip that for the sake of simplicity.

### Step 3 - Create a method for storing routes
We'll create a method named `get()` for storing routes and it's callback. The get method should have two parameters: `uri` and `callback`
```javascript
class Router {
    constructor(){
       this.routes = [];
    }
    
    get(uri, callback){
    
    }
}
```
I've named the method as `get` for readability. Therefore, `router.get(uri, callback);` should mean: get a particular uri and return a callback. You can name yours as you which. Maybe, `router.if(uri, callback);`

### Step 4 - Validate parameters of the get method  
In this method, we'll validate our parameters to make sure we don't mistakenly pass the wrong type of variables as parameters when using our router.
```js
class Router {

    constructor(){
       this.routes = [];
    }
    
    get(uri, callback){
        // ensure that the parameters are not empty
        if(!uri || !callback) throw new Error('uri or callback must be given');

        // ensure that the parameters have the correct types
        if(typeof uri !== "string") throw new TypeError('typeof uri must be a string');
        if(typeof callback !== "function") throw new TypeError('typeof callback must be a function');
        // throw an error if the route uri already exists to avoid confilicting routes
        this.routes.forEach(route=>{
            if(route.uri === uri) throw new Error(`the uri ${route.uri} already exists`);
        });
    }
}
```
### Step 5 - add route to the array of routes
After validating the parameter of the `get()` method, we'll create an object named `route` and push that object to our existing array of routes.
```js
class Router {

    constructor(){
       this.routes = [];
    }
    
    get(uri, callback){
        // ensure that the parameters are not empty
        if(!uri || !callback) throw new Error('uri or callback must be given');

        // ensure that the parameters have the correct types
        if(typeof uri !== "string") throw new TypeError('typeof uri must be a string');
        if(typeof callback !== "function") throw new TypeError('typeof callback must be a function');

        // throw an error if the route uri already exists to avoid confilicting routes
        this.routes.forEach(route=>{
            if(route.uri === uri) throw new Error(`the uri ${route.uri} already exists`);
        })

        // Step 5 - add route to the array of routes
        const route = {
            uri, // in javascript, this is the same as uri: uri, callback: callback, avoids repition
            callback
        }
        this.routes.push(route);
    }
}
```
### Step 6 - Process the routes with the `init()` method
We are almost there! Let's process the routes using the `init()` method. When this method is called, we'd want it to loop through our array of routes and match the `route.uri` against the `window.request.pathname`. If we find a match, we'd break out of the loop by returning the `route.callback` function. To easily break out of the loop, we'll be using the `Array.some()` method in place of `Array.forEach()` because `Array.some()` will end the loop when a truthy value is returned in the loop.  

```js
class Router {

    constructor(){
       this.routes = [];
    }
    
    get(uri, callback){
        // ensure that the parameters are not empty
        if(!uri || !callback) throw new Error('uri or callback must be given');

        // ensure that the parameters have the correct types
        if(typeof uri !== "string") throw new TypeError('typeof uri must be a string');
        if(typeof callback !== "function") throw new TypeError('typeof callback must be a function');

        // throw an error if the route uri already exists to avoid confilicting routes
        this.routes.forEach(route=>{
            if(route.uri === uri) throw new Error(`the uri ${route.uri} already exists`);
        })

        // Step 5 - add route to the array of routes
        const route = {
            uri, // in javascript, this is the same as uri: uri, callback: callback, avoids repition
            callback
        }
        this.routes.push(route);
    }

    init(){
        this.routes.some(route=>{

            let regEx = new RegExp(`^${route.uri}$`); // i'll explain this conversion to regular expression below
            let path = window.location.pathname;

            if(path.match(regEx)){
                // our route logic is true, return the corresponding callback
                
                let req = { path } // i'll also explain this code below
                return route.callback.call(this, req);
            }
        })
    }
}
```
Very little code with some strange things going on there right? I'll start with the conversion to regular expression.  
I converted our `route.uri` to a regular expression because we'd want to match the exact value of the route.uri against the `window.location.pathname` else ``router.get('/about', callback)`` would match '/about-us', '/about-me', hence i introduced the regExp keywords `^` and `$`.  
You also noticed ``let req = { path }`` which also means `let req = { path: path }`. This is just to pass an object that can be accessible through our callback parameter. In practical, this means:
```js
const router = new Router();
router.get('/about-me', function(req){
      console.log(req.path); // outputs /about-me to the console
}
router.init();
```

## Conclusion
These are the the steps you can reciprocate in building just a **basic** javascript router. For more advancement, you should target features like:
- having route parameters
- being able to assess query parameters
- having named routes
- grouping routes  

If you don't know how to implement these, you can check out the [source code](https://github.com/kodnificent/sparouter) of the router library i built, to see how i implemented some of these features. Better still, you can install the library via npm with `npm i @kodnificent/sparouter` and use it in your script. Check out the install guide on [npm](https://www.npmjs.com/package/@kodnificent/sparouter).  

**Note**
This is basically for frontend routing purposes. If you want to build a backend router, you can follow similar process but the process of getting the request uri would depend on the server.

This is my first post here on dev.to, so clicking the hearts will be very encouraging. Comments, contributions and criticism are highly welcomed. Check out my [dev.to profile](https://dev.to/@kodnificent) and give me a follow so we can dev together.