5. Adding Validation and System Logging
Tags: nestjs, mongodb, validation, logging
Published: 2023-11-07 03:26:05

## Introduction:

In this tutorial, we will explore how to add validation and system logging to our NestJS application. Adding validation to user input is considered best practice as it ensures that user input is reliable and secure. 
Additionally, we will enhance our system logging using the NestJS Pino library, which makes logging requests and responses a breeze. By the end of this tutorial, you'll have a better understanding of how to validate user input and improve your application's logging capabilities.

## Step 1: Installing Dependencies
To begin, we need to install two essential dependencies: **class-validator** and **class-transformer**. These libraries will help us validate user input effectively. You can install them using npm:

```
npm install class-validator class-transformer
```

## Step 2: Enhancing Logging with NestJS Piano
We'll improve our system logging by using the NestJS **Pino library**, which automates request and response logging. It also allows us to correlate logs effortlessly without additional configuration. Here's how to set it up:

**2.1. Install NestJS Pino:**

```
npm install nestjs-pino pino-http
```

## Step 3: Implementation of validation and logger

To add validation and logger to our app, it's very easy.
All we have to do is call **useGlobalPipes** and **useLogger** here on our application.
to do this just open your `apps/reservations/src/main.ts`
and update the code with the following content:

```
import { NestFactory } from '@nestjs/core';
import { ReservationsModule } from './reservations/reservations.module';
import { ValidationPipe } from '@nestjs/common';
import { Logger } from 'nestjs-pino';

async function bootstrap() {
  const app = await NestFactory.create(ReservationsModule);
  app.useGlobalPipes(new ValidationPipe());
  app.useLogger(app.get(Logger));
  await app.listen(3000);
}
bootstrap();
```

**Format Logs with Pino Pretty:**
To make the logs more readable, install the pinot-pretty package:

```
npm install pino-pretty
```

## Step 4: Abstracting Logging for Reusability

To ensure that we can reuse our logging setup in other microservices and maintain a clean structure, we'll create a logger module in our **common library**.

Run the Nest CLI command to generate a logger module in your common project:

```
nest generate module logger
```

it will give you this promp choose **common**

```
? Which project would you like to generate to?
  reservations [ Default ]
> common
```
it will create a logger module inside your **libs/common/src/logger/logger.module.ts**

populate it with the following code:

```
import { Module } from '@nestjs/common';
import { LoggerModule as PinoLogerModule } from 'nestjs-pino';

@Module({
  imports: [
    PinoLogerModule.forRoot({
      pinoHttp: {
        transport: {
          target: 'pino-pretty',
          options: {
            singleLine: true,
          },
        },
      },
    }),
  ],
})
export class LoggerModule {}
```

**Code Explanation**
**Import Statements:**
import { Module } from '@nestjs/common';: This line imports the Module decorator from the '@nestjs/common' package, which is used for defining modules in NestJS.

**import { LoggerModule as PinoLogerModule }** from 'nestjs-pino';: This line imports the 'LoggerModule' from the 'nestjs-pino' package, renaming it as 'PinoLoggerModule' to be used in the current code.

**Module Definition:**
@Module({ ... }): This decorator is used to define a NestJS module. The configuration object inside the decorator contains the module's settings.

**Module Configuration:**
**imports**: Inside the module configuration, the imports property is specified as an array.

**PinoLoggerModule.forRoot({ ... }):** This is an import of the 'PinoLoggerModule' using the .forRoot() static method provided by the module.

**pinoHttp:** This is an object passed as a configuration parameter to the 'PinoLoggerModule.forRoot()' method. It configures the behavior of the 'pino' logger used in this module.
transport: This nested object defines the transport options for the logger.

**target:** The 'target' property specifies the target output for the logger. In this case, it's set to 'pino-pretty', indicating that the logger will output logs in a pretty-printed format.

**options:** This object provides additional options for the logger's transport.

**singleLine: true:** The 'singleLine' option is set to 'true', which means that each log message will be formatted as a single line, making it more readable and concise.

**Export Logger Module:**
Create an index.ts file in your logger module and export everything from the logger module.
to do this just run the following code on your terminal:

```
touch libs/common/src/logger/index.ts
```
then populate it with the following code:
`export * from './logger.module';`

make sure to also export the logger module in our libs/common/src/index.ts

update your index code with the following content: 

```
export * from './config';
export * from './database';
export * from './logger';
```

**Import Logger Module:**
In your reservations module, import the logger module from the common library and use it in your application.
to do this open your 
`apps/reservations/src/reservations/reservations.module.ts`
and update with the following content 

```
import { Module } from '@nestjs/common';
import { ReservationsService } from './reservations.service';
import { ReservationsController } from './reservations.controller';
import { DatabaseModule, LoggerModule } from '@app/common';
import { ReservationsRepository } from './reservations.repository';
import {
  ReservationDocument,
  ReservationSchema,
} from './entities/reservation.entity';

@Module({
  imports: [
    DatabaseModule,
    DatabaseModule.forFeature([
      { name: ReservationDocument.name, schema: ReservationSchema },
    ]),
    LoggerModule,
  ],
  controllers: [ReservationsController],
  providers: [ReservationsService, ReservationsRepository],
})
export class ReservationsModule {}

```
What we did was simply import the LoggerModule to use it in our reservation application.

**Test our Logger Setup**
run `npm run start:dev` 
if you saw this beautiful logger in your terminal 

```
[11:17:30.769] INFO (12092): ReservationsController {/reservations}: {"context":"RoutesResolver"}
[11:17:30.774] INFO (12092): Mapped {/reservations, POST} route {"context":"RouterExplorer"}
[11:17:30.775] INFO (12092): Mapped {/reservations, GET} route {"context":"RouterExplorer"}
[11:17:30.775] INFO (12092): Mapped {/reservations/:id, GET} route {"context":"RouterExplorer"}
[11:17:30.776] INFO (12092): Mapped {/reservations/:id, PATCH} route {"context":"RouterExplorer"}
[11:17:30.776] INFO (12092): Mapped {/reservations/:id, DELETE} route {"context":"RouterExplorer"}
[11:17:30.777] INFO (12092): Nest application successfully started {"context":"NestApplication"}
```
it means that our setup was successful 

## Step 5: Adding Validation to Reservations
 
Now that we have improved our logging setup, let's focus on adding validation to our reservations, specifically for the create reservation request.

**5.1. Use Class Validator Decorators:**
In your Create Reservation DTO, use Class Validator decorators to validate the incoming data.

open your `apps/reservations/src/reservations/dto/create-reservation.dto.ts` and update the content with the following code:

```
import { Type } from 'class-transformer';
import { IsDate, IsNotEmpty, IsString } from 'class-validator';

export class CreateReservationDto {
  @IsDate()
  @Type(() => Date)
  startDate: Date;

  @IsDate()
  @Type(() => Date)
  endDate: Date;

  @IsString()
  @IsNotEmpty()
  placeId: string;

  @IsString()
  @IsNotEmpty()
  invoiceId: string;
}

```

**Class Properties Explanation:**

startDate: Date;: This property represents the start date of the reservation. It is annotated with the **@IsDate()** decorator, which indicates that the value of this property should be a valid JavaScript Date object.

endDate: Date;: This property represents the end date of the reservation and, like startDate, is annotated with the **@IsDate()** decorator to ensure it is a valid JavaScript Date object.

placeId: string;: This property represents the place or location identifier for the reservation. It is annotated with the **@IsString()** decorator to ensure that it is a string, and with **@IsNotEmpty()** to ensure that it is not an empty string.

invoiceId: string;: This property represents the invoice identifier for the reservation. It is also annotated with **@IsString()** to ensure that it is a string, and with **@IsNotEmpty()** to ensure that it is not an empty string.

the **@Type** decorator from Class Transformer to specify that certain properties should be returned as date objects.

**5.2. Enable Whitelisting:**
To ensure that only properties defined in the DTO are allowed, set the whitelist option to true in your validation pipe configuration in the main.ts file.
in your `**apps/reservations/src/main.ts**` update your 
`app.useGlobalPipes(new ValidationPipe());` with the following code specifying the whitelist to true

```
  app.useGlobalPipes(
    new ValidationPipe({
      whitelist: true,
    }),
  );
```

With validation in place, your API will reject requests that don't meet the defined criteria, making it more robust and secure.

## Test our validation setup

**Scenario: Creating a Reservation**

**1. User Action:** A user wants to create a new reservation in your system. They prepare a JSON request to send to your API.

**2. Request Body:** The user sends a POST request to the /reservations endpoint with a JSON request body, which includes various reservation details. For testing validation, let's consider the following cases:

**a. Valid Request:**

```
{
    "startDate": "11/26/2023",
    "endDate":"11/30/2023" ,
    "placeId":"223443" ,
    "invoiceId":"0098748" 
}

```

**b. Invalid Request (Missing Required Field):**

```
{
  "startDate": "2023-11-01",
  "endDate": "2023-11-05"
}

```
**c. Invalid Request (Invalid Date Format):**

```
{  
"startDate": "2023-11-01222",
  "endDate": "2023-11-05",
  "placeId": "12345",
  "invoiceId": "67890"
  }

```
**d. Invalid Request (Empty String for Required Field):**
```
{
  "startDate": "2023-11-01",
  "endDate": "2023-11-05",
  "placeId": "",
  "invoiceId": "67890"
}

```
**3. Expected Response:**

**a.** In the case of a valid request, you should receive a 201 Created response with the created reservation details.

**b.** For an invalid request due to missing a required field, you should receive a 400 Bad Request response with a message indicating the missing field.

**c.** If the request contains an invalid date format, you should also receive a 400 Bad Request response with a message indicating the date format is incorrect.

**d.** If the request includes an empty string for a required field, you should again receive a 400 Bad Request response.

**4. Testing Steps:**

- Use a tool like Postman or a tool for making HTTP requests.
- Send each of the mentioned request scenarios to your API.
- Verify that the responses match the expected results.

By testing these scenarios, you can ensure that your validation rules are correctly applied to incoming requests, and your NestJS application responds accordingly to both valid and invalid input.


## Conclusion:

In this tutorial, we've learned how to add validation to a NestJS application, enhance system logging, and abstract the logging setup for reusability. We've also explored how to use Class Validator and Class Transformer to validate and transform data effectively. By following these steps, you can improve the reliability and security of your NestJS application. In the next tutorial, we will Dockerize our app and explore adding authentication to our backend.