59-Nodejs Course 2023: Break III: Enhancing Request And Response
Tags: node, typescript, mongodb, fastify
Published: 2022-11-11 20:03:39

---
title: 59-Nodejs Course 2023: Break III: Enhancing Request And Response
published: true
description: 
series: Nodejs Course 2023
tags: nodejs, typescript, mongodb, fastify
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pqwxwt1p6pxm70g7kmq9.png
# Use a ratio of 100:42 for best results.
---

In our previous article, we managed response events and saw how it works and how important it is, now let's enhance our http request and response classes.

## Removing validate method

As the validation process is related to validator, then let's move the validate method to the validator folder, we can actually make a helper function let's call it `validateAll` this function will validate the validation rules and the custom validation function (if exists) and return the result.

```ts
// src/core/validator/validateAll.ts
import config from "@mongez/config";
import { Request } from "core/http/request";
import { Response } from "core/http/response";
import { Route } from "core/router/types";
import Validator from "./validator";

/**
 * Validate the request route
 */
export default async function validateAll(
  validation: Route["handler"]["validation"],
  request: Request,
  response: Response,
) {
  if (validation?.rules) {
    const validator = new Validator(request, 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 response.send(
        {
          [responseErrorsKey]: validator.errors(),
        },
        responseStatus,
      );
    }
  }

  if (validation?.validate) {
    const result = await validation.validate(request, response);

    if (result) {
      return result;
    }
  }
}
```

What we did is we moved the `validate` method body to the method, we set the validation type to be `Route.handler.validation` type and passed the request and response classes, now let's import it in our request class.

But first let's update the validator index to export the `validateAll` function.

```ts
// src/core/validator/index.ts
export { default as validateAll } from "./validateAll";
// ...
```

Now updating the request class.

```ts
// src/core/http/request.ts
import { validateAll } from "core/validator";

// ...


  /**
   * 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 using validateAll helper function
    const validationOutput = await validateAll(
      handler.validation,
      this,
      this.response,
    );

    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;
  }
  // Don't forget to remove validate method
```

Now we moved the validate method to validator and got the result from it, if the result is `not undefined` then we return it, otherwise we continue with the request execution.

We've added the event trigger to validation class for the validation rules, but we didn't add it to the custom validation function, so let's add the `custom validation` event trigger.

Let's add the`customValidating` `customPasses` `customFails` `customDone` events to `ValidatioNEvent` type.

```ts
// src/core/validator/types.ts

/**
 * Validation event types
 */
export type ValidationEvent =
  | "validating"
  | "passes"
  | "fails"
  | "done"
  | "customValidating"
  | "customPasses"
  | "customFails"
  | "customDone"
```

Now le'ts update `Validator.trigger` to be public to trigger the custom validation events.

```ts
// src/core/validator/validator.ts
  /**
   * Trigger an event
   */
  public trigger(event: ValidationEvent, ...args: any[]) {
    this.events.trigger(event, ...args);
  }
```

Updating `validateAll` function

```ts
// src/core/validator/validateAll.ts
import config from "@mongez/config";
import { Request } from "core/http/request";
import { Response } from "core/http/response";
import { Route } from "core/router/types";
import Validator from "./validator";

/**
 * Validate the request route
 */
export default async function validateAll(
  validation: Route["handler"]["validation"],
  request: Request,
  response: Response,
) {
  if (validation?.rules) {
    const validator = new Validator(request, 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 response.send(
        {
          [responseErrorsKey]: validator.errors(),
        },
        responseStatus,
      );
    }
  }

  if (validation?.validate) {
    Validator.trigger("customValidating", validation.validate);
    const result = await validation.validate(request, response);

    Validator.trigger("customDone", result);

    // if there is a result, it means it failed
    if (result) {
      Validator.trigger("customFails", result);

      // check if there is no response status code, then set it to config value or 400 as default
      if (!response.statusCode) {
        response.setStatusCode(config.get("validation.responseStatus", 400));
      }
      return result;
    }

    Validator.trigger("customPasses");
  }
}
```

We added the events as mentioned earlier, but i also added the response status code check step, as if the custom validation did not set response code, then we need to set it internally to the config value or `400` as default.

## Capturing Response Body

Now we can not tell if we have the response body set or not, so let's add a property called `body` that will hold the response body when set in the `send` method.

```ts
// src/core/http/response.ts
export class Response {
  // ...
  
  /**
   * Current response body
   */
  protected currentBody: any;

  /**
   * Get Current response body
   */
  public get body() {
    return this.currentBody;
  }
}
```

Now let's capture it in the `send` method.

```ts
// src/core/http/response.ts
  /**
   * Send the response
   */
  public send(body: any, statusCode?: number) {
    this.currentBody = body;

    // ...
  }
```

## Reset response data on response send

Now imagine that you have set the response body in the `/` route, now the user has navigated to `/users`, if you tried to log `response.body` you will see that there is a value, so we need to reset the response body on each response send, we can use Fastify [onResponse](https://www.fastify.io/docs/latest/Reference/Hooks/#onresponse) hook as it is called after each response is sent to clear the response body, route and current status code.

In that case, we 're going to visit `connectToServer.ts` file to listen to it.

```ts
// src/core/http/connectToServer.ts
import multipart from "@fastify/multipart";
import config from "@mongez/config";
import router from "core/router";
import Fastify from "fastify";
import response from "./response";

export default async function connectToServer() {
  const server = Fastify();

  server.register(multipart, {
    attachFieldsToBody: true,
  });

  // call reset method on response object to response its state
  server.addHook("onResponse", response.reset.bind(response));

  router.scan(server);

  try {
    // 👇🏻 We can use the url of the server
    const address = await server.listen({
      port: config.get("app.port"),
      host: config.get("app.baseUrl"),
    });

    console.log(`Start browsing using ${address}`);
  } catch (err) {
    console.log(err);

    server.log.error(err);
    process.exit(1); // stop the process, exit with error
  }
}
```

We added a new hook (Event) to Fastify object, that when the `onResponse` event is triggered, it will call the `reset` method of the response object.

Now let's implement the `reset` method.

```ts
// src/core/http/response.ts
  /**
   * Reset the response state
   */
  public reset() {
    this.route = undefined;
    this.currentBody = null;
    this.currentStatusCode = 200;
  }
```

Now the typescript will start complaining, because we have not defined the `route` property, so let's update it to be optional.

```ts
// src/core/http/response.ts

export class Response {
  // ...

  /**
   * Current route
   */
  protected route?: Route;

  // ...
}
```

## Caching Request Payload

Caching request payload will decrease the process we do on the request inputs such as `parseInputValue`, so we need to do it once the `setRequest` method receives the new request.

```ts
// src/core/http/response.ts

  /**
   * Parsed Request Payload
   */
  protected payload: any = {};

  /**
   * Set request handler
   */
  public setRequest(request: FastifyRequest) {
    this.request = request;

    this.parsePayload();

    return this;
  }
```

Now let's implement the `parsePayload` method.

```ts
// src/core/http/response.ts

  /**
   * Parse the payload and merge it from the request body, params and query string
   */
  protected parsePayload() {
    this.payload.body = this.parseBody();
    this.payload.query = this.request.query;
    this.payload.params = this.request.params;
    this.payload.all = {
      ...this.payload.body,
      ...this.payload.query,
      ...this.payload.params,
    };
  }

  
  /**
   * Parse body payload
   */
  private parseBody() {
    const body: any = {};
    for (const key in this.request.body) {
      const keyData = this.request.body[key];

      if (Array.isArray(keyData)) {
        body[key] = keyData.map(this.parseInputValue.bind(this));
      } else {
        body[key] = this.parseInputValue(keyData);
      }
    }

    return body;
  }
  
  /**
   * Update the body getter to receive the request body from payload body property
   * Get request body
   */
  public get body() {
    return this.payload.body;
  }
  
  /**
   * Get all inputs
   */
  public all() {
    return this.payload.all;
  }
```

The payload object will hold **four** objects, the `body`, `query`, `params` and `all` which is the combination of all of them.

The `body` object will hold the parsed body, the `query` object will hold the query string, the `params` object will hold the route params and the `all` object will hold the combination of all of them.

Now we can use the `all` method to get all inputs using `payload.all` instead of `payload.body`, `payload.query` and `payload.params`.

## Updating input method

Now le'ts enhance the input method to take the value from `payload.all` property, but also we need to accept `dot.notation` syntax.

```ts
import { get } from '@mongez/reinforcements';

// ...


  /**
   * Get request input value from query string, params or body
   */
  public input(key: string, defaultValue: any = null) {
    return get(this.payload.all, key, defaultValue);
  }
```

We used `get` method from `@mongez/reinforcements` package to get the value from the object using `dot.notation` syntax.

Now we can get a value from request input using dot notation.

```ts
const username = response.input('user.name', 'Hasan');
```

And that's it for now!

## 🎨 Conclusion

In this article, we have enhanced our request class, moved validation process in a separated function and added custom validation events.

We also added a `response body` property to store current body and added a `reset` method to reset response data.

Later we enhanced the request payload and made it more controlled and performant.

## 🚀 Project Repository

You can find the latest updates of this project on [Github](https://github.com/hassanzohdy/nodejs-2023)

## 😍 Join our community

Join our community on [Discord](https://discord.gg/pb2vmdfhGf) 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](https://www.youtube.com/playlist?list=PLGO8ntvxgiZMJc7RN2lIq9WmMOlWZGzmz), 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](https://dev.to/hassanzohdy/event-driven-architecture-the-best-paradigm-that-i-love-to-work-with-in-javascript-and-node-js-1gnk)
* [Best Practices For Case Styles: Camel, Pascal, Snake, and Kebab Case In Node And Javascript](https://dev.to/hassanzohdy/best-practices-for-case-styles-camel-pascal-snake-and-kebab-case-in-node-and-javascript-55oi)
* [After 6 years of practicing MongoDB, Here are my thoughts on MongoDB vs MySQL
](https://dev.to/hassanzohdy/after-6-years-of-practicing-mongodb-here-are-my-thoughts-on-mongodb-vs-mysql-574b)

Packages & Libraries

* [Collections: Your ultimate Javascript Arrays Manager](https://dev.to/hassanzohdy/collections-your-ultimate-javascript-array-handler-3o15)
* [Supportive Is: an elegant utility to check types of values in JavaScript](https://dev.to/hassanzohdy/supportive-is-an-elegant-utility-to-check-types-of-values-in-javascript-1b3e)
* [Localization: An agnostic i18n package to manage localization in your project](https://dev.to/hassanzohdy/mongez-localization-the-simplest-way-to-translate-your-website-regardless-your-favorite-framework-4gi3)

 React Js Packages

* [useFetcher: easiest way to fetch data in React Js](https://dev.to/hassanzohdy/usefetcher-the-easiest-way-to-fetch-data-in-react-45o9)

Courses (Articles)

* [React Js: Let"s Create File Manager With React Js and Node Js](https://dev.to/hassanzohdy/lets-create-a-file-manager-from-scratch-with-react-and-typescript-chapter-i-a-good-way-to-expand-your-experience-5g4k)