55-Nodejs Course 2023: Request Events
Hasan Zohdy
Posted on November 10, 2022
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';
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);
}
}
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;
}
}
}
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);
}
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;
}
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;
}
}
}
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
- Event Driven Architecture: A Practical Guide in Javascript
- Best Practices For Case Styles: Camel, Pascal, Snake, and Kebab Case In Node And Javascript
- After 6 years of practicing MongoDB, Here are my thoughts on MongoDB vs MySQL
Packages & Libraries
- Collections: Your ultimate Javascript Arrays Manager
- Supportive Is: an elegant utility to check types of values in JavaScript
- Localization: An agnostic i18n package to manage localization in your project
React Js Packages
Courses (Articles)
Posted on November 10, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.