Get started with 33% off your first certification using code: 33OFFNEW

How to build a router in vanilla JavaScript

3 min read
Published on 3rd August 2023

Single-page applications (SPAs) have grown in popularity due to their ability to deliver smooth, uninterrupted user experiences. The magic behind SPAs often lies in a critical component - the router. Routers control the application's views based on the URL, without the need for full-page refreshes.

While several libraries such as React Router for React and Vue Router for Vue.js as well as routers built into frameworks such as express offer robust solutions for managing routes in SPAs, you can build your own rudimentary router in Vanilla JavaScript to understand the underlying mechanics.

What You Will Need

  1. A basic understanding of JavaScript.
  2. Knowledge of the HTML5 History API.
  3. Familiarity with JavaScript Event Listeners.

Creating a Router

Let's start by defining a simple Router class.

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

In the constructor, we store the routes passed when instantiating the router and call _loadInitialRoute() to load the correct view when the application starts.

Defining Routes

Each route is an object with two properties: path and callback. path is the URL at which the route should be triggered, and callback is the function to execute when the route is activated. Define the routes in an array:

const routes = [
  { path: '/', callback: () => console.log('Home page') },
  { path: '/about', callback: () => console.log('About page') },
];

Parsing the Current URL

To determine which route to load, we need to parse the current URL into a usable format. Let's create a helper method, _getCurrentURL(), in our Router class:

class Router {
  ...
  _getCurrentURL() {
    const path = window.location.pathname;
    return path;
  }
  ...
}

This method uses window.location.pathname to get the current URL path.

Matching Routes

Next, we add a _matchUrlToRoute() method that matches the current URL to one of our predefined routes:

class Router {
  ...
  _matchUrlToRoute(urlSegs) {
    const matchedRoute = this.routes.find(route => route.path === urlSegs);
    return matchedRoute;
  }
  ...
}

Loading the Initial Route

Now we can implement _loadInitialRoute(). This method will be invoked when the router is instantiated, triggering the correct route based on the current URL:

class Router {
  ...
  _loadInitialRoute() {
    const pathnameSplit = window.location.pathname.split('/');
    const pathSegs = pathnameSplit.length > 1 ? pathnameSplit.slice(1) : '';

    this.loadRoute(...pathSegs);
  }
  ...
}

Loading Routes

The loadRoute() method takes the URL segments, finds the matching route, and invokes its callback function:

class Router {
  ...
  loadRoute(...urlSegs) {
    const matchedRoute = this._matchUrlToRoute(urlSegs);
    if (!matchedRoute) {
      throw new Error('Route not found');
    }
    matchedRoute.callback();
  }
  ...
}

Adding Navigation

To enable navigation, we'll use the history.pushState() method to change the URL without refreshing the page:

class Router {
  ...
  navigateTo(path) {
    window.history.pushState({}, '', path);
    this.loadRoute(path);
  }
  ...
}

Now, you can navigate to different routes in your application:

const router = new Router(routes);
router.navigateTo('/about');

Remember to bind the popstate event to handle the browser's forward and back buttons:

window.addEventListener('popstate', () => {
  router._loadInitialRoute();
});

This is a basic example of a router in Vanilla JavaScript. It only scratches the surface of what's possible. The real-world implementations are much more complex and have additional features like route guards, nested routes, and more. But the core idea remains the same, and this foundation can be a stepping stone to more advanced topics.