DEV Community

Hasan Zohdy
Hasan Zohdy

Posted on

 

20-Nodejs Course 2023: Let's Create Our Own Request Handler

Now we're good with Databases, let's head back to our request and make more sense of it.

Request Handler

Basically what we're using so far is the request and reply objects from fastify to handle our requests. But we can do better than that. We can create our own request handler.

Http Directory

Remember the requests directory we created? let's rename it to http, why??? because it will also contain a response handler so we can set both response and request in the same directory.

Request class

Create request.ts file inside src/core/http directory and add the following code:

// src/core/http/request.ts
export class Request {
  /**
   * Fastify Request instance
   */
  public request: any;

  /**
   * Fastify response instance
   */
  public response: any;

  /**
   * Set request instance
   */
  public setRequest(request: any) {
    this.request = request;

    return this;
  }

  /**
   * Set response instance
   */
  public setResponse(response: any) {
    this.response = response;

    return this;
  }
}

const request = new Request();

export default request;
Enter fullscreen mode Exit fullscreen mode

We added only two properties and two methods, these properties will contain the current instance of Fastify request and response, and the methods will set the current instance of Fastify request and response.

We also need to add the handler controller of current route so let's add it as well.

// src/core/http/request.ts
export class Request {
  /**
   * Fastify Request instance
   */
  public request: any;

  /**
   * Fastify response instance
   */
  public response: any;

  /**
   * Request handler
   */
  public handler: any;

  /**
   * Set request instance
   */
  public setRequest(request: any) {
    this.request = request;

    return this;
  }

  /**
   * Set response instance
   */
  public setResponse(response: any) {
    this.response = response;

    return this;
  }

  /**
   * Set handler instance
   */
  public setHandler(handler: any) {
    this.handler = handler;

    return this;
  }

  /**
   * Execute handler
   */
  public async execute() {
    return await this.handler(this, this.response);
  }
}

const request = new Request();

export default request;
Enter fullscreen mode Exit fullscreen mode

How this will work?

Let's take a step back at the router.scan method, it will loop through all the routes and register them to Fastify, but it will also set the handler of each route to the request instance we created.

Now Fastify handler will be the a custom handler of our own which we will create in a bit, that handler will receive the handler of route and return a callback, that callback will receive from Fastify the request and response objects, and we will set them to our request instance, then we will execute the handler of route and return the response.

But why would we do this? because we'll add more features to the request handler, also more features to the router, for instance we'll add a custom validation to the request handler controller if we want to validate the request before accessing the controller, also managing request data like casting numbers or booleans.

Router Class

Open src/core/router/index.ts and update the scan method with the following code:

// src/core/router/index.ts


  /**
   * Register routes to the server
   */
  public scan(server: any) {
    this.routes.forEach(route => {
      const requestMethod = route.method.toLowerCase();
      const requestMethodFunction = server[requestMethod].bind(server);

      // πŸ‘‡πŸ» we changed `route.handler` to that new method handleRoute
      requestMethodFunction(route.path, this.handleRoute(route));
    });
  }

  /**
   * Handle the given route
   */
  private handleRoute(route: any) {
    return async (request: any, response: any) => {
      return await route.handler(request, response);
    };
  }
Enter fullscreen mode Exit fullscreen mode

We just wrapped our handler in another method, that method will return the same exact response, except we added a little code to make it always async.

Now let's update our route handler to receive our request instance instead of Fastify request and response.

// src/routes/index.ts
import request from "core/http/request";
import { Route } from "./types";

export class Router {
  // ...
  /**
   * Register routes to the server
   */
  public scan(server: any) {
    this.routes.forEach(route => {
      const requestMethod = route.method.toLowerCase();
      const requestMethodFunction = server[requestMethod].bind(server);

      requestMethodFunction(route.path, this.handleRoute(route));
    });
  }

  /**
   * Handle the given route
   */
  private handleRoute(route: any) {
    return async (fastifyRequest: any, fastifyResponse: any) => {
      // update request instance
      request
        .setRequest(fastifyRequest)
        .setResponse(fastifyResponse)
        .setHandler(route.handler);

      // now execute the handler
      return await request.execute();
    };
  }
}

const router = Router.getInstance();

export default router;
Enter fullscreen mode Exit fullscreen mode

We imported our request, then injected the current request and response handlers of Fastify to our request instance, then we set the handler of current route to our request instance, then we executed the handler and returned the response.

That execute method will call the handler and return the proper response accordingly.

Request Executer

Navigating again to our request, let's create a new method called execute which will execute the handler of current route.

// src/core/http/request.ts

export class Request {
  /**
   * Fastify Request instance
   */
  public request: any;

  /**
   * Fastify response instance
   */
  public response: any;

  /**
   * Request handler
   */
  public handler: any;

  /**
   * Set request instance
   */
  public setRequest(request: any) {
    this.request = request;

    return this;
  }

  /**
   * Set response instance
   */
  public setResponse(response: any) {
    this.response = response;

    return this;
  }

  /**
   * Set handler instance
   */
  public setHandler(handler: any) {
    this.handler = handler;

    return this;
  }

  /**
   * Execute handler
   */
  public async execute() {
    return await this.handler(this, this.response);
  }
}

const request = new Request();

export default request;
Enter fullscreen mode Exit fullscreen mode

Now it is perfect, except that the controller now will receive our request instance instead of Fastify request.

This means that now we can add any feature we want to our request instance, and it will be available to the controller.

Conclusion

In this article we created a custom router, and we created a custom request instance, we also updated the router to use our request instance instead of Fastify request and response.

In our next article we'll start add some features to test it against our newly created request handler.

Next Lesson

In the next lesson we'll start working with Database Models!

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

🎞️ Video Course (Arabic Voice)

If you want to learn this course in video format, you can find it on Youtube, the course is in Arabic language.

πŸ’° Bonus Content πŸ’°

You may have a look at these articles, it will definitely boost your knowledge and productivity.

General Topics

Packages & Libraries

React Js Packages

Courses (Articles)

Top comments (0)

typescript

11 Tips That Make You a Better Typescript Programmer

1 Think in {Set}

Type is an everyday concept to programmers, but it’s surprisingly difficult to define it succinctly. I find it helpful to use Set as a conceptual model instead.

#2 Understand declared type and narrowed type

One extremely powerful typescript feature is automatic type narrowing based on control flow. This means a variable has two types associated with it at any specific point of code location: a declaration type and a narrowed type.

#3 Use discriminated union instead of optional fields

...

Read the whole post now!