DEV Community

Hasan Zohdy
Hasan Zohdy

Posted on

7-Nodejs Course 2023: Create Router Handler

So our last article was about creating the Fastify server and run it, now we will create the router handler for our server.

But keep your eyes open, because we're going to create our own router layer over the fastify router.

Updating the server call

As i reviewed the Fastify documentation, i found that we can enhance our code a little bit like the listen method returns the url of the server so we can use.

// src/server.ts
import Fastify from "fastify";

const server = Fastify();

server.get("/", (request, response) => {
  response.send("Hello World!");
});

async function start() {
  // we w'll wrap the server in a try catch block so if there is any error
  // we can catch it and log it
  try {
    // 👇🏻 We can use the url of the server
    const address = await server.listen({ port: 3000 });

    console.log(`Start browsing using ${address}`);
  } catch (err) {
    server.log.error(err);
    process.exit(1);
  }
}

start();
Enter fullscreen mode Exit fullscreen mode

That's no more, now let's create our router layer.

Creating the router layer

Let's create src/core/router folder and create index.ts file inside it.

// src/core/router/index.ts

export default class Router {

}
Enter fullscreen mode Exit fullscreen mode

Simple and easy, now let's describe what this router is going to do exactly.

Router Features

The main purpose for this router is to make it easier to call and organize our routes, so we will create a method for each HTTP method and store all of these routes in an array.

// src/core/router/index.ts

export default class Router {
  // this were we will store all of our routes
  private routes: any[] = [];

  /**
   * Add GET route
   */
  public get(path: string, handler: any) {
    this.routes.push({
      method: "GET",
      path,
      handler,
    });

    return this;
  } 
}
Enter fullscreen mode Exit fullscreen mode

So based on our previous code, we created an array that will contain all of our registered routes, and we created a method for each HTTP method, and we return this so we can chain the methods.

We can also define a type for our routes, let's do it.

// src/core/router/types.ts

export type Route = {
  method: string;
  path: string;
  handler: any;
};
Enter fullscreen mode Exit fullscreen mode
// src/core/router/index.ts

import { Route } from "./types";

export default class Router {
  private routes: Route[] = [];

  public get(path: string, handler: any) {
    this.routes.push({
      method: "GET",
      path,
      handler,
    });

    return this;
  } 
}
Enter fullscreen mode Exit fullscreen mode

Singleton Pattern

Now we have our router, but we need to make sure that we can only have one instance of it, so we will use the singleton pattern.

// src/core/router/index.ts
import { Route } from "./types";

export default class Router {
  // 👇🏻 We will use this to store the instance (object) of the router
  private static instance: Router;

  /**
   * Registered routes list
   */
  private routes: Route[] = [];

  // 👇🏻 Notice the constructor here is private
  private constructor() {
    //
  }

  /**
   * Get router instance
   */
  public static getInstance() {
    if (!Router.instance) {
      Router.instance = new Router();
    }

    return Router.instance;
  }

  /**
   * Add new GET route
   */
  public get(path: string, handler: any) {
    this.routes.push({
      method: "GET",
      path,
      handler,
    });

    return this;
  }
}
Enter fullscreen mode Exit fullscreen mode

What we did here is that we created a static method that will return the instance of the router, and if there is no instance, it will create one.

Keep in mind that the main key of singletons is to make the constructor private, so we can't create new instances of the router, that's why we're using the static method.

Router Scanner

Now we're ready for our first step, let's give it a try.

// src/index.ts
import Router from "./core/router";
import Fastify from "fastify";

const server = Fastify();

const router = Router.getInstance();

router.get('/', (request, response) => {
  return { hello: 'world' };
});

async function start() {
  router.scan(server);
  try {
    const address = await server.listen({ port: 3000 });

    console.log(`Start browsing using ${address}`);
  } catch (err) {
    server.log.error(err);
    process.exit(1);
  }
}

start();
Enter fullscreen mode Exit fullscreen mode

So all what we did here is we imported the router, we replaced the server.get with router.get and we called the scan method in the start method, now let's define it in our router class.

// src/core/router/index.ts

import { Route } from "./types";

export default class Router {
  private static instance: Router;

  private routes: Route[] = [];

  private constructor() {
    //
  }

  public static getInstance() {
    if (!Router.instance) {
      Router.instance = new Router();
    }

    return Router.instance;
  }

  public get(path: string, handler: any) {
    this.routes.push({
      method: "GET",
      path,
      handler,
    });

    return this;
  }

  /**
   * Scan all routes and register them in the server
   */
  public scan(server: any) {
    this.routes.forEach((route) => {
      const requestMethod = route.method.toLowerCase();
      server[requestMethod](route.path, route.handler);
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

So what we did here is we looped over all of our routes and we got the route request method and used it to call the server method, and we passed the path and the handler.

Now let's run the server again, it should be working just fine.

Conclusion

In this article, we learned how to create a router layer for our server, and we learned how to use the singleton pattern to make sure that we can only have one instance of the router.

🎨 Project Repository

You can find the latest updates of this project on Github

😍 Join our community

Join our community on Discord to get help and support (Node Js 2023 Channel).

Oldest comments (0)