DEV Community

Cover image for 58-Nodejs Course 2023: Response Events
Hasan Zohdy
Hasan Zohdy

Posted on

58-Nodejs Course 2023: Response Events

We have created our custom response class earlier. Now we want to add the response events.

Response Events

Let's first review the response events list

  • response.sending: Fired when the response is about to be sent.
  • response.sent: Fired when the response has been sent.
  • response.success: Fired when the response has been sent and the status code is 2xx.
  • response.successCreate: Fired when the response has been sent and the status code is 201.
  • response.badRequest: Fired when the response has been sent and the status code is 400.
  • response.unauthorized: Fired when the response has been sent and the status code is 401.
  • response.throttled: Fired when the response has been sent and the status code is 429, which means the request has been throttled.
  • response.forbidden: Fired when the response has been sent and the status code is 403.
  • response.notFound: Fired when the response has been sent and the status code is 404.
  • response.serverError: Fired when the response has been sent and the status code is 500.
  • response.error: Fired when the response has been sent and the status code is 4xx or 5xx.

The trick here is to check the status code at the send method whereas the send method will be used by all the response methods.

But let's first define our ResponseEvent Type

// src/http/type.ts
// ...

/**
 * Response Event Types
 */
export type ResponseEvent =
  /**
   * Triggered before sending the response
   */
  | "sending"
  /**
   * Triggered after sending the response regardless of the response status code
   */
  | "sent"
  /**
   * Triggered after sending the response if the response status code is 2xx
   */
  | "success"
  /**
   * Triggered after sending the response if the response status code is 201
   */
  | "successCreate"
  /**
   * Triggered after sending the response if the response status code is 400
   */
  | "badRequest"
  /**
   * Triggered after sending the response if the response status code is 401
   */
  | "unauthorized"
  /**
   * Triggered after sending the response if the response status code is 403
   */
  | "forbidden"
  /**
   * Triggered after sending the response if the response status code is 404
   */
  | "notFound"
  /**
   * Triggered after sending the response if the response status code is 429
   */
  | "throttled"
  /**
   * Triggered after sending the response if the response status code is 500
   */
  | "serverError"
  /**
   * Triggered after sending the response if the response status code is 4xx or 5xx
   */
  | "error";
Enter fullscreen mode Exit fullscreen mode

We just set the type of the event. Now let's add the event emitter and listener to the Response class

// src/http/response.ts
import { ResponseEvent } from "./types";
import events, { EventSubscription } from '@mongez/events';

export class Response {
  // ...

  /**
   * Add a listener to the response event
   */
  protected on(event: ResponseEvent, listener: (...args: any[]) => void): EventSubscription {
    return events.subscribe(event, listener);
  }

  /**
   * Trigger the response event
   */
  protected trigger(event: ResponseEvent, ...args: any[]) {
    events.trigger(event, ...args);
  }

  /**
   * Send the response
   */
  public send(data: any, statusCode = 200) {
    // ...

    // trigger the sending event
    this.trigger('sending', data, statusCode);

    // send the response status and body
    this.baseResponse.status(statusCode).send(data);

    // trigger the sent event
    this.trigger('sent', data, statusCode);

    // trigger the success event if the status code is 2xx
    if (statusCode >= 200 && statusCode < 300) {
      this.trigger('success', data, statusCode);
    }

    // trigger the successCreate event if the status code is 201
    if (statusCode === 201) {
      this.trigger('successCreate', data, statusCode);
    }

    // trigger the badRequest event if the status code is 400
    if (statusCode === 400) {
      this.trigger('badRequest', data, statusCode);
    }

    // trigger the unauthorized event if the status code is 401
    if (statusCode === 401) {
      this.trigger('unauthorized', data, statusCode);
    }

    // trigger the forbidden event if the status code is 403
    if (statusCode === 403) {
      this.trigger('forbidden', data, statusCode);
    }

    // trigger the notFound event if the status code is 404
    if (statusCode === 404) {
      this.trigger('notFound', data, statusCode);
    }

    // trigger the throttled event if the status code is 429
    if (statusCode === 429) {
      this.trigger('throttled', data, statusCode);
    }

    // trigger the serverError event if the status code is 500
    if (statusCode === 500) {
      this.trigger('serverError', data, statusCode);
    }

    // trigger the error event if the status code is 4xx or 5xx
    if (statusCode >= 400) {
      this.trigger('error', data, statusCode);
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

So we added our usual methods, on and trigger to manage events, then in our send method we trigger the sending event, then we send the response, then we trigger the sent event, then we check the status code and trigger the appropriate event.

Now we can listen to any event from anywhere in our application.

// src/app/users/routes.ts
import response from 'core/http/response';

response.on('sending', (data, statusCode) => {
  console.log('sending', data, statusCode);
});

response.on('sent', (data, statusCode) => {
  console.log('sent', data, statusCode);
});

response.on('success', (data, statusCode) => {
  console.log('success', data, statusCode);
});

response.on('successCreate', (data, statusCode) => {
  console.log('successCreate', data, statusCode);
});
// and so on
Enter fullscreen mode Exit fullscreen mode

Importance of Response Events

You might think this is just useless addition to our base code, but actually it's not. It's very important to have this feature in our application.

Why? well let's see with a real world example.

Imagine that you want to add a value to each response regardless where the response is coming from any handler, for example i want to return the current user information with the request to make it easier for the frontend to use it and update current user information, we can do it like this.

// src/app/users/routes.ts
import response from 'core/http/response';
import user from 'core/auth/user';

response.on('sending', (data, statusCode) => {
    if (! data.user) {
        data.user = user.data();
    }
});
Enter fullscreen mode Exit fullscreen mode

Which we'll actually do later in our application, but the point here is that we can add this value to all the responses regardless where the response is coming from.

We can actually improve it more, what about sending the current route handler to the response events? let's do it.

The route handler is sent to the request class which is updating the response class with Fastify reply instance, we can also update the response class with the route handler object.

// src/http/request.ts

// ...
  /**
   * Set route handler
   */
  public setRoute(route: Route) {
    this.route = route;

    // pass the route to the response object
    this.response.setRoute(route);

    return this;
  }
Enter fullscreen mode Exit fullscreen mode

We added here a new line to pass the route to the response object, now we need to update the response class to accept the route.

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

export class Response {
    // ...
  /**
   * Current route
   */
  protected route!: Route;

  /**
   * Set route handler
   */
  public setRoute(route: Route) {
    this.route = route;

    return this;
  }
}
Enter fullscreen mode Exit fullscreen mode

Actually there is something off here, we added a statusCode method to receive the statusCode, but we only checked for that one in the send class, so we need to make sure that both are synced with each other, let's improve it a little bit.

// src/http/response.ts

export class Response {
    // ...

  /**
   * Current status code
   */
  protected currentStatusCode?: number;

  /**
   * Set the status code
   */
  public setStatusCode(statusCode: number) {
    this.currentStatusCode = statusCode;

    return this;
  }
}
Enter fullscreen mode Exit fullscreen mode

Now we updated the status method and renamed it to setStatusCode why? because its more readable :p, now we added a new property to store current status code.

Now let's update the send method

// src/http/response.ts

export class Response {
    // ...

  /**
   * Send the response
   */
  public send(data: any, statusCode?: number) {
    if (statusCode) {
      this.currentStatusCode = statusCode;
    }

    if (!this.currentStatusCode) {
      this.currentStatusCode = 200;
    }

    // trigger the sending event
    this.trigger("sending", this.currentStatusCode, data);

    this.baseResponse.status(this.currentStatusCode).send(data);

    // trigger the sent event
    this.trigger("sent", this.currentStatusCode, data);

    // trigger the success event if the status code is 2xx
    if (this.currentStatusCode >= 200 && this.currentStatusCode < 300) {
      this.trigger("success", data, this.currentStatusCode, this.route);
    }

    // trigger the successCreate event if the status code is 201
    if (this.currentStatusCode === 201) {
      this.trigger("successCreate", data, this.currentStatusCode, this.route);
    }

    // trigger the badRequest event if the status code is 400
    if (this.currentStatusCode === 400) {
      this.trigger("badRequest", data, this.currentStatusCode, this.route);
    }

    // trigger the unauthorized event if the status code is 401
    if (this.currentStatusCode === 401) {
      this.trigger("unauthorized", data, this.currentStatusCode, this.route);
    }

    // trigger the forbidden event if the status code is 403
    if (this.currentStatusCode === 403) {
      this.trigger("forbidden", data, this.currentStatusCode, this.route);
    }

    // trigger the notFound event if the status code is 404
    if (this.currentStatusCode === 404) {
      this.trigger("notFound", data, this.currentStatusCode, this.route);
    }

    // trigger the throttled event if the status code is 429
    if (this.currentStatusCode === 429) {
      this.trigger("throttled", data, this.currentStatusCode, this.route);
    }

    // trigger the serverError event if the status code is 500
    if (this.currentStatusCode === 500) {
      this.trigger("serverError", data, this.currentStatusCode, this.route);
    }

    // trigger the error event if the status code is 4xx or 5xx
    if (this.currentStatusCode >= 400) {
      this.trigger("error", data, this.currentStatusCode, this.route);
    }

    return this;
  }
}
Enter fullscreen mode Exit fullscreen mode

Now we updated send method to accept the second argument as optional, if it is set, then we'll override current status code, if not then we need to check if there is no current status code, then we'll set it to 200.

Now we can use it like this

// src/app/users/routes.ts

import response from 'core/http/response';

response.on('sending', (statusCode, data, route) => {
  console.log('sending', statusCode, data);
});
Enter fullscreen mode Exit fullscreen mode

And that's it, now we can listen to any event from anywhere in our application.

🎨 Conclusion

In this article, we saw how to use response events how important they are, we also modified the status code to be more controlled and we added a new property to set the status code.

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