Prismaで関連レコードの作成はconnectを使う

弊社では開発しているサービスではTypeScripでバックエンドを実装しています。
合わせてORMでPrismaを使用しています。
今回Prismaを使用してリレーションが1対多対1のレコードを作成する際に少し型エラーがでてしまったのでどのように解消したか紹介します。
1対多対1のPrismaのschemaファイル
UserがPostを複数持ち、PostはPhotoを1つだけ持つという関係性です。
model User { id Int @default(autoincrement()) @id posts Post[]}
model Post { id Int @default(autoincrement()) @id photoId Int? @map("photo_id") @unique photo Photo? @relation(fields: [photoId], references: [id])}
model Photo { id Int @default(autoincrement()) @id userId Int @map("user_id") user User @relation(fields: [userId], references: [id]) posts Post[]}
erDiagram users ||--o{ posts: "" users ||--o{ photos: "" posts ||--o| photos: ""
users { bigint id PK }
posts { bigint id PK references user FK references photo FK }
photos { bigint id PK references user FK }
既存のUserがPostと関連するPhotoを追加する
今回の要件はすでに存在しているUserがPostを作成しつつ、Postの作成と同時にPhotoを作成して関連づけるようなシーンです。
結論としては下記のコードで動作させることができます。
const post = this.prisma.posts.create({ data: { user: { connect: { id: userId, }, }, photo: { create: { url: 'photo url', user: { connect: { id: userId }, }, }, }, },}
あとで記載しますが、connectを使用するのがポイントです。
connectを使うことですでに作成済みのデータを関連付けることができます。
Prismaの公式サイトでも紹介されている方法です。
https://www.prisma.io/docs/concepts/components/prisma-client/relation-queries#connect-a-single-record
公式でのサンプルと形は違えど、connectを使用してデータを関連付けることには変わりません。
connect を使わずエラーになってしまうパターン
既存データを関連付けるにはconnectを使用するのが基本ですが、状況によっては直接idを指定しても動作します。
たとえば下記のようにconnectではなくpostのdataに直接userIdを入れても関連付けはされます。
const post = this.prisma.posts.create({ data: { userId, },});
しかし、この方法ではうまくいくパターンといかないパターンがあります。
今回の要件のようにPostの作成と同時にPhotoを作成する場合はidを直接しているする方法ではうまくいきません。
const post = this.prisma.posts.create({ data: { userId, }, photo: { create: { url: "photo url", userId, }, },});
一見うまくいくように見えますが、TypeScriptの型エラーが発生します。
Type '{ userId: number; photo: { create: { url: string; userId: number; }; }; startDate: Date; endDate: Date; description: string; title: string; status: $Enums.PlanStatus; }' is not assignable to type '(Without<PlanCreateInput, PlanUncheckedCreateInput> & PlanUncheckedCreateInput) | (Without<...> & PlanCreateInput)'. Type '{ userId: number; photo: { create: { url: string; userId: number; }; }; startDate: Date; endDate: Date; description: string; title: string; status: $Enums.PlanStatus; }' is not assignable to type 'Without<PlanUncheckedCreateInput, PlanCreateInput> & PlanCreateInput'. Type '{ userId: number; photo: { create: { url: string; userId: number; }; }; startDate: Date; endDate: Date; description: string; title: string; status: $Enums.PlanStatus; }' is not assignable to type 'Without<PlanUncheckedCreateInput, PlanCreateInput>'. Types of property 'userId' are incompatible. Type 'number' is not assignable to type 'never'.
userIdはたしかにnumber型になっていますがneverと解釈されてしまいます。
Prismaで関連付けを行う時はconnectを使う
idを直接指定せずにconnectを使用していれば出会わなかったエラーですが、結果的にPrismaの理解が深まりました。
動作してしまうがゆえにidを直接指定してしまっている方の参考になれば幸いです。
TypeScriptでバックエンドを実装するにはORMとしてPrismaはとても便利で快適に使うことができるので、これからも理解を深めていきます。