Mikro-orm: Question: How to transform and validate MikroORM collections using class-transformer and class-validator?

Created on 8 Aug 2020  路  5Comments  路  Source: mikro-orm/mikro-orm

Hi! I would like to ask your advice about MikroORM integration with class-transformer and class-validator. I have a project with the following dependencies:

  1. Nest.js
  2. TypeORM
  3. @nestjsx/crud
  4. class-transformer
  5. class-validator

And in this project I have a code similar to this:

import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from "typeorm";
import { IsArray, MaxLength, ValidateNested, IsOptional } from "class-validator";
import { Type } from "class-transformer";
import { CrudValidationGroups } from "@nestjsx/crud";
import { GroupFilter } from "../group-filter/group-filter.entity";

const { UPDATE, CREATE } = CrudValidationGroups;

@Entity()
export class Group {
  @PrimaryGeneratedColumn("uuid")
  id: string;

  @Column()
  @MaxLength(200, { always: true })
  @IsOptional({ groups: [UPDATE] })
  name: string;

  @OneToMany(() => GroupFilter, groupFilter => groupFilter.group, { cascade: ["insert", "update"] })
  @Type(() => GroupFilter)
  @IsArray({ always: true })
  @ValidateNested({ always: true })
  @IsOptional({ groups: [UPDATE] })
  filters: GroupFilter[];
}

I have the following flow:

  1. class-transformer creates instances of entities
  2. class-validator validates the instances
  3. @nestjsx/crud saves the instances to a database (using TypeORM)

I want to replace TypeORM with MikroORM in this project. But MikroORM has collections instead of arrays, so I can't just use class-transformer and class-validator with collections. I think I have three options:

  1. Make patches to class-transformer and class-validator using patch-package
  2. I can try to make a custom transformation using @Transform() and a custom validation using @ValidatorConstraint(). But I can't have transformation/validation options inside @Transform() and @ValidatorConstraint() without hacks.
  3. Make some kind of array wrapper for the collection (class-transformer and class-validator can work with Array subclasses):

    export class ArrayWrapper<T> extends Array implements Collection<T> {
        private readonly collection: Collection<T>;
    
        async loadItems(): Promise<T[]> {
            return this.collection.getItems();
        }
    
        getItems(): T[] {
            return this.collection.getItems();
        }
    
        // ...
    }
    
    @Entity()
    export class Book {
        @ManyToMany()
        tags = new ArrayWrapper<BookTag>(this);
    }
    

Which option is better? Are there any other options?

question

All 5 comments

I would say none of those 3 you mentioned.

class-transformer creates instances of entities

Don't do this, use em.create() instead. class-transformer will not handle collections properly, and I am quite sure there will be more issues than just this (another one I can imagine is that it will blindly construct entity instances, ignoring identity map, making things less performant). MikroORM handles serialization too (via toJSON() method).

In general, you don't need class-transformer, not for creating entities. You can use class-validator to validate DTOs, but creating entity instances needs to happen via one of the ORM methods (em.create(), em.assign() or em.merge()).

Thank you for the answer! But I have more questions.

  1. I can do custom transformations using class-transformer. Is there a way to do custom transformations using em.create()? I am thinking about a setter inside an entity class.
  2. The solution with em.create() cannot be reused with regular DTOs and the ValidationPipe. But I can implement my own ValidationPipe and use em.create() for entities and class-transformer for regular DTOs. Is there a way to determine if a JS class is an entity? I mean something like this: if (isEntity(maybeEntityClass)) { ... }
  3. But still, how to validate MikroORM collections using class-validator? class-validator only supports Array, Set and Map.
  4. About performance. I think class-transformer will do the same as a developer would do. Something like this:

    const group = new Group();
    group.name = name;
    for(const groupFilter of groupFilters) {
    const groupFilterInstance = new GroupFilter();
    groupFilterInstance.prop = groupFilter.prop;
    group.filters.add(groupFilterInstance);
    }
    
  1. not a class-transformer user myself, so can't help with that.
  2. use Utils.isEntity() - it checks a bool flag that is added to the prototype during discovery. it expects entity instance, if you have only class, pass its prototype there
  3. you will have arrays in the DTO, so you will validate arrays as usual.
  4. this is exactly what em.create() does too

about the 1. - you could also use custom types and handle the validation there: https://mikro-orm.io/docs/custom-types

  1. you will have arrays in the DTO, so you will validate arrays as usual.

I want to use entities as DTOs. In my case, I will have a MikroORM collection after em.create(), not an array.

const entities = em.create(notValidatedUserData);
const errors = await classValidator.validate(entities);
// ...

BTW, is it safe to pass not validated user data to em.create()?

I'm also curious about this. I'd love to just run validate(entity) and know that everything is hunky-dory.

Was this page helpful?
0 / 5 - 0 ratings