In our previous chapter, we saw how to use our resource and added couple features boot
and extend
which led us to make our toJSON
method to be async
, that's where We got a serious issue, as JSON.stringify
doesn't support async
functions, so we need to find a way to make it work.
Async Json Parser
Remember our response
class? yes that one that we didn't use it yet, it's time to use it now, we'll use it to parse the data before sending it to the response, but before updating it, we need to update our request
class to pass the output of the handler to the response class.
// src/core/http/request.ts
// ...
/**
* Execute the request
*/
public async execute() {
// check for middleware first
const middlewareOutput = await this.executeMiddleware();
if (middlewareOutput !== undefined) {
// 👇🏻 send the response
return this.response.send(middlewareOutput);
}
const handler = this.route.handler;
// 👇🏻 check for validation using validateAll helper function
const validationOutput = await validateAll(
handler.validation,
this,
this.response,
);
if (validationOutput !== undefined) {
// 👇🏻 send the response
return this.response.send(validationOutput);
}
// call executingAction event
this.trigger("executingAction", this.route);
const output = await handler(this, this.response);
// call executedAction event
this.trigger("executedAction", this.route);
// 👇🏻 send the response
await this.response.send(output);
}
So instead of returned the output from middleware, validation or the handler, we are going to pass that output to the response's send
method so we can handle that output freely.
Now let's update our response
class to handle the output.
// src/core/http/response.ts
// ...
/**
* Send the response
*/
public async send(data?: any, statusCode?: number) {
if (data) {
this.currentBody = data;
}
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;
}
This is our previous send method, we're going to add a new line after that first check.
// src/core/http/response.ts
// ...
/**
* Send the response
*/
public async send(data?: any, statusCode?: number) {
if (data) {
this.currentBody = data;
}
// parse the body and make sure it is transformed to sync data instead of async data
data = await this.parseBody();
// ...
}
We added a new line here, which is going to parse our currentBody
data, and make sure it's transformed to sync data instead of async data, so we can send it to the response.
Body Parser
Now let's create our parseBody
method, but before we go to the implementation let me tell you the workflow of this method.
Basically, this method will call another method called parse
and pass the currentBody
to that method.
Why would we do this? because we're going in some recursion here, so the parse
method will receive any kind of data and handle it accordingly regardless its the main body or a nested object.
Now returning to the parse method workflow
- If the given value is a
falsy
value, we'll return it as it is. - Check if the passed data has
toJSON
method, if so thenawait
it and return the result. - Check if the passed data is an
array
, if so then loop over it and call theparse
method on each item and return the result. - If it is not plain object and not an array and does not have
toJSON
then we're going to just return it. - If it is a plain object then we're going to loop over its keys and call the
parse
method on each value and return the result.
Actually we're going to change the point of array
, we're going to check if the value is iterable
instead of checking if it is an array, because we want to support any kind of iterable data, not just arrays.
Now let's go to the implementation.
// src/core/http/response.ts
// we'll need it here to make couple checks later
import Is from "@mongez/supportive-is";
// ...
export class Response {
// ...
/**
* Parse body
*/
protected async parseBody() {
return await this.parse(this.currentBody);
}
// ...
}
Now let's create the parse
method.
// src/core/http/response.ts
// ...
export class Response {
// ...
/**
* Parse the given value
*/
protected async parse(value: any): Promise<any> {
// if it is a falsy value, return it
if (!value) return value;
// if it has a `toJSON` method, call it and await the result then return it
if (value.toJSON) {
return await value.toJSON();
}
// if it is iterable, an array or array-like object then parse each item
if (Is.iterable(value)) {
return await Promise.all(
Array.from(value).map((item: any) => {
return this.parse(item);
}),
);
}
// if not plain object, then return it
if (!Is.plainObject(value)) {
return value;
}
// loop over the object and check if the value and call `parse` on it
for (const key in value) {
const subValue = value[key];
value[key] = await this.parse(subValue);
}
return value;
}
}
The code is pretty much as the flow steps i wrote above, we checked if its a falsy
value then we returned it, if it has a toJSON
method then await the call of it and return it.
If it is iterable
(That's why we imported Supportive Is method) then we're going to loop over it and call the parse
method on each item and return the result.
If it is not plainObject
then we're going to return it.
If it is a plainObject
then we're going to loop over its keys and call the parse
method on each value and return the result.
Regarding the iterable
data, you might see the code a little weird, we used Promise.all
to await all promises to be resolved, that Promise.all
method receives an array, that's why we used Array.from
to convert the iterable data (arrays are also considered iterable) to an array, then map each value to be called with the parse
method.
Now if we tried again returning the resource, it will work just fine.
🎨 Conclusion
In this section, we customized the final output of the response, we make our own data parser to make every async
data to be sent as sync
data.
☕♨️ Buy me a Coffee ♨️☕
If you enjoy my articles and see it useful to you, you may buy me a coffee, it will help me to keep going and keep creating more content.
🚀 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)
Top comments (0)