Next.js で PlanetScale (+ Prisma) を使う
まずは https://planetscale.com/ からアカウントを作成する
DB を作る(GUI から DB 名とリージョンを決めるだけで作成できる)
(CLI から作成する場合は pscale database create {DB名}
)
Prisma のセットアップ
💡 参考 の手順に従っている。
- Next.js アプリのセットアップ(既存の Next.js プロジェクトに組み込む場合はスキップ)
- 次に、Next.js プロジェクトのディレクトリで
npx prisma init
を実行- .env と schema.prisma が作成される
.env に DATABASE_URL="mysql://root@127.0.0.1:3309/{さっき決めた DB 名}"
を追加
スキーマを定義する
schema.prisma
ファイルを開き、以下の内容で更新する:
generator client {
provider = "prisma-client-js"
previewFeatures = ["referentialIntegrity"]
}
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
referentialIntegrity = "prisma"
}
PlanetScale は外部キー制約をサポートしておらず、かつ Prisma はリレーションを表現するのにデフォルトで外部キーを使うようになっているため、上記のように referentialIntegrity
プロパティを設定する必要がある。
続いて、このスキーマファイルにデータモデルを定義していく。
ここは(PlanetScale 固有の話ではなく)Prisma の話。
例)
model Inquiry {
id Int @default(autoincrement()) @id
name String
email String
subject String?
message String
}
Referential integrity について
以下、上記の Prisma のドキュメントより引用(拙訳)。
これを設定している場合、あるレコードが別のレコードを参照しているとき、参照されたレコードは必ず存在している必要がある。
たとえば、Post
モデルが author を定義している場合、author も必ず存在していなければならない。
こういった参照を破壊するような変更を加える制約を課すこと、および、レコードの更新時あるいは削除時に実行される referential actions を定義することによって Referential integrity が強制される。
Post
モデルの例では、author が削除されたときの処理を定義しておく必要があるということになる。
Prisma では、Referential integrity は @relation
属性を使ってレコード間のリレーションを定義することによって実装(というか実現)される。@relation
属性に対して onUpdate
および onDelete
引数を与えることで referential action が被参照レコード(上記例では author)の更新時および削除時に実行される。
model Post {
id Int @id @default(autoincrement())
title String
author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
authorId Int
}
model User {
id Int @id @default(autoincrement())
posts Post[]
}
この例では、onDelete: Cascade
が指定されており、User
が削除されると関連する Post
が削除される。
他の Referential actions はこちら。
memo
うーん、上で言ってることは理解できるけど結局言われた以上のことはわからない(「つまり外部キー制約が有効な場合のこの操作はこうしないといけないってことか!」みたいな自己解釈を挟んだ上での納得は得られていない。そもそもバックエンドに関してゆるふわ理解駆動開発しかしていないため)
データベースをローカルで実行する
データモデルを定義したら、ターミナルで以下を実行する:
pscale connect {DB名} main --port 3309
このコマンドを実行することで、DB へのローカルのプロキシーが立ち上がる。それによって、ローカルでアプリを動かしているときに DB に接続することが可能になる。
また、上記のコマンドの引数の変更で DB のブランチを変えることができる。
このステップではメインブランチは本番環境へ昇格していない。次のステップで Prisma のスキーマを DB のスキーマと同期させる。
なお、本番ブランチのスキーマを変更することはできない(メモ:直接変更することはできないという意味? 後の部分で詳しい説明が出てくるっぽい)。
新しいターミナルで、以下のコマンドを実行することで prisma.schema で定義したスキーマと PlanetScale のスキーマを同期させる:
npx prisma db push
-> 成功メッセージ "Your database is now in sync with your schema." を確認する。
pscale shell {DB 名} main
を実行し、describe {テーブル名};
(セミコロン忘れずに)を実行することでテーブルを確認できる(スキーマと DB の同期成功を確認できる)。
⚠️ このとき、mysql-client
が必要(マシンに入っていなければ、mysql-client を入れろというメッセージ付きのエラーが出るので、(それに従えばいいため)あまり気にしなくてよい)
pscale branch promote {DB 名} main
を実行することで、ブランチを本番に昇格させる。
メモ: ここでの本番 (production) ってどういう意味だろうか(シンプルに考えたら本番環境で使うための DB だと思ってたが)。このあと API を作って DB に書き込んだりするステップが来るが (POST http://localhost:3000/api/hoge
)、本番の DB をローカルから更新するということ?(チュートリアルだから?)
チュートリアルを読み進めればわかるかな。
参考にした記事を最後まで読んだけど DB のブランチ運用についてはあまり書かれていなくて、今回は main ブランチだけを作成してそれを開発時も本番時も使うというチュートリアルだから上記のようになったよう。
いずれにしても、本番ブランチが存在しないということはありえないので上記のように main ブランチなどを本番に昇格させて、その後は開発ブランチ (DB) を作成してそれを使って開発→本番ブランチ (DB) にマージという流れ。
ブランチ運用に関しては以下のページがまとまってそう:
pscale コマンドがうまくいかないときは
ログインできてない可能性あり:pscale auth login
でログイン
PlanetScale に複数 Organization 持っている場合は正しい Org が指定されてないかも:pscale org switch {org-name}
補足:pscale CLI のインストール
brew install planetscale/tap/pscale
"pscale: undefined method `license' for #Class:0x000...." というエラーが出たため以下を実行した:
brew update-reset
brew config
この後再度 brew install planetscale/tap/pscale を実行したところインストール成功。
ここまでの手順を終えたらあとは Next.js の API Route で以下のように API を定義できる:
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
export default async function handler(req, res) {
if (req.method === 'POST') {
return await createInquiry(req, res);
}
else {
return res.status(405).json({ message: 'Method not allowed', success: false });
}
}
async function createInquiry(req, res) {
const body = req.body;
try {
const newEntry = await prisma.inquiry.create({
data: {
name: body.firstName,
email: body.email,
subject: body.subject,
message: body.message
}
});
return res.status(200).json(newEntry, {success: true});
} catch (error) {
console.error("Request error", error);
res.status(500).json({ error: "Error creating question", success:false });
}
}
実装中に遭遇した事象と対処法のメモなど
- PrismaClient のインスタンスは以下のように一箇所で new したものを export して使いまわさないと
warn(prisma-client)There are already 10 instances of Prisma Client actively running.
という警告が出る
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
export { prisma }
- DB は立ち上げ済みなのに
Error: P1001: Can't reach database server at 127.0.0.1:3309 when connecting locally with prisma
というエラーが出る場合
結論としては DATABASE_URL=mysql://root@127.0.0.1:3309/{YOUR_DB_NAME}?connect_timeout=30&pool_timeout=30&socket_timeout=30
という設定にすることで解決した。
詳細:https://github.com/planetscale/discussion/discussions/139#discussioncomment-1794696
Prisma Ver. 4.5.0 以降はスキーマ定義の client
と db
は以下のように変更する必要あり。
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
relationMode = "prisma"
}