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
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 {
}
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 = [];
}
}
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){
}
}
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`);
});
}
}
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);
}
}
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);
}
})
}
}
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();
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)
Nicely done.
$0.02 from me take it or leave it.
Very true Vijay, It'll only be required if i wanted to chain methods. I'll edit accordingly. Thanks for noting that.
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:
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!
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.
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
Nice :)
Maybe you can add a hash change event or something like that.
But I loved :)
Thanks.
Very true, Ali. But i just tried keeping it simple.
Enjoyed the article, interesting read, thanks.
I can't make it to work. Any route I navigate send me to a Not Found (404) page. What am I missing? Thanks.
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.
very good entry, but I have a doubt the code runs in the front end