メインコンテンツへスキップ

Sequelizeからの移行

このガイドでは、SequelizeからPrisma ORMへの移行方法について説明します。移行手順を示すために、Sequelize Expressの例を拡張したものをサンプルプロジェクトとして使用します。このガイドで使用した例は、GitHubにあります。

この移行ガイドでは、例としてPostgreSQLデータベースを使用しますが、Prismaでサポートされている他のリレーショナルデータベースにも同様に適用できます。

Prisma ORMとSequelizeの比較については、Prisma ORM vs Sequelizeのページをご覧ください。

移行プロセスの概要

SequelizeからPrisma ORMへの移行手順は、どのようなアプリケーションやAPIレイヤーを構築しているかに関わらず、常に同じであることに注意してください。

  1. Prisma CLIをインストールする
  2. データベースをイントロスペクトする
  3. ベースライン移行を作成する
  4. Prisma Clientをインストールする
  5. SequelizeクエリをPrisma Clientで段階的に置き換える

これらの手順は、REST API(例えば、Express、koa、NestJSなど)、GraphQL API(例えば、Apollo Server、TypeGraphQL、Nexusなど)、またはデータベースアクセスにSequelizeを使用するその他の種類のアプリケーションを構築している場合でも適用されます。

Prisma ORMは、段階的な導入に非常に適しています。つまり、プロジェクト全体をSequelizeからPrisma ORMに一度に移行する必要はなく、データベースクエリをSequelizeからPrisma ORMに段階的に移動することができます。

サンプルプロジェクトの概要

このガイドでは、Expressで構築されたREST APIをサンプルプロジェクトとして使用し、Prisma ORMに移行します。これには4つのモデル/エンティティがあります。

module.exports = (sequelize, DataTypes) => {
const User = sequelize.define('User', {
name: {
type: DataTypes.STRING,
},
email: {
type: DataTypes.STRING,
unique: true,
allowNull: false,
},
})

User.associate = (models) => {
User.hasMany(models.Post, {
foreignKey: 'authorId',
as: 'posts',
})
User.hasOne(models.Profile, {
onDelete: 'CASCADE',
foreignKey: 'userId',
})
}
return User
}

モデルには次の関係があります。

  • 1対1: UserProfile
  • 1対多: UserPost
  • 多対多: PostCategory

対応するテーブルは、生成されたSequelizeのマイグレーションを使用して作成されています。

このガイドでは、ルートハンドラーはsrc/controllersディレクトリにあります。モデルはsrc/modelsディレクトリにあります。そこから、中央のsrc/routes.jsファイルにプルされ、src/index.jsで必要なルートを設定するために使用されます。

└── blog-sequelize
├── package.json
└──src
   ├── controllers
   │   ├── post.js
   │   └── user.js
   ├── models
   │   ├── Category.js
   │   ├── Post.js
   │   ├── Profile.js
   │   └── User.js
   ├── index.js
   └── routes.js

ステップ1. Prisma CLIをインストールする

Prisma ORMを採用するための最初のステップは、プロジェクトにPrisma CLIをインストールすることです。

npm install prisma --save-dev

ステップ2. データベースをイントロスペクトする

2.1. Prisma ORMを設定する

データベースをイントロスペクトする前に、Prismaスキーマを設定し、Prisma ORMをデータベースに接続する必要があります。ターミナルで次のコマンドを実行して、基本的なPrismaスキーマファイルを作成します。

npx prisma init

このコマンドは、次のファイルを含むprismaという新しいディレクトリを作成しました。

  • schema.prisma: データベース接続とモデルを指定するPrismaスキーマ
  • .env: 環境変数としてデータベース接続URLを設定するためのdotenv

現在のPrismaスキーマは次のようになっています。

prisma/schema.prisma
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}

generator client {
provider = "prisma-client-js"
}
ヒント

VS Codeを使用している場合は、必ずPrisma VS Code拡張機能をインストールして、構文の強調表示、フォーマット、オートコンプリート、その他の便利な機能を使用してください。

2.2. データベースを接続する

PostgreSQLを使用していない場合は、datasourceブロックのproviderフィールドを現在使用しているデータベースに合わせて調整する必要があります。

datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}

完了したら、データベース接続URL.envファイルで設定できます。Sequelizeからのデータベース接続がPrisma ORMで使用される接続URL形式にどのようにマッピングされるかは次のとおりです。


src/models/index.jsに次のデータベース接続詳細があると仮定します。


src/models/index.js
const sequelize = new Sequelize('blog-sequelize', 'alice', 'myPassword42', {
host: 'localhost',
dialect: 'postgres',
})

Prisma ORMでは、対応する接続URLは次のようになります。


.env
DATABASE_URL="postgresql://alice:myPassword42@localhost:5432/blog-sequelize"

オプションで、接続URLにschema引数を追加してPostgreSQLのスキーマを設定できることに注意してください。


.env
DATABASE_URL="postgresql://alice:myPassword42@localhost:5432/blog-sequelize?schema=myschema"

指定しない場合、publicという名前のデフォルトスキーマが使用されます。


2.3. Prisma ORMを使用してデータベースをイントロスペクトする

接続URLを設定したら、データベースをイントロスペクトして、Prismaモデルを生成できます。

npx prisma db pull

これにより、次のPrismaモデルが作成されます。

prisma/schema.prisma
model Categories {
id Int @id @default(autoincrement())
name String
createdAt DateTime
updatedAt DateTime
PostCategories PostCategories[]
}

model PostCategories {
createdAt DateTime
updatedAt DateTime
CategoryId Int
PostId Int
Categories Categories @relation(fields: [CategoryId], references: [id])
Posts Posts @relation(fields: [PostId], references: [id])

@@id([CategoryId, PostId])
}

model Posts {
id Int @id @default(autoincrement())
title String
content String?
published Boolean? @default(false)
createdAt DateTime
updatedAt DateTime
authorId Int?
Users Users? @relation(fields: [authorId], references: [id])
PostCategories PostCategories[]
}

model Profiles {
id Int @id @default(autoincrement())
bio String
createdAt DateTime
updatedAt DateTime
userId Int? @unique
Users Users? @relation(fields: [userId], references: [id])
}

model SequelizeMeta {
name String @id
}

model Users {
id Int @id @default(autoincrement())
name String?
email String @unique
createdAt DateTime
updatedAt DateTime
Posts Posts[]
Profiles Profiles?
}

2.4. ベースライン移行を作成する

Prisma Migrateを使用してデータベーススキーマを進化させ続けるには、データベースをベースライン化する必要があります。

まず、migrationsディレクトリを作成し、その中に移行に使用する適切な名前のディレクトリを追加します。この例では、移行名として0_initを使用します。

mkdir -p prisma/migrations/0_init

次に、prisma migrate diffを使用して移行ファイルを生成します。次の引数を使用します。

  • --from-empty: 移行元のデータモデルが空であると仮定します。
  • --to-schema-datamodel: datasourceブロックのURLを使用した現在のデータベース状態。
  • --script: SQLスクリプトを出力します。
npx prisma migrate diff --from-empty --to-schema-datamodel prisma/schema.prisma --script > prisma/migrations/0_init/migration.sql

生成された移行を確認して、すべてが正しいことを確認します。

次に、--applied引数を使用してprisma migrate resolveを使用し、移行が適用済みとしてマークします。

npx prisma migrate resolve --applied 0_init

このコマンドは、_prisma_migrationsテーブルに追加することで、0_initを適用済みとしてマークします。

これで、現在のデータベーススキーマのベースラインができました。データベーススキーマをさらに変更するには、Prismaスキーマを更新し、prisma migrate devを使用して変更をデータベースに適用できます。

2.5. createdAt および updatedAt フィールドの調整

生成された Prisma モデルは、データベーステーブルを表しており、データベースにクエリを送信するためのプログラムによる Prisma Client API の基盤となります。モデル内の createdAt および updatedAt フィールドを調整します。Sequelize は、データベースにテーブルを作成する際に createdAtDEFAULT 制約を追加しません。したがって、createdAt および updatedAt 列にそれぞれ @default(now()) および @updatedAt 属性を追加します。Prisma ORM がどのようにこれを行うかについて詳しくは、@default(now()) および @updatedAt をご覧ください。更新されたスキーマは次のようになります。

prisma/schema.prisma
model Categories {
id Int @id @default(autoincrement())
name String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
PostCategories PostCategories[]
}

model PostCategories {
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
CategoryId Int
PostId Int
Categories Categories @relation(fields: [CategoryId], references: [id])
Posts Posts @relation(fields: [PostId], references: [id])

@@id([CategoryId, PostId])
}

model Posts {
id Int @id @default(autoincrement())
title String
content String?
published Boolean? @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
authorId Int?
Users Users? @relation(fields: [authorId], references: [id])
PostCategories PostCategories[]
}

model Profiles {
id Int @id @default(autoincrement())
bio String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
userId Int? @unique
Users Users? @relation(fields: [userId], references: [id])
}

model SequelizeMeta {
name String @id
}

model Users {
id Int @id @default(autoincrement())
name String?
email String @unique
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
Posts Posts[]
Profiles Profiles?
}

2.6. Prisma スキーマの調整 (オプション)

イントロスペクションによって生成されたモデルは、現在、データベーステーブルに正確にマッピングされています。このセクションでは、Prisma ORM の命名規則に従うように、Prisma モデルの名前を調整する方法を学びます。

これらの調整はすべて完全にオプションであり、現時点で何も調整したくない場合は、すでに次のステップに進んでも構いません。後でいつでも戻って調整を行うことができます。

現在の Prisma モデルのスネークケース表記とは対照的に、Prisma ORM の命名規則は次のとおりです。

  • モデル名には PascalCase
  • フィールド名には camelCase

Prisma モデルとフィールド名を、@@map および @map を使用して、基になるデータベースの既存のテーブル名および列名にマッピングすることにより、名前を調整できます。

また、後でデータベースにクエリを送信するために使用する Prisma Client API を最適化するために、リレーションフィールドの名前を変更することもできます。たとえば、Posts モデル名を単数形の Post に変更していますが、user モデルの posts フィールドはリストであるため、複数形であることを示すために posts という名前を維持することが理にかなっています。

Sequelize は、ライブラリによって内部的に使用される SequelizeMeta モデルを生成しますが、これは不要です。したがって、スキーマから手動で削除します。

これらの点を考慮した、調整された Prisma スキーマのバージョンを以下に示します。

prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}

datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}

model Category {
id Int @id @default(autoincrement())
name String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
postCategories PostToCategories[]

@@map("Categories")
}

model PostToCategories {
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
categoryId Int
postId Int
category Category @relation(fields: [categoryId], references: [id])
post Post @relation(fields: [postId], references: [id])

@@id([categoryId, postId])
@@map("PostCategories")
}

model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean? @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
authorId Int?
author User? @relation(fields: [authorId], references: [id])
postToCategories PostToCategories[]

@@map("Posts")
}

model Profile {
id Int @id @default(autoincrement())
bio String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
userId Int? @unique
user User? @relation(fields: [userId], references: [id])

@@map("Profiles")
}

model User {
id Int @id @default(autoincrement())
name String?
email String @unique
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
posts Post[]
profile Profile?

@@map("Users")
}

ステップ 3. Prisma Client のインストール

次のステップとして、プロジェクトに Prisma Client をインストールして、現在 Sequelize で作成されているプロジェクト内のデータベースクエリの置き換えを開始できます。

npm install @prisma/client

ステップ 4. Sequelize クエリを Prisma Client に置き換える

このセクションでは、サンプル REST API プロジェクトのサンプルルートに基づいて、Sequelize から Prisma Client に移行するいくつかのサンプルクエリを示します。Prisma Client API が Sequelize とどのように異なるかについて包括的に知りたい場合は、API 比較ページを参照してください。

まず、さまざまなルートハンドラからデータベースクエリを送信するために使用する PrismaClient インスタンスを設定します。src ディレクトリに prisma.js という名前の新しいファイルを作成します。

touch src/prisma.js

次に、PrismaClient をインスタンス化し、後でルートハンドラで使用できるようにファイルからエクスポートします。

src/prisma.js
const { PrismaClient } = require('@prisma/client')

const prisma = new PrismaClient()

module.exports = prisma

コントローラファイルでのインポートは次のようになります。

src/controllers/post.js
const { Post, User, Category } = require('../models')
const { Op } = require('sequelize')
src/controllers/user.js
const { User } = require('../models')

Sequelize から Prisma に移行するにつれて、コントローラのインポートを更新します。

src/controllers/post.js
const prisma = require('../prisma')
src/controllers/user.js
const prisma = require('../prisma')

4.1. GET リクエストでのクエリの置換

REST API には、GET リクエストを受け入れる 4 つのルートがあります。

  • /feed: 公開されているすべての投稿を返します
  • /filterPosts?searchString=SEARCH_STRING: 返された投稿を SEARCH_STRING でフィルタリングします
  • /post/:postId: 特定の投稿を返します
  • /authors: 作成者の一覧を返します

これらのリクエストを実装するルートハンドラを見ていきましょう。

/feed

現在、/feed ハンドラは次のように実装されています。

src/controllers/post.js
const feed = async (req, res) => {
try {
const feed = await Post.findAll({
where: { published: true },
include: ['author', 'categories'],
})
return res.json(feed)
} catch (error) {
return res.status(500).json(error)
}
}

返される各 Post オブジェクトには、関連付けられている author および category へのリレーションが含まれていることに注意してください。Sequelize では、リレーションを含めることはタイプセーフではありません。たとえば、取得されるリレーションにタイプミスがあった場合、データベースクエリは実行時にのみ失敗します。JavaScript コンパイラはここでは安全を提供しません。

Prisma Client を使用して同じルートを実装する方法を以下に示します。

src/controllers/post.js
const feed = async (req, res) => {
try {
const feed = await prisma.post.findMany({
where: { published: true },
include: { author: true, postToCategories: true },
})
return res.json(feed)
} catch (error) {
return res.status(500).json(error)
}
}

Prisma Client が author リレーションを含める方法は完全にタイプセーフであることに注意してください。Post モデルに存在しないリレーションを含めようとすると、JavaScript コンパイラがエラーをスローします。

/filterPosts?searchString=SEARCH_STRING

現在、/filterPosts ハンドラは次のように実装されています。

src/controllers/post.js
const filterPosts = async (req, res) => {
const { searchString } = req.query

try {
const filteredPosts = await Post.findAll({
where: {
[Op.or]: [
{
title: {
[Op.like]: `%${searchString}%`,
},
},
{
content: {
[Op.like]: `%${searchString}%`,
},
},
],
},
include: 'author',
})

res.json(filteredPosts)
} catch (error) {
return res.status(500).json(error)
}
}

Prisma ORM では、ルートは次のように実装されます。

src/controllers/post.js
const filterPosts = async (req, res) => {
const { searchString } = req.query

try {
const filteredPosts = prisma.post.findMany({
where: {
OR: [
{
title: { contains: searchString },
},
{
content: { contains: searchString },
},
],
},
})

res.json(filteredPosts)
} catch (error) {
return res.status(500).json(error)
}
}

Sequelize は、データのクエリ時に使用する 演算子シンボル - Op - を提供していることに注意してください。一方、Prisma ORM は、複数の where 条件を暗黙的な AND 演算子と組み合わせているため、この場合、Prisma Client クエリは OR を明示的に行う必要があります。

/post/:postId

現在、/post/:postId ハンドラは次のように実装されています。

src/controllers/post.js
const getPostById = async (req, res) => {
const { postId } = req.params

try {
const post = await Post.findOne({
where: { id: postId },
include: 'author',
})

return res.json(post)
} catch (error) {
return res.status(500).json(error)
}
}

Prisma ORM では、ルートは次のように実装されます。

src/controllers/post.js
const getPostById = async (req, res) => {
const { postId } = req.params

try {
const post = await prisma.post.findUnique({
where: { id: Number(postId) },
include: { author: true },
})

return res.json(post)
} catch (error) {
return res.status(500).json(error)
}
}

4.2. POST リクエストでのクエリの置換

REST API には、POST リクエストを受け入れる 3 つのルートがあります。

  • /user: 新しい User レコードを作成します
  • /post: 新しい User レコードを作成します
  • /user/:userId/profile: 指定された ID を持つ User レコードの新しい Profile レコードを作成します

/user

現在、/user ハンドラは次のように実装されています。

src/controllers/user.js
const createUser = async (req, res) => {
const { name, email } = req.body

try {
const user = await User.create({
name,
email,
})

return res.json(user)
} catch (error) {
return res.status(500).json(error)
}
}

Prisma ORM では、ルートは次のように実装されます。

src/controllers/user.js
const createUser = async (req, res) => {
const { name, email } = req.body

try {
const user = await prisma.user.create({
data: {
name,
email,
},
})

return res.json(user)
} catch (error) {
return res.status(500).json(error)
}
}

/post

現在、/post ハンドラは次のように実装されています。

src/controllers/post.js
const createDraft = async (req, res) => {
const { title, content, authorEmail } = req.body

try {
const user = await User.findOne({ email: authorEmail })

const draft = await Post.create({
title,
content,
authorId: user.id,
})

res.json(draft)
} catch (error) {
return res.status(500).json(error)
}
}

Prisma ORM では、ルートは次のように実装されます。

src/controllers/post.js
const createDraft = async (req, res) => {
const { title, content, authorEmail } = req.body

try {
const draft = await prisma.post.create({
data: {
title,
content,
author: {
connect: { email: authorEmail },
},
},
})

res.json(draft)
} catch (error) {
return res.status(500).json(error)
}
}

Prisma Client のネストされた書き込みでは、最初に emailUser レコードを取得する必要がある最初のクエリが保存されることに注意してください。これは、Prisma Client を使用すると、一意のプロパティを使用してリレーションのレコードを接続できるためです。

/user/:userId/profile

現在、/user/:userId/profile ハンドラは次のように実装されています。

src/controllers/user.js
const setUserBio = async (req, res) => {
const { userId } = req.params
const { bio } = req.body

try {
const user = await User.findOne({
where: {
id: Number(userId),
},
})

const updatedUser = await user.createProfile({ bio })

return res.json(updatedUser)
} catch (error) {
return res.status(500).json(error)
}
}

Prisma ORM では、ルートは次のように実装されます。

src/controllers/user.js
const setUserBio = async (req, res) => {
const { userId } = req.params
const { bio } = req.body

try {
const user = await prisma.user.update({
where: { id: Number(userId) },
data: {
profile: {
create: { bio },
},
},
})

return res.json(user)
} catch (error) {
return res.status(500).json(error)
}
}

4.3. PUT リクエストでのクエリの置換

REST API には、PUT リクエストを受け入れる 1 つのルートがあります。

  • /addPostToCategory?postId=POST_ID&categoryId=CATEGORY_ID: POST_ID を持つ投稿を CATEGORY_ID を持つカテゴリに追加します

これらのリクエストを実装するルートハンドラを見ていきましょう。

/addPostToCategory?postId=POST_ID&categoryId=CATEGORY_ID

現在、/addPostToCategory?postId=POST_ID&categoryId=CATEGORY_ID ハンドラは次のように実装されています。

src/controllers/post.js
const addPostToCategory = async (req, res) => {
const { postId, categoryId } = req.query

try {
const post = await Post.findOne({
where: { id: postId },
})

const category = await Category.findOne({
where: { id: categoryId },
})

const updatedPost = await post.addCategory(category)

return res.json(updatedPost)
} catch (error) {
return res.status(500).json(error)
}
}

Prisma ORM では、ルートは次のように実装されます。

src/controllers/post.js
const addPostToCategory = async (req, res) => {
const { postId, categoryId } = req.query

try {
const post = await prisma.post.update({
data: {
postToCategories: {
create: {
categories: {
connect: { id: Number(categoryId) },
},
},
},
},
where: {
id: Number(postId),
},
})

return res.json(post)
} catch (error) {
return res.status(500).json(error)
}
}

この Prisma Client は、リレーションを暗黙的な多対多リレーションとしてモデル化することで、冗長性を減らすことができます。その場合、クエリは次のようになります。

src/controllers/posts.js
const post = await prisma.post.update({
data: {
category: {
connect: { id: categoryId },
},
},
where: { id: postId },
})

その他

主キー列

デフォルトでは、Sequelize は primaryKey を定義し、定義されていない場合はデフォルトで id を使用しました。これはオプションです。独自の主キーを設定する場合は、primaryKey: true を使用し、選択したフィールドで優先するデータ型を定義できます。

// changing the primary key column
module.exports = (sequelize, DataTypes) => {
const Post = sequelize.define('Post', {
postId: {
type: DataTypes.INTEGER,
primaryKey: true,
},
})
return Post
}

// changing the id DataType
module.exports = (sequelize, DataTypes) => {
const Post = sequelize.define('Post', {
id: {
type: DataTypes.UUID, // alternative: DataTypes.STRING
primaryKey: true,
},
})
return Post
}

テーブル名の推論

Sequelize は、モデル名からテーブル名を推測します。テーブル名が指定されていない場合、Sequelize はモデル名を自動的に複数形にし、inflectionというライブラリを使用してテーブル名として使用します。一方、Prisma ORM は、データのモデリングで、モデル名をデータベース内のテーブル名にマッピングします。Sequelize でこのデフォルトの動作を変更する場合は、テーブル名をモデル名と等しくなるように強制するか、テーブル名を直接指定できます。

// enforcing table name to be equal to model name
module.exports = (sequelize, DataTypes) => {
const Post = sequelize.define(
'Post',
{
// ... attributes
},
{
freezeTableName: true,
}
)
return Post
}
// providing the table name directly
module.exports = (sequelize, DataTypes) => {
const Post = sequelize.define(
'Post',
{
// ... attributes
},
{
tableName: 'Post',
}
)
return Post
}

タイムスタンプ

Sequelize は、デフォルトで、データ型 DataTypes.DATE を使用して、すべてのモデルにフィールド createdAt および updatedAt を自動的に追加します。timestamps: false オプションを使用すると、モデルでこれを無効にできます。

sequelize.define(
'User',
{
// ... (attributes)
},
{
timestamps: false,
}
)

Prisma ORM では、これらのフィールドをモデルで柔軟に定義できます。createdAt および updatedAt フィールドをモデルで明示的に定義することにより追加します。モデルに createdAt フィールドを設定するには、列に default(now()) 属性を追加します。updatedAt 列を設定するには、列に @updatedAt 属性を追加してモデルを更新します。

model User {
id Int @id @default(autoincrement())
name String?
email String @unique
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}

暗黙的な多対多の関係

SequelizeのbelongsToMany()関連付けメソッドと同様に、Prisma ORMでは多対多の関係を暗黙的にモデル化できます。つまり、関係テーブル(JOINテーブルとも呼ばれる)をスキーマで明示的に管理する必要がない多対多の関係です。以下はSequelizeの例です。

module.exports = (sequelize, DataTypes) => {
const Post = sequelize.define('Post', {
title: {
type: DataTypes.STRING,
allowNull: false,
},
content: {
type: DataTypes.STRING,
},
published: {
type: DataTypes.BOOLEAN,
defaultValue: false,
},
})
Post.associate = (models) => {
Post.belongsTo(models.User, {
foreignKey: 'authorId',
as: 'author',
})
Post.belongsToMany(models.Category, {
through: 'PostCategories',
as: 'categories',
})
}
return Post
}
module.exports = (sequelize, DataTypes) => {
const Category = sequelize.define('Category', {
name: {
type: DataTypes.STRING,
allowNull: false,
},
})
Category.associate = (models) => {
Category.belongsToMany(models.Post, {
through: 'PostCategories',
as: 'posts',
})
}
return Category
}

アプリケーションを起動すると、Sequelizeはこれらのモデルに基づいてテーブルを作成します。

Executing (default): CREATE TABLE IF NOT EXISTS "PostCategories"
("createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL,
"CategoryId" INTEGER REFERENCES "Categories" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
"PostId" INTEGER REFERENCES "Posts" ("id") ON DELETE CASCADE ON UPDATE CASCADE, PRIMARY KEY ("CategoryId","PostId"));

Prisma ORMでデータベースをイントロスペクトすると、Prismaスキーマで次の結果が得られます(一部のリレーションフィールド名は、イントロスペクションからの生のバージョンと比較して、よりわかりやすく調整されていることに注意してください)。

model Categories {
id Int @id @default(autoincrement())
name String
createdAt DateTime
updatedAt DateTime
PostCategories PostCategories[]

@@map("category")
}

model PostCategories {
createdAt DateTime
updatedAt DateTime
CategoryId Int
PostId Int
Categories Categories @relation(fields: [CategoryId], references: [id])
Posts Posts @relation(fields: [PostId], references: [id])

@@id([CategoryId, PostId])
@@map("PostCategories")
}

model Posts {
id Int @id @default(autoincrement())
title String
content String?
published Boolean? @default(false)
createdAt DateTime
updatedAt DateTime
authorId Int?
Users Users? @relation(fields: [authorId], references: [id])
PostCategories PostCategories[]

@@map("post")
}

このPrismaスキーマでは、多対多の関係は関係テーブルPostCategoriesを介して明示的にモデル化されています。

Prismaの関係テーブルの規約に従うことで、関係は次のようになります。

model Categories {
id Int @id @default(autoincrement())
name String
posts Posts[]

@@map("category")
}

model Posts {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
authorId Int?
author User? @relation(fields: [authorId], references: [id])
categories Categories[]

@@map("post")
}

これにより、この関係のレコードを変更するためのPrisma Client APIがより人間工学的で簡潔になります。なぜなら、PostCategoriesモデルを最初にたどる必要はなく、PostからCategoryへの直接パス(およびその逆)を持つことができるからです。