Combining validation and transformation pipes in NestJS for streamlined data processing

Optimizing NestJS pipes for validation and transformation

I am working on enhancing the pipe functionality in my NestJS controller by chaining two pipes. The first pipe will validate the request body against a specific DTO type, while the second pipe will transform this DTO into a specific Type required as an argument for a service.

Current Setup:

@Post()
@UseGuards(JwtAuthGuard, ProductOwnershipGuard)
async create(@Body() createAuctionDto: CreateAuctionDto) {
  return this.auctionsService.create(createAuctionDto);
}

Desired Workflow

  1. JwtAuthGuard verifies if the user is logged in
  2. ProductOwnershipGuard ensures that the product_id in the request body belongs to a product owned by the user
  3. ValidateAuctionPipe validates the user input against the createAuctionDto type
  4. TransformAuctionPipe converts the createAuctionDto to Auction type by incorporating additional properties from the associated product (referenced by product_id)
  5. The Controller receives a variable of type Auction and passes it to the auctionsService

Attempted Solutions

Initially, I utilized a global ValidationPipe to automatically validate the request body against the CreateAuctionDto type without any extra annotations.

I experimented with multiple solutions such as:

1.

@Post()
@UseGuards(JwtAuthGuard, ProductOwnershipGuard)
async create(@Body(TransformAuctionPipe) auction: Auction) {
  return this.auctionsService.create(createAuctionDto);
}

Unfortunately, the global ValidationPipe checked the body for the Auction type instead of CreateAuctionDto

2.

@Post()
@UseGuards(JwtAuthGuard, ProductOwnershipGuard)
async create(@Body(new ValidationPipe()) createAuctionDto: CreateAuctionDto, @Body(TransformAuctionPipe) auction: Auction) {
  return this.auctionsService.create(auction);
}

While this properly validated createAuctionDto, the TransformAuctionPipe received an empty object of Auction type

3.

@Post()
@UseGuards(JwtAuthGuard, ProductOwnershipGuard)
async create(@Body(new ValidationPipe({ expectedType: CreateAuctionDto }), TransformAuctionPipe) auction: Auction) {
  return this.auctionsService.create(auction);
}

This approach did not validate the body against CreateAuctionDto at all

  1. I also attempted using the @UsePipes() decorator instead of passing pipes to @Body() decorator, but it did not yield the desired results

Below are the codes for my pipes:

ValidateDtoPipe

/* eslint-disable @typescript-eslint/ban-types */
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';
import { validate } from 'class-validator';
import { plainToInstance } from 'class-transformer';

@Injectable()
export class ValidateDtoPipe implements PipeTransform<any> {
  async transform(value: any, { metatype }: ArgumentMetadata) {
    if (!metatype || !this.toValidate(metatype)) {
      return value;
    }
    const object = plainToInstance(metatype, value);
    const errors = await validate(object);
    if (errors.length > 0) {
      throw new BadRequestException('Validation failed');
    }
    return value;
  }

  private toValidate(metatype: Function): boolean {
    const types: Function[] = [String, Boolean, Number, Array, Object];
    return !types.includes(metatype);
  }
}

TransformAuctionPipe

import { ArgumentMetadata, Injectable, NotFoundException, PipeTransform } from '@nestjs/common';
import { ProductsService } from 'src/api/products/products.service';
import { CreateAuctionDto } from '../dto/create-auction.dto';
import { Auction } from '../entities/auction.entity';
import { EAuctionStatus } from '../types/auction.types';

/**
 * This pipe transforms CreateAuctionDto to Auction,
 * by fetching associated Product and adding its properties to CreateAuctionDto
 * */
@Injectable()
export class TransformAuctionPipe implements PipeTransform<CreateAuctionDto, Promise<Auction>> {
  constructor(private readonly productsService: ProductsService) {}

  async transform(createAuctionDto: CreateAuctionDto, metadata: ArgumentMetadata): Promise<Auction> {
    const product = await this.productsService.findOneById(createAuctionDto.product_id);

    if (!product) {
      throw new NotFoundException('Product not found.');
    }

    const auctionStatus: EAuctionStatus = +createAuctionDto.start_at <= Date.now() ? EAuctionStatus.ACTIVE : EAuctionStatus.PENDING;
    const { name, description, price } = product;

    const auction = new Auction({ ...createAuctionDto, name, description, price, product, status: auctionStatus });

    return auction;
  }
}

Answer №1

Chaining multiple pipes is a powerful feature:

@Body(new ParseJsonPipe(), new CustomValidationPipe(createUserValidationSchema))

In this example, the data will first be parsed by the ParseJsonPipe before being validated by the CustomValidationPipe. The CustomValidationPipe will work on the transformed data provided by the ParseJsonPipe

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

The custom pagination feature in MUI DataGridPro does not display the rowsPerPageOptions dropdown as expected

I am currently utilizing React in my project. "react": "^18.2.0", "@mui/material": "^5.10.7", "@mui/x-data-grid-pro": "^5.16.0" An issue arises with the visibility of the rowsPerPageOptions dr ...

Having issues with your Typescript in Sublime Text?

The issue with the TypeScript plugin in Sublime Text (version 3126) suddenly arose without any identifiable cause. It seems that the plugin no longer recognizes types, resulting in disabled error highlights and autocompletions. This problem occurred on M ...

JavaScript: Translating Date into Moment

Is there a way to convert a Date object to Moment in JavaScript? let testDate = new Date(2020, 05, 03, 1, 2); I attempted the following code without success toMoment(testDate) What is the correct syntax to achieve this conversion? ...

Does the TS keyof typeof <Object> rule prohibit the assignment of object.keys(<Object>)?

I'm having trouble understanding the issue with this code snippet. Here is the piece of code in question: export type SportsTypes = keyof typeof SportsIcons export const sports: SportsTypes[] = Object.keys(SportsIcons); The problem arises when I at ...

Eliminating the parent property name from the validation message of a nested object

When using @ValidateNested() with the class-validator library, I encountered a formatting issue when validating a nested object: // Object Schema: export class CreateServerSettingsDTO { @IsNotEmpty({ message: 'Username is required' }) usernam ...

There are no properties shared between type 'dateStyle: string' and type 'DateTimeFormatOptions'

"typescript": "^4.0.3" Can anyone help me resolve the TypeScript error I am encountering in the code below?: components/OrderListItem.tsx const newedate = (_date) => { const options = {dateStyle: 'medium'}; //{ weekday: ...

Issue with Ant Design form validation

After reading through the documentation, I attempted to implement the code provided: Here is a basic example: import { Button, Form, Input } from "antd"; export default function App() { const [form] = Form.useForm(); return ( <Form f ...

Is it possible to pass additional arguments to setState other than prevState and props?

I'm currently facing an issue with my component that involves calling a function called addOption, which is defined on its parent component. This function takes a parameter 'option' from a form field and concatenates it with an array of opti ...

Wait until a svelte store value is set to true before fetching data (TypeScript)

I have implemented a pop-up prompt that requests the user's year group. Since I have databases for each year group, I need to trigger a function once the value of userInfo changes to true. My JavaScript skills are limited, and my experience has been ...

Error message: Missing "@nestjs/platform-express" package when performing end-to-end testing on NestJS using Fastify

Just set up a new NestJS application using Fastify. While attempting to run npm run test:e2e, I encountered the following error message: [Nest] 14894 - 11/19/2021, 10:29:10 PM [ExceptionHandler] The "@nestjs/platform-express" package is missi ...

Tips for waiting for an Http response in TypeScript with Angular 5

Is it possible to create a function that can retrieve a token from a server, considering that the http.post() method generates a response after the function has already returned the token? How can I ensure that my function waits for the http.post() call t ...

What are the best ways to work with LatLng objects?

When I run a request to retrieve data from a database, the response displayed in the console using JSON.Stringify() is as follows: sites : [{"siteName":"Site de Marseille", "siteAdress1":"rue du string", "siteAddress2":"string", "siteCodPost":"13010","sit ...

Converting <reference path/> directive to ESM import: A step-by-step guide

As I embark on developing a TypeScript application, I've reached the realization that I am not a fan of using the <reference path /> triple-slash directive. Instead, I prefer utilizing import 'something'. However, every time I attempt ...

Incorporating custom validation methods with jQuery FormValidation Engine

Is there a way to verify the validity of an email by checking for the presence of an '@' sign? The input text will always be either a name or an email address. ...

Can TypeScript's Zod library be utilized to parse a class instance?

Using the Zod validation library with TypeScript has been a great experience for me so far. I am currently exploring the best pattern to extend Zod Schema with class-like functionality, starting with a Vector3 schema like this: const Vector3Schema = z.obj ...

Despite being listed in the entry components, HelloComponent is not actually included in the NgModule

Check out my StackBlitz demo where I am experimenting with dynamically instantiating the HelloComponent using the ReflexiveInjector. The HelloComponent is added to the app modules entryComponents array. Despite this setup, I am still encountering the foll ...

Child component experiencing issues with Materialize Pagination and Sorting functionalities not functioning

New to materialize pagination and currently working on the hierarchy below: app modules > list > list.component app.component Implemented a sample code example in app.component which worked perfectly. However, encountered issues when trying to imp ...

React form validation feature that becomes visible upon input of accurate data

I have set up a simple login form and incorporated form validation with material UI. However, the validation messages are appearing even when there is text in the input fields. Here is the code for LoginComponent: LoginComponent import React from "reac ...

Issue during deployment: The type 'MiniCssExtractPlugin' cannot be assigned to the parameter type 'Plugin'

I'm working on deploying a Typescript / React project and have completed the necessary steps so far: Created a deployment branch Installed gh-pages for running the deployed application Added a deploy command as a script in the package.j ...

Is there a way to dynamically change an icon based on certain conditions using typescript?

I am struggling with displaying icons in my TypeScript code using Material Icons. I need to dynamically change the icon based on a condition, for example if the power is false I want to display 'power_off', but if it's true then I want to di ...