DEV Community

Cover image for Validating a polymorphic body in nest JS
Julien Prugne for Webeleon

Posted on

11 1

Validating a polymorphic body in nest JS

Sometimes, an issue rise, this time I had to validate a body that could be of two distinct forms.
I could have chosen to build a big dto mixing both classes validation.
But in the end, it was kind of ugly, lacking the inherent elegance of Nest.

Today, I'll share with you my solution and the reasons for its necessity.

party

Here is our target controller method signature:

import { Controller, Post } from '@nestjs/common';
import { CollegeStudentDto, OnlineStudentDto } from './student.dto';

@Controller('student')
export class StudentController {
  @Post()
  signup(signupDto: CollegeStudentDto | OnlineStudentDto) {
    return 'call the service and apply some logic'
  }
}

Enter fullscreen mode Exit fullscreen mode

Looks nice, eh?
Unfortunately, it won't work. The reflected metadata used in the ValidationPipe only knows how to cast to one class.
It can't discriminate the data and guess which of the classes to use for validation.

Ok, first thing first, let's define the DTOs:

import { IsNotEmpty, IsString } from 'class-validator';

export enum StudentType {
  ONLINE = 'online',
  COLLEGE = 'college',
}

export class StudentDto {
  @IsString()
  @IsNotEmpty()
  firstName: string;

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

export class CollegeStudentDto extends StudentDto {
  @IsString()
  @IsNotEmpty()
  college: string;
}

export class OnlineStudentDto extends StudentDto {
  @IsString()
  @IsNotEmpty()
  platform: string;
}
Enter fullscreen mode Exit fullscreen mode

wonderful

So, how can we compensate for these limitations?
Easy! use setup our own transform pipe in the @Body() annotation

import {
  BadRequestException,
  Body,
  Controller,
  Post,
  ValidationPipe,
} from '@nestjs/common';
import { CollegeStudentDto, OnlineStudentDto } from './student.dto';
import { plainToInstance } from 'class-transformer';
import { validate } from 'class-validator';

@Controller('student')
export class StudentController {
  @Post()
  signup(
    @Body({
      transform: async (value) => {
        let transformed: CollegeStudentDto | OnlineStudentDto;
        if (value.college) {
          // use plainToClass with older class-transformer versions
          transformed = plainToInstance(CollegeStudentDto, value);
        } else if (value.platform) {
          transformed = plainToInstance(OnlineStudentDto, value);
        } else {
          throw new BadRequestException('Invalid student signup');
        }

        const validation = await validate(transformed);
        if (validation.length > 0) {
          const validationPipe = new ValidationPipe();
          const exceptionFactory = validationPipe.createExceptionFactory();
          throw exceptionFactory(validation);
        }

        return transformed;
      },
    })
    signupDto: CollegeStudentDto | OnlineStudentDto,
  ) {
    if (signupDto instanceof CollegeStudentDto) {
      return 'college student';
    } else if (signupDto instanceof OnlineStudentDto) {
      return 'online student';
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

And that's it!
Now you know!

Questions?

questions?

I'll be glad to answers questions in the comments.

If you liked my discord consider joining my coding lair!
☎️Webeleon coding lair on discord

You can also email me and offer me a contract 💰
✉️Email me!

And since I'm a nice guy, here, take this sample repo containing a working codebase!
🎁Get the code of the tuto from github

SurveyJS custom survey software

Simplify data collection in your JS app with a fully integrated form management platform. Includes support for custom question types, skip logic, integrated CCS editor, PDF export, real-time analytics & more. Integrates with any backend system, giving you full control over your data and no user limits.

Learn more

Top comments (1)

Collapse
 
dexfs profile image
André Santos

Awesome!! I was looking for something this, I found a solution on stackoverflow before found yours, but I had the same idea and improved my solution after read your article.

I create a custom ValidationPipe and use the @UsePipes decorator. It was the difference.

nextjs tutorial video

Youtube Tutorial Series 📺

So you built a Next.js app, but you need a clear view of the entire operation flow to be able to identify performance bottlenecks before you launch. But how do you get started? Get the essentials on tracing for Next.js from @nikolovlazar in this video series 👀

Watch the Youtube series