DEV Community

Victor Mbamara 🇳🇬
Victor Mbamara 🇳🇬

Posted on • Edited on

How to build a Router with vanilla JavaScript

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 router library 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 and javascript regular expressions 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.

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
Enter fullscreen mode Exit fullscreen mode

Building our Router

Step 1 - create a router class

We'll create a class named Router that will be called with the new keyword.

class Router {

}
Enter fullscreen mode Exit fullscreen mode

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.

class Router {
    constructor(){
       this.routes = [];
    }
}
Enter fullscreen mode Exit fullscreen mode

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

class Router {
    constructor(){
       this.routes = [];
    }

    get(uri, callback){

    }
}
Enter fullscreen mode Exit fullscreen mode

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.

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`);
        });
    }
}
Enter fullscreen mode Exit fullscreen mode

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.

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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.

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);
            }
        })
    }
}
Enter fullscreen mode Exit fullscreen mode

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:

const router = new Router();
router.get('/about-me', function(req){
      console.log(req.path); // outputs /about-me to the console
}
router.init();
Enter fullscreen mode Exit fullscreen mode

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 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.

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 and give me a follow so we can dev together.

Top comments (11)

Collapse
 
vijayjangid profile image
Vijay Jangid

Nicely done.

$0.02 from me take it or leave it.

constructor(){
  this.routes = [];
  return this; // I don't think this is explicitly required 
                     // (unless you want to return something else than 'this')
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
kodnificent profile image
Victor Mbamara 🇳🇬

Very true Vijay, It'll only be required if i wanted to chain methods. I'll edit accordingly. Thanks for noting that.

Collapse
 
couch3ater profile image
Connor Tangney • Edited

Super informative and well written. One small thing that jumps out at me is that you mention you are using ES6 classes but make no mention of the other ES6 features you are using.

The example that jumps out at me:

You also noticed

let req = { path }

which also means

let req = { path: path }

This is a great example of shorthand properties, and making note of this may or may not be beneficial for newcomers.

Outside of that, this was an excellent first post! All these awesome first posts are making me really want to get my own up here now...

Kudos!

Collapse
 
kodnificent profile image
Victor Mbamara 🇳🇬

Thanks alot. You're right about making no mention of other ES6 features. I'll edit the post and generalize the use of es6 features rather than specifying just es6 classes.

Collapse
 
routersip profile image
routersip

Aside from the Web interface, you can likewise utilize the free downloadable Netgear Genie application for the activity network ip address: 192.168.0.254

Collapse
 
itachiuchiha profile image
Itachi Uchiha

Nice :)

Maybe you can add a hash change event or something like that.

But I loved :)

Thanks.

Collapse
 
kodnificent profile image
Victor Mbamara 🇳🇬

Very true, Ali. But i just tried keeping it simple.

Collapse
 
vbpupil profile image
Dean Haines

Enjoyed the article, interesting read, thanks.

Collapse
 
mmucino1 profile image
Marco Muciño

I can't make it to work. Any route I navigate send me to a Not Found (404) page. What am I missing? Thanks.

Collapse
 
kodnificent profile image
Victor Mbamara 🇳🇬

This is because you need to setup a router from the serverside that routes all request to the server to a single page. Then take over the routing with javascript.

Collapse
 
jmz12 profile image
Jorge Méndez Ortega

very good entry, but I have a doubt the code runs in the front end