nestjs で TypeORM とかのfetch結果を nextjs から取得するって場合、エンティティをそのまま返してもいいのだけど n:n な関連は 1:n に置き換えたほうがフロントエンドで扱いやすいので適当なDTOに詰め替えてから返す、って操作はありがち。
しかしそんな操作をいちいち手書きしているととても面倒なので class-transformer とか使う。
エンティティ定義
Book, BookGenre, Genre による n:n 構造を作る。
@Entity("books")
export class Book {
@PrimaryGeneratedColumn()
id: number;
@Column()
title: string;
@Column()
author: string;
@Column('text')
description: string;
@Column()
publishedYear: number;
@Column()
genre: string;
@OneToMany(() => BookGenre , bookGenre => bookGenre.book)
bookGenres: BookGenre[];
}
@Entity()
export class BookGenre {
@PrimaryGeneratedColumn()
id: number;
@ManyToOne(() => Book, book => book.bookGenres)
book: Book;
@ManyToOne(() => Genre, genre => genre.bookGenres)
genre: Genre;
}
@Entity()
export class Genre {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@OneToMany(() => BookGenre, bookGenre => bookGenre.genre)
bookGenres: BookGenre[];
}
DTO
構造を単純にするために BookDto, GenreDto の2つにしぼり、 1:n の構造を作る。
export class BookDto {
@Expose()
id: number;
@Expose()
title: string;
@Expose()
author: string;
@Expose()
publishedYear: number;
@Expose()
description: string;
@Expose({ name: 'bookGenres' })
@Type(() => GenreDto)
@Transform(({ obj }) =>
obj.bookGenres.map((bookGenre: BookGenre) => plainToInstance(GenreDto, bookGenre.genre))
)
genres: GenreDto[];
}
Transform で bookGenres:BookGenre[] を genre:GenreDto[] に展開する。
変換
const books = await this.repos.find({
relations: ['bookGenres', 'bookGenres.genre'],
order: { id: 'ASC' },
});
const dto = plainToInstance(BookDto, books);
変換はこれだけ。
その他
AutoMapper を使おうとしたけど pojos ストラテジーではベースのプロパティも forMember() ですべて記述する必要があるみたいで、さすがにそれだったら books.map(...) したほうがましだと思って不採用とした。