LoginSignup
2
2

More than 1 year has passed since last update.

【NestJS】一対多のリレーションを実装する

Last updated at Posted at 2021-10-19

はじめに

テストの実装に引き続き、リレーション(一対多)の実装を行いました。

実装したコードを確認したい方は以下よりご確認ください。

実装

中古車価格APIについて、販売価格レポートの登録およびレポートとユーザデータのリレーションを実装します。

価格レポートに必要なデータは以下になります。

  • メーカ(make)
  • 車種(model)
  • 年式(year)
  • 走行距離(mileage)
  • 経度(longitude)
  • 緯度(latitude)
  • 値段(price)

また、レポートがどのログインユーザによって作成されたかどうかを判別するために、ユーザIDのカラムも必要です。
そして、このユーザIDによってレポートテーブル(Report)とユーザテーブル(User)の関連付け(リレーション)を行います。

NestとTypeORMでリレーションを実装する流れは以下のようになります。

テーブル同士の関係性(一対一、一対多)を把握する

User1つにつき複数のReportをもつ場合があります。
そのため、関係性としては一対多(OneToMany)となります。

Entityに適切なデコレータを付加する

UserEntityには@OneToMany()、ReportEntityには@ManyToOne()のデコレータを付加します。

user.entity.ts
@Entity()
export class User {
  ...
  @OneToMany(() => Report, (report) => report.user)
  reports: Report[];
}
report.entity.ts
@Entity()
export class Report {
  ...
  @ManyToOne(() => User, (user) => user.reports)
  user: User;
}

レコードの保存前にリレーションを行う

Reportを作成する際、各入力値のバリデーションを行うためにCreateReportDtoを作成します。
このとき、@Max() @Min() @IsLongitude()などのデコレータを使用します。

create-report.dto.ts
export class CreateReportDto {
  @IsNumber()
  @Min(0)
  @Max(1000000)
  price: number;

  @IsString()
  make: string;

  @IsString()
  model: string;

  @IsNumber()
  @Min(1930)
  @Max(2050)
  year: number;

  @IsLongitude()
  lng: number;

  @IsLatitude()
  lat: number;

  @IsNumber()
  @Min(0)
  @Max(1000000)
  mileage: number;
}

ReportsServicecreate()メソッドにReport作成の処理を書きます。
CreateReportDtoのバリデーションを通ったreportDtoからreportインスタンスを作成し、report.useruserインスタンスを付加します。
最終的なreportthis.repo.save(report)でDBに保存します。

reports.service.ts
@Injectable()
export class ReportsService {
  constructor(@InjectRepository(Report) private repo: Repository<Report>) {}

  ...

  create(reportDto: CreateReportDto, user: User) {
    const report = this.repo.create(reportDto);
    report.user = user; // user entityのインスタンスを登録
    return this.repo.save(report);
  }
}

SerializeInterceptorでレスポンスを加工する

前の記事で作成したSerializeInterceptorで、特定のカラムをレスポンスに含むかどうかを制御することができます。

例えば、@Serialize(ReportDto)のデコレータをControllerのルートハンドラーに付加してあげることで、ReportDtoに記述した形でレスポンスを返すことができます。

reports.controller.ts
@Controller('reports')
export class ReportsController {
  constructor(private reportsService: ReportsService) {}

  ...

  @Post()
  @UseGuards(AuthGuard)
  @Serialize(ReportDto)
  createReport(@Body() body: CreateReportDto, @CurrentUser() user: User) {
    return this.reportsService.create(body, user);
  }
}

ReportDtoの中身は以下のようになります。
すべてのカラムがレスポンスに入るように、それぞれに@Expose()デコレータを付加しています。
ただ、userについては、Entityのインスタンスをそのまま返すとパスワードが含まれてしまうので、userIDのみを返すように@Transform(({ obj }) => obj.user.id)デコレータを追加します。
このように、デコレータを使用することで、データの表示有無を制御するだけではなく、表示データの加工も行うことができます。

report.dto.ts
export class ReportDto {
  @Expose()
  id: number;

  @Expose()
  price: number;

  @Expose()
  year: number;

  @Expose()
  lng: number;

  @Expose()
  lat: number;

  @Expose()
  make: string;

  @Expose()
  model: string;

  @Expose()
  mileage: number;

  @Expose()
  approved: boolean;

  @Transform(({ obj }) => obj.user.id)
  @Expose()
  userId: number;
}

参考資料

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2