DEV Community

Cover image for 84-Nodejs Course 2023: Resources: Boot And Extend
Hasan Zohdy
Hasan Zohdy

Posted on

84-Nodejs Course 2023: Resources: Boot And Extend

We have done a good job in our resources so far, let's go one more step, and define a way to customize our resource output using boot and extend methods.

The need

Why would we need these methods? Well, sometimes we need to customize the output of our resource, for example, we may need to add a new key to the output, or we may need to remove a key from the output, or we may need to change the value of a key in the output.

These changes are mostly conditional, which means, for example, if current resource is User Resource and the current user is the same as the one in the resource, then we may need to add a new key to the output, for example, let's say we want to add a new key called cart to the output, but we only want to add this key if the current user is the same as the user in the resource.

Another use case, if you're working with courses api, and you're returning list of courses, or just a single course, we want to add a new key to the final output called isSubscribed which is a boolean value, and we want to set this value to true if the current user is subscribed to the course, otherwise the key will be set to false or may be not returned at all, so that's a must feature to be implemented in our resources.

Boot And Extend

Basically, these are two methods, the first one boot will be called before transforming our output property to the final output, and the second one extend will be called after transforming our output property to the final output.

So you can use boot to add new keys to the output, replace or remove any keys from it and you can use extend to add new keys to the output, replace or remove any keys from it but after the output has been transformed.

Implementation

Implementation is so simple, these are two methods (non-static of course), the first one will be called before transforming the output, and the second one will be called after transforming the output.

Please note that we'll make these two methods async methods so you can perform any async operation inside them like fetching data from the database.

// core/resources/resource.ts

// ...

export default class Resource {

  /**
   * Boot method
   * Called before transforming the resource
   */
  public async boot() {
    //
  }

  /**
   * Extend the resource output
   * Called after transforming the resource
   */
  public async extend() {
    //
  }
  // ...
}
Enter fullscreen mode Exit fullscreen mode

Simple two async empty methods, now we are going to update our toJSON function to be async method and call these two methods before and after transforming the output.

// core/resources/resource.ts

// ...

export default class Resource {

  // ...

  /**
   * Transform resource to object, that's going to be used as the final output
   */
  public async toJSON() {
    await this.boot();

    await this.transformOutput();

    await this.extend();

    return this.data;
  }
  // ...
}
Enter fullscreen mode Exit fullscreen mode

You might be wondered, where is that giant code block that was in our toJSON method, well i just moved it to transformOutput method, that's much cleaner now.

We can also make any callable function that will be used in the transforming process to be async as well.

Async transformation

Now let's update our transformOutput method to be async method as well.

Inside it, there will be a call to transformValue this method must be awaited so we can make it async as well.

// core/resources/resource.ts

// ...

export default class Resource {

  // ...

  /**
   * Transform final output
   */
  protected async transformOutput() {
    for (const key in this.output) {
      // first check if key is disabled
      if (this.isDisabledKey(key)) continue;

      if (!this.isAllowedKey(key)) continue;

      // get value type
      const valueType = this.output[key];

      // now get the value from the given resource data
      const value = get(
        this.resource,
        key,
        get(this.defaults, key, missingKey),
      );

      if (value === missingKey) {
        continue;
      }

      if (Is.empty(value)) continue;

      if (Array.isArray(value)) {
        this.data[key] = value.map(
          async item => await this.transformValue(item, valueType),
        );
      } else {
        this.data[key] = await this.transformValue(value, valueType);
      }
    }
  }

  // ...
}
Enter fullscreen mode Exit fullscreen mode

I added async keyword to the transformOutput method, and i added await keyword to the transformValue method call.

Note that we have to make the callback in the map function to be async as well, so we can await the transformValue method call.

Now let's update our transformValue method to be async method as well.

// core/resources/resource.ts

// ...


export default class Resource {

  // ...

  /**
   * Transform final output
   */
  protected async transformOutput() {
    for (const key in this.output) {
      // first check if key is disabled
      if (this.isDisabledKey(key)) continue;

      if (!this.isAllowedKey(key)) continue;

      // get value type
      const valueType = this.output[key];

      // now get the value from the given resource data
      const value = get(
        this.resource,
        key,
        get(this.defaults, key, missingKey),
      );

      if (value === missingKey) {
        continue;
      }

      if (Is.empty(value)) continue;

      if (Array.isArray(value)) {
        this.data[key] = value.map(
          async item => await this.transformValue(item, valueType),
        );
      } else {
        this.data[key] = await this.transformValue(value, valueType);
      }
    }
  }

  /**
   * Transform value
   */
  protected async transformValue(value: any, valueType: any) {
    if (typeof valueType === "string") {
      value = this.cast(value, valueType);
    } else if (valueType.prototype instanceof Resource) {
      if (!this.isValidResourceValue(value)) return null;
      value = new valueType(value);
    } else if (typeof valueType === "function") {
      value = await valueType.call(this, value);
    }

    return value;
  }
  // ...
}
Enter fullscreen mode Exit fullscreen mode

I added async keyword to the transformValue method, and i added await keyword to the valueType function call.

Now let's give it a try in our UserResource class.

// src/app/users/resources/user-resource.ts
import Resource from "core/resources/resource";

export default class UserResource extends Resource {
  /**
   * {@inheritDoc}
   */
  public async boot(): Promise<void> {
    this.data.welcome = true;
  }

  /**
   * {@inheritDoc}
   */
  public async extend(): Promise<void> {
    this.data.goodbye = true;
  }
}
Enter fullscreen mode Exit fullscreen mode

Now you should see something like this:

  {
    "welcome": true,
    "isActive": true,
    "isPhoneVerified": false,
    "name": "John Doe",
    "email": "hassanzohdy@gmail.com",
    "image": "http://127.0.0.1:3000/uploads/users/my-image.jpg",
    "goodbye": true
  },
Enter fullscreen mode Exit fullscreen mode

But this really is not going to work, why? because our method toJSON is basically sync method and now we changed it to be async method, thus this won't work anymore.

And that's going to be our next topic.

🎨 Conclusion

In this chapter, we talked about the importance of booting and extending resources and why it should be in our codebase.

In our next chapter, we're going to see how to make our toJSON method to be async method.

That's where our powered Response class comes in.

β˜•β™¨οΈ 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

Packages & Libraries

React Js Packages

Courses (Articles)

Top comments (0)