はじめに
テストの実装に引き続き、リレーション(一対多)の実装を行いました。
実装したコードを確認したい方は以下よりご確認ください。
実装
中古車価格APIについて、販売価格レポートの登録およびレポートとユーザデータのリレーションを実装します。
価格レポートに必要なデータは以下になります。
- メーカ(make)
- 車種(model)
- 年式(year)
- 走行距離(mileage)
- 経度(longitude)
- 緯度(latitude)
- 値段(price)
また、レポートがどのログインユーザによって作成されたかどうかを判別するために、ユーザIDのカラムも必要です。
そして、このユーザIDによってレポートテーブル(Report)とユーザテーブル(User)の関連付け(リレーション)を行います。
NestとTypeORMでリレーションを実装する流れは以下のようになります。
テーブル同士の関係性(一対一、一対多)を把握する
User1つにつき複数のReportをもつ場合があります。
そのため、関係性としては一対多(OneToMany)となります。
Entityに適切なデコレータを付加する
UserEntityには@OneToMany()
、ReportEntityには@ManyToOne()
のデコレータを付加します。
@Entity()
export class User {
...
@OneToMany(() => Report, (report) => report.user)
reports: Report[];
}
@Entity()
export class Report {
...
@ManyToOne(() => User, (user) => user.reports)
user: User;
}
レコードの保存前にリレーションを行う
Reportを作成する際、各入力値のバリデーションを行うためにCreateReportDto
を作成します。
このとき、@Max()
@Min()
@IsLongitude()
などのデコレータを使用します。
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に保存します。
@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
に記述した形でレスポンスを返すことができます。
@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)
デコレータを追加します。
このように、デコレータを使用することで、データの表示有無を制御するだけではなく、表示データの加工も行うことができます。
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;
}
参考資料