Primate Controller

A Flexible and Dynamic CRUD Controller

The PrimateController class is a key component in the Primate framework, designed for handling generic CRUD (Create, Read, Update, Delete) operations in web applications built with Node.js and Express.js. This controller is highly flexible, dynamic, and reusable, enabling rapid API prototyping with minimal code duplication across different models.

Imports

  • createError: Used for creating HTTP errors.

  • PrimateService: A generic service to handle CRUD operations if no custom service is defined.

  • fs: Used for reading files, checking if a service file exists.

  • chalk: Provides colored output for logging warnings and info.

  • primate: Some application-wide object that might contain hooks or configurations.

  • pluralize: Converts singular words to their plural form.

Example Flow

If you instantiate the controller like this:

const userController = new PrimateController('User');

Model Name Transformation:

  • modelName: "User"

  • serviceFileName: "user"

  • this.singular: "user"

  • this.plural: "users"

If the file ./entities/users/user.service.js exists, it will be dynamically imported as the service for the User model.

If no service is found or passed, the controller logs a warning and will default to using the generic PrimateService for CRUD operations.

Options

In the PrimateController class, the options parameter plays a crucial role in allowing for flexibility and customization in how CRUD operations are performed. These options are passed during the instantiation of the controller and are used in various parts of the class to adjust the behavior of the methods according to specific needs.

Let's break down where and how the options are used throughout the PrimateController class:

1. During Controller Initialization (Constructor)

In the constructor, options are assigned to the controller instance as this.options:

this.options = options;

These options can contain custom configurations such as:

  • Custom services (e.g., options.service)

  • Authenticated user information (e.g., options.user)

  • Additional settings or hooks to modify the behavior of CRUD operations (e.g., logging, additional query constraints)

2. Inside the all() Method

In the all() method, the options object is used to:

  • Pass user information: If the request contains a user (req.user), this information is added to options. For example, the user.id is attached to the options:

    if (req.user) this.options.user = req.user.payload;

  • Hooks and global modifications: Before fetching data, the method checks if there are any global hooks defined in primate.hooks.all. If so, it calls this hook with the options object:

    if (primate.hooks?.all) {
        primate.hooks.all(req, res, next, this.options);
    }

    This allows for global behaviors, like logging or modifying the request before processing, to be added via the options.

  • Service usage: The options are also passed when calling the service (either custom or generic PrimateService):

    const { count, data } = await this.service.all(req.query, this.options);

3. Inside the create() Method

In the create() method, the options are used for:

  • Attaching the current user: Just like in the all() method, if the request contains a user, their information is added to the options:

    if (req.user) options.idUser = req.user.payload.id;

  • Service usage: When creating a new record, the options are passed to the custom service or the generic PrimateService:

    const record = await service.create(req.body, this.entity, options);

This allows for custom logic during the creation process, such as associating the new record with a specific user or applying additional constraints.

4. Inside the get() Method

The options are passed when fetching a single record in the get() method:

  • Passing options to the service: Similar to other methods, the options object is passed along with the query to the service or PrimateService:

    const record = await service.get(req.params.id, this.entity, req.query, this.options);

This can help in scenarios where additional filters, restrictions, or logging need to be applied to single record retrieval.

5. Inside the update() Method

In the update() method, the options are used in the following ways:

  • Adding user information: If the user is present, their ID is added to the options to ensure updates are linked to the appropriate user:

    if (user) {
        options.idUser = user.payload.id;
    }

  • Passing options to the service: The options are passed both when fetching the old record and when updating the record:

    oldRecord = await PrimateService.get(id, entity, req.query, options);
    updatedRecord = await PrimateService.update(id, data, entity, options);

This is useful if certain update conditions depend on the user, such as only allowing the creator of a record to update it.

6. Inside the delete() Method

In the delete() method:

  • Passing options to the service: The options are passed when deleting a record:

    record = await this.service.delete(id, this.options);

Again, this allows for custom behaviors during the deletion process, such as checking if the user has permission to delete the record.

7. Inside the serviceCall() Method

In this method, which dynamically calls a service function:

  • Options can be part of the request: Though the options are not explicitly used here, they could be passed as part of the service function being called, depending on how that service is designed to work.

8. Inside the updateMetas() Method

In the updateMetas() method, the options object is not directly passed to PrimateService, but it could easily be modified to include it if needed for custom metadata handling.

Last updated