Validate a nested array of objects GET query parameter using a DTO & class-validator in NestJS

Sometimes, the smallest things take up quite a bit of time. This time, it was a simple serialisation issue.

Validate a nested array of objects GET query parameter using a DTO & class-validator in NestJS
Photo by Fabrizio Frigeni / Unsplash

Sending an array of objects as a GET request's query parameters using axios from the frontend to a NestJS backend, I ended up with an error:

"constraints":{"nestedValidation":"each value in nested property byRelationships must be either object or array"}

Backend-side

Try to figure out what it was I was doing wrong, I first enabled transform in the app's validation pipe.

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

Then set @ValidateNested({ each: true }), and @Type(() => YourClass in my validation schema, like this.

import { DictionnaryOfValues } from './types';
import { IsIn, IsString } from 'class-validator';

import { Type } from 'class-transformer';
import { IsIn, IsOptional, IsString, ValidateNested } from 'class-validator';

export class NestedPropertyDto {
  @IsString()
  id: string;

  @IsIn([...Object.values(DictionnaryOfValues)])
  indexName: DictionnaryOfValues;
}


export class DtoClass {
  @ValidateNested({ each: true })
  @Type(() => NestedPropertyDto)
  @IsOptional()
  byRelationShips?: NestedPropertyDto[];
 }

To no avail. I was still getting the error. Removing the @ValidateNested and @Type decorators from the validation schema in the backend, then outputing in the console the query parameter I actually received, I noticed it was not an object, but a stringified object, because...

Frontend-side

... in my frontend, axios didn't spontaneously serialize my query parameters the way I needed them The solution here was quite simple, but not necessarily obvious, and once again I have to thank a generous StackOverflow contributor for it:

  1. Use paramSerializer in the axios request
  2. Serialize the query's parameters using the qs library

Like this:

axios.get('/myController/myAction', {
  params: {
    storeIds: [1,2,3]
  },
  paramsSerializer: params => {
    return qs.stringify(params)
  }
})

We still get the error. If we remove the @ValidateNested & @Type decorators in the backend, then output the actual query parameter received, we will notice it is not an object, but a stringified object. Because our frontend doesn't serialize it correctly. Let's fix that.

Epilogue

This took me more or less three to four hours to figure out. Perhaps because I have little frontend experience. Perhaps because I have little to no experience with either NestJS or class-validator. Nonetheless, I'd wager I'm not the only one around to have faced this, and I'd bet others will in the future. So, here you go; hope it helps 😉

Here, have a cup of coffee 😉 - Photo by Nathan Dumlao / Unsplash