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

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はとても便利で快適に使うことができるので、これからも理解を深めていきます。

Related Posts

Collaborative Innovation
for Your Growth

合同会社コードコネクトはフロントエンドを中心としたWeb開発を得意としています。
このようなシステム開発にご興味のある方、またはお困りの方はお気軽にお問い合わせください。

  • JamstackをベースとしたWebサイトの構築
  • Reactなどを用いたアプリケーションの開発
  • 運用に耐えられるメンテナンス性を考慮したプロダクト開発
  • モダンで運用可能な開発環境の構築