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

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

https://qiita.com/suzuki0430/items/d15c8642b46b57cdf91b

https://qiita.com/suzuki0430/items/697601370b078cc65fd6

https://qiita.com/suzuki0430/items/ac5ccc587e75a19bb567

https://qiita.com/suzuki0430/items/e4f8cabc61fc1fd41dcc

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

https://github.com/suzuki0430/car-api-nest

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

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

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

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

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

##テーブル同士の関係性(一対一、一対多)を把握する
User1つにつき複数のReportをもつ場合があります。
そのため、関係性としては一対多(OneToMany)となります。

##Entityに適切なデコレータを付加する
UserEntityには`@OneToMany()`、ReportEntityには`@ManyToOne()`のデコレータを付加します。

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

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

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

```ts: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;
}
```
`ReportsService`の`create()`メソッドにReport作成の処理を書きます。
`CreateReportDto`のバリデーションを通った`reportDto`から`report`インスタンスを作成し、`report.user`に`user`インスタンスを付加します。
最終的な`report`を`this.repo.save(report)`でDBに保存します。

```ts: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`で、特定のカラムをレスポンスに含むかどうかを制御することができます。

https://qiita.com/suzuki0430/items/d15c8642b46b57cdf91b#interceptor

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

```ts: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)`デコレータを追加します。
このように、デコレータを使用することで、データの表示有無を制御するだけではなく、表示データの加工も行うことができます。

```ts: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;
}
```

#参考資料

https://www.udemy.com/share/104SA43@5iHng3mXCUwhKvkqKp79zmFdmB999CLF2Bm6VZyebKenD1rjzDnn_4JeokhgumWA/

https://typeorm.io/#/many-to-one-one-to-many-relations