DEV Community

Cover image for 55-Nodejs Course 2023: Request Events
Hasan Zohdy
Hasan Zohdy

Posted on

55-Nodejs Course 2023: Request Events

We've implemented the middleware concept in our previous article, now le'ts go back to our main topic, the events.

In this article, we'll implement the request events, but let's first review the http events.

Http Events

As i mentioned earlier, there are multiple events that can be fired during the http request, let's review them:

  • request.executingMiddleware: This event is fired before calling all the middlewares.
  • request.executedMiddleware: This event is fired after calling all the middlewares.
  • request.executingAction: This event is fired before the action is called.
  • request.executedAction: This event is fired after the action is called and before.

Let's implement them now in our Request class.

If you've a good observation, you'll notice that i renamed the events.

Trigger And On Methods

As usual, we'll encapsulate the request events to be called in the Request class, so we'll add two methods to the Request class:

  • trigger: This method will be used to trigger the event.
  • on: This method will be used to listen to the event.

But let's first define the RequestEvent type that will be used to define the events.

// src/core/http/types.ts

export type RequestEvent = 'executingMiddleware' | 'executedMiddleware' | 'executingAction' | 'executedAction';
Enter fullscreen mode Exit fullscreen mode

Now let's implement the trigger and on methods.

// src/core/http/request.ts
import { RequestEvent } from "./types";

export class Request {
//...

  /**
   * Trigger an http event
   */
  protected trigger(eventName: RequestEvent, ...args: any[]) {
    return events.trigger(`request.${eventName}`, ...args, this);
  }

  /**
   * Listen to the given event
   */
  public on(eventName: RequestEvent, callback: any) {
    return this.trigger(eventName, callback);
  }
}
Enter fullscreen mode Exit fullscreen mode

Here we defined two methods, the trigger method will be used to trigger the event this method receives the event name and the arguments that will be passed to the event listeners, and the on method will be used to listen to the event, this method receives the event name and the callback that will be called when the event is fired.

Middleware events

We'll start with the middleware events, so we'll implement the callingMiddleware and calledMiddleware events.

We already added our middleware in the execute method, but now we're going to increase our code to handle the events.

So let's move the middleware code to a new method called callMiddleware and call it from the execute method.

// src/core/http/request.ts

  /**
   * Execute the request
   */
  public async execute() {
    // check for middleware first
    const middlewareOutput = await this.executeMiddleware();

    if (middlewareOutput !== undefined) {
      return middlewareOutput;
    }

    const handler = this.route.handler;

    // check for validation
    if (handler.validation) {
      if (handler.validation.rules) {
        const validator = new Validator(this, handler.validation.rules);

        try {
          await validator.scan(); // start scanning the rules
        } catch (error) {
          console.log(error);
        }

        if (validator.fails()) {
          const responseErrorsKey = config.get(
            "validation.keys.response",
            "errors",
          );
          const responseStatus = config.get("validation.responseStatus", 400);

          return this.response.status(responseStatus).send({
            [responseErrorsKey]: validator.errors(),
          });
        }
      }

      if (handler.validation.validate) {
        const result = await handler.validation.validate(this, this.response);

        if (result) {
          return result;
        }
      }
    }

    return await handler(this, this.response);
  }

  /**
   * Execute middleware list of current route
   */
  protected async executeMiddleware() {
    if (!this.route.middleware) return;

    for (const middleware of this.route.middleware) {
      const output = await middleware(this, this.response);

      if (output !== undefined) {
        return output;
      }
    }
  }
Enter fullscreen mode Exit fullscreen mode

We moved the middleware code to a new method called executeMiddleware then we called it from the execute method.

If it returns a value, then we'll return it from the execute method.

Now let's fire up some events.

// src/core/http/request.ts

  /**
   * Execute middleware list of current route
   */
  protected async executeMiddleware() {
    if (!this.route.middleware || this.route.middleware.length === 0) return;

    // trigger the executingMiddleware event
    this.trigger('executingMiddleware', this.route.middleware, this.route);

    for (const middleware of this.route.middleware) {
      const output = await middleware(this, this.response);

      if (output !== undefined) {
        this.trigger('executedMiddleware');
        return output;
      }
    }

    // trigger the executedMiddleware event
    this.trigger('executedMiddleware', this.route.middleware, this.route);
  }
Enter fullscreen mode Exit fullscreen mode

Here we fired up the executingMiddleware event before calling the middleware, and the executedMiddleware event after calling the middleware.

Any of both events will send the middleware list that will be executed and the route object as well.

Also notice that if a middleware is returning a value, we need to fire up the executedMiddleware event, so we'll fire it up before returning the value.

Now let's implement the executingAction and executedAction events.

They will be called just before and after calling the action.

// src/core/http/request.ts
  /**
   * Execute the request
   */
  public async execute() {
    // check for middleware first
    const middlewareOutput = await this.executeMiddleware();

    if (middlewareOutput !== undefined) {
      return middlewareOutput;
    }

    const handler = this.route.handler;

    // check for validation
    if (handler.validation) {
      if (handler.validation.rules) {
        const validator = new Validator(this, handler.validation.rules);

        try {
          await validator.scan(); // start scanning the rules
        } catch (error) {
          console.log(error);
        }

        if (validator.fails()) {
          const responseErrorsKey = config.get(
            "validation.keys.response",
            "errors",
          );
          const responseStatus = config.get("validation.responseStatus", 400);

          return this.response.status(responseStatus).send({
            [responseErrorsKey]: validator.errors(),
          });
        }
      }

      if (handler.validation.validate) {
        const result = await handler.validation.validate(this, this.response);

        if (result) {
          return result;
        }
      }
    }

    // call executingAction event
    this.trigger("executingAction", this.route);
    const output = await handler(this, this.response);

    // call executedAction event
    this.trigger("executedAction", this.route);

    return output;
  }
Enter fullscreen mode Exit fullscreen mode

We replaced the last returned line by calling the executingAction event before calling the action, and the executedAction event after calling the action then we returned the output.

You know what, the validation step is too long to be in the same method, so let's move it to a new method called validate and call it from the execute method.

// src/core/http/request.ts

  /**
   * Execute the request
   */
  public async execute() {
    // check for middleware first
    const middlewareOutput = await this.executeMiddleware();

    if (middlewareOutput !== undefined) {
      return middlewareOutput;
    }

    const handler = this.route.handler;

    // check for validation
    const validationOutput = await this.validate(handler.validation);

    if (validationOutput !== undefined) {
      return validationOutput;
    }

    // call executingAction event
    this.trigger("executingAction", this.route);
    const output = await handler(this, this.response);

    // call executedAction event
    this.trigger("executedAction", this.route);

    return output;
  }

  /**
   * Validate the request
   */
  protected async validate(validation: any) {
    if (!validation) return;

    if (validation.rules) {
      const validator = new Validator(this, validation.rules);

      try {
        await validator.scan(); // start scanning the rules
      } catch (error) {
        console.log(error);
      }

      if (validator.fails()) {
        const responseErrorsKey = config.get(
          "validation.keys.response",
          "errors",
        );
        const responseStatus = config.get("validation.responseStatus", 400);

        return this.response.status(responseStatus).send({
          [responseErrorsKey]: validator.errors(),
        });
      }
    }

    if (validation.validate) {
      const result = await validation.validate(this, this.response);

      if (result) {
        return result;
      }
    }
  }
Enter fullscreen mode Exit fullscreen mode

Pretty much cleaner now.

Now as long as the middleware and validation does not return a value that is undefined, the handler itself will be called.

And that's it!, we're done with the request events.

🎨 Conclusion

In this article, we learned how to implement events in our request class and reorganized it to be much readable and maintainable.

In our next article we'll start handle the response class and make our own one, then we'll implement events in it.

🚀 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)