DEV Community 👩‍💻👨‍💻

Cover image for 79-Nodejs Course 2023: Resources: Recursive Resources
Hasan Zohdy
Hasan Zohdy

Posted on

79-Nodejs Course 2023: Resources: Recursive Resources

In this article, we'll see how to use resource as an output type and how to implement it.

Purpose

Why would we need to assign to an output key a resource type? well, because we're using MongoDB, which means a document(s) can be stored in a single key in another document, for example we can store createdBy object in literally any document (which we'll do later), and so on.

So we need to map that createdBy object to use its proper resource to make a proper response output.

We are going to learn how to use two things, checking wether if a variable is a typeof class, and also a partial recursion, which means a class/function that can call itself (in somehow).

Before implementing

We're going to add a new feature in our Resource class, which is allowing outputs to be resources, this requires us to add a check for the valueType to check if it is a Resource type, so i want to show you how to check if a variable is a type of class, (The class itself not the object is instance of it).

// some-file.ts
class MyClass {

}

const A = MyClass;

// check if A is a `MyClass` type

if (A.prototype instanceof MyClass) {
  // A is a MyClass type
}
Enter fullscreen mode Exit fullscreen mode

We used here prototype to check if the class is an instance of another class, so we can use this to check if the valueType is a Resource type.

So we can say if we want to check if variable is a class type, we use prototype, if we want to check if variable is an object/instance of class we use instanceof directly.

Implementation

Now let's see how we can do this.

It's very simple, we just assign to the output key, let's say createdBy the value of UserResource type and in our toJSON we'll add another check if the given value type is typeof a Resource, then we'll pass the data to it, otherwise we'll skip the key entirely.

// src/core/resources/resource.ts
import { get } from "@mongez/reinforcements";

// this will be used to skip the output property if it is missing from the given resource
const missingKey = Symbol("missing");

export default class Resource {
  /**
   * Constructor
   */
  public constructor(protected resource: any = {}) {
    //
  }

  /**
   * Output shape
   */
  protected output: any = {};

  /**
   * {@inheritDoc}
   */
  public toJSON() {
    // final output
    const data: Record<string, any> = {};

    // loop through the output property
    for (const key in this.output) {
      // get the value type
      const valueType = this.output[key];
      // get the value, and also make sure to skip the output property if it is missing from the given resource
      let value = get(this.resource, key, missingKey);

      // skip the output property if it is missing from the given resource
      if (value === missingKey) {
        continue;
      }

      if (typeof valueType === "string") {
        // cast the value
        value = this.cast(value, valueType);
      } else if (valueType.prototype instanceOf Resource) {
        // if the value type is a resource, then pass the value to it
        value = new valueType(value).toJSON();
      } else if (typeof valueType === "function") {
        // call the custom output handler
        value = valueType(value);
      }

      // just for now sett the output value to the data
      data[key] = value;
    }

    return data;
  }

  /**
   * Builtin casts
   */
  protected cast(value: any, type: string) {
    switch (type) {
      case "number":
        return Number(value);
      case "float":
      case "double":
        return parseFloat(value);
      case "int":
      case "integer":
        return parseInt(value);
      case "string":
        return String(value);
      case "boolean":
        return Boolean(value);
      default:
        return value;
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Here we just added a little if statement, which is checking if the value type is a Resource type, if so, then we'll pass the value to it, otherwise we'll skip the key entirely.

Now we can update our UserResource class

// src/app/users/resources/user-resource.ts
import Resource from 'core/resources/resource';
import { uploadsUrl } from 'core/utils/urls';

export default class UserResource extends Resource {
  /**
   * Output shape
   */
  protected output: any = {
    id: 'number',
    name: 'string',
    email: 'string',
    age: 'number',
    avatar: uploadsUrl,
    createdBy: UserResource,
 };
}
Enter fullscreen mode Exit fullscreen mode

This should be working just fine with you, pass to the data a key named createdBy and pass to it the object.

The point here is to map that data into a proper output using the UserResource class.

This is also called Recursive Resource because we're using the same resource to map the data to the output.

🎨 Conclusion

We just learnt how to implement a recursive call for resources inside each other, also how to check if a variable is a type of class.

☕♨️ 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)

12 APIs That You Will Love

>> Check out this classic DEV post <<