Prisma:(1)
2026年6月21日https://www.prisma.io/docs/orm/getting-started
インストール・概要
pnpm add prisma tsx @types/pg --save-dev
pnpm add @prisma/client @prisma/adapter-pg dotenv pg
pnpm add -D dotenv-clinpx prisma initコマンドでルートにprismaフォルダとconfigファイルができる。
pull と migrate / push:
pull:DBの構造を元にSchemaを作成
migrate / push:Schemaを元にDBを変更
migrateはsqlファイルとして履歴が残る / pushは残らない
Prisma v7
prisma.ts :
import { PrismaPg } from "@prisma/adapter-pg";
import { PrismaClient } from "./generated/prisma";
const adapter = new PrismaPg({
connectionString: process.env.VERCEL_DATABASE_URL,
},{ schema: "" });
const globalForPrisma = global as unknown as {
prisma: PrismaClient;
};
export const prisma =
globalForPrisma.prisma ||
new PrismaClient({
adapter,
});
if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;DBスキーマ
~v7までは環境変数にDBのスキーマを含めた接続URLを書くだけで良かったが、v7では接続URLを書くだけだとpulbicスキーマの構造が取得される。
対策1(推奨): prisma.ts
const adapter = new PrismaPg(
{
connectionString: process.env.DATABASE_URL,
},
{
schema: "", // 追加
}
);対策2: schema.prisma
generator client {
provider = "prisma-client"
output = "./generated/prisma"
}
datasource db {
provider = "postgresql"
schemas = ["scheme_name"]
}
model User {
...
@@schema("scheme_name")
}@@schema("")をすべてのモデルに書く。
スクリプトコマンド
Prismaは.env しか自動で読み込まないのでdotenv-cliを用いたスクリプトコマンドを予め登録する。
"scripts": {
"db:pull": "dotenv -e .env.development.local -- npx prisma db pull",
"db:generate": "dotenv -e .env.development.local -- npx prisma generate",
"db:push": "dotenv -e .env.development.local -- npx prisma db push",
"db:studio": "dotenv -e .env.development -- npx prisma studio"
"db:migrate": "dotenv -e .env.development -- npx prisma migrate dev"
},migrateのやり方に慣れたら
db:pushは削除した方が良い。
データが無い場合
import { prisma } from "@/prisma/prisma";
export const getTags = async () => {
try {
return await prisma.tag.findMany();
} catch (error) {
console.error("Failed to fetch tags:", error);
return [];
}
};データ(レコード)が空の場合、空配列が返るので明示的に空配列を返す必要は無い。
上記はcatchで明示的に空配列を返しているが、DB接続やクエリ失敗時でもアプリを安全に動かすため
migrateの使用
*pull/pushコマンドで管理している既存のDBに対してmigrateコマンドを実行するとデータを全削除することが促される。Yキーを押した時点でDB ( schema )からデータがすべて削除される。
migrateについて:
migrate devでprisma.schemaのモデルをもとにDBにテーブルが作られる。DBスキーマを事前に作成する必要はない。migrate devの実行時に変更履歴がsqlファイルとして残る。また_prisma_migrationsというmigrationの管理用テーブルがDBに作成される。*スクラッチの状態からmigrateコマンドでDBを管理しないと難しい仕組みになっているので、場合によってはpush/pullコマンドで管理する方に割り切った方が良い。
.gitignore
重要:
prisma/migrationsフォルダをgitignoreに含めてはいけない。prisma/generatedはスキーマがあれば生成できるので含めていい。
migrationsフォルダがローカルにないとmigrateコマンド時にエラーになる。
*本番DBからローカルにmigrationsフォルダを生成するコマンドは無い。
開発/本番でDBを分ける
docker-compose.yml:
services:
db:
image: postgres:16
container_name: dev_db
ports:
- "5432:5432"
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: dev_db
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:開発時は
docker compose up -dでコンテナのPostgresを使う。
.env.local:
DATABASE_URL="postgresql://user:pass@localhost:5432/dev_db?schema=test"package.json:scripts
"vercel-build": "prisma generate && prisma migrate deploy && next build"Vercelダッシュボードのビルドコマンドを上記に変更
migrateを途中から取り入れる場合
pull/push で管理しているPrismaを使ったアプリをmigrateによる管理に切り替える場合
既存データがあるDBの接続URLの状態で
db pullをしてprisma.schemaにモデルを作成空フォルダ
/prisma/migrations/0_initを作成以下のコマンドを実行
npx dotenv -q -e .env.development -- npx prisma migrate diff \
--from-empty \
--to-schema prisma/schema.prisma \
--script > prisma/migrations/0_init/migration.sql「3」で
/migrations/0_initに作成されたmigration.sqlの上部に以下のような行がある場合は削除
[dotenv@17.2.3] injecting env (0) from .env.development -- tip: ✅ audit secrets and track compliance: https://dotenvx.com/ops以下のコマンドを実行
npx dotenv -q -e .env.development -- npx prisma migrate resolve --applied 0_init*事前に色々いじっていて本番DBに
_prisma_migrationsテーブルがある場合は慎重に削除する。
DBの接続URLを開発のもの(コンテナ)に切り替えてからmigrateコマンドを実行
pnpm run db:migrate*コンテナのDB(スキーマ)に
_prisma_migrationsテーブルがあるとエラーになるのでテーブルを削除するか、スキーマ自体を削除してからコマンドを再実行
リポジトリに
git pushをして Vercelでmigrate deployコマンドが成功するか確認
以降:
開発環境で
schema.prismaに変更を行ったらmigrate devでmigrationファイルを作成する
Vercel(migrate deploy)でエラーが出るケース:
モデルのidのStringをIntに変えたような場合、DBには既にStringのidとしてデータがあるのでエラーが出る。
対策はモデルを変えたテーブルのデータを全て消す
モデル名(テーブル名)自体の変更も既存の構造に変更を加える事を意味するので対策が難しい
Vercel
Prisma Clientの生成
Prismaを使ったNextjsアプリをそのままVercelにデプロイするとエラーになる
原因:
Prisma Clientは
schema.prismaを元に生成されるコードを必要とするが、Vercelのビルドコマンドはnext buildだけなので、ビルド時にPrisma Client が未生成になりエラーが出る
対策 1(推奨):Vercelの build command を以下にoverride
npx prisma generate && turbo run build
対策2:
package.jsonの scripts に以下を追加
"scripts": {
"postinstall": "npx prisma generate"
},ターミナルで依存関係をインストールする度にpostinstallが走る
connection_limit
Timed out fetching a new connection from the connection pool. More info: http://pris.ly/d/connection-pool (Current connection pool timeout: 10, connection limit: 3)
原因:
ビルド時に静的ページのプリレンダリングを行う際に並行処理で同時にPrismaへの接続が行われるので、すぐにデフォルトのconnection_limit = 3 に達した為
対策:
環境変数の
DATABASE_URLの後ろに以下を追加するとビルドエラーがなくなった
&connection_limit=5The database server was reached but timed out
migrateコマンドを取り入れてから発生。原因はわからないが以下でこのエラーは消える
対策:
Vercelのダッシュボードから環境変数に以下を追加
PRISMA_SCHEMA_DISABLE_ADVISORY_LOCK=trueモデル
モデルの型を使用
schema.prismaのモデルはPrisma Clientを生成するとgeneratedフォルダ内に型情報として残るので、それを型として使うことができる。
import { User } from "@/prisma/generated/prisma";
const exampleUser: User = {
id: "asd",
name: "rwe",
age: 11,
};リレーションも含めた型
Prisma.GetPayload:
import { Prisma } from "@/prisma/generated/prisma";
type Props = Prisma.ItemsGetPayload<{
include: { categories: { include: { category: true } } };
}>[];typeof(推奨):
const items = await prisma.Item.findMany({
include: {
tags: true,
},
})
export type Item = (typeof items)[number]モデルの内容
@map:
userId String @map("user_id")DBでは
user_idだが、補完ではuserIdとして使える
@@map:
model Post {
...
@@map("post")
}DBのテーブル名は
post、型としてはPostで使える
アプリはcamelCase、DBはsnake_caseが一般的なので @mapや@@mapの設定を見ることが多い。モデルが大文字スタートなのは型の慣習に合わせるため。慣習に従うならモデル名は大文字スタートのcamelCase、要素は小文字スタートのcamelCaseで書いて@mapでsnake_caseに、テーブルは@@mapでsnake_caseにする。
@relation:
model User {
id Int @id @default(autoincrement()) // Postの参照先
name String
posts Post[]
}
model Post {
id Int @id @default(autoincrement())
title String
content String
authorId Int // 外部キー
author User @relation(fields: [authorId], references: [id])
}PostがどのUserのものかを示す情報 (= 外部キー)はPostが持つ
1対多の関係では「多」の方が外部キーを持つ
( fields: [外部キー] , references: [外部キーが参照する先] )
Cascade:
model Recipe {
id Int @id @default(autoincrement())
dishName String
ingredient Ingredient[]
createdAt DateTime @default(now())
}
model Ingredient {
id Int @id @default(autoincrement())
name String
recipeId Int
recipe Recipe @relation(fields: [recipeId], references: [id], onDelete: Cascade)
}参照先 = 親(Recipe)が削除されたら、子(Ingredient)も自動で削除される
@enum:
enum Status {
DRAFT
PUBLISHED
}
model Post {
status Status @default(PUBLISHED)
}@enumはDB側に設定を追加するので、後で削除や変更をする際は既存データを消さないとmigrate devが通らなくなる
@@unique:
model User {
id Int @id @default(autoincrement())
email String @unique
memos Memo[]
}
model Memo {
id Int @id @default(autoincrement())
userId Int
title String
body String?
user User @relation(fields: [userId], references: [id])
@@unique([userId, title])
}MemoのtitleをUniqueにすることでUserが同じtitleのMemoを持つ事は避けたいとする。しかしUserが複数いることを想定するとtitleをUniqueにしてしまったらあるUserが作成したtitleを他のUserは作れなくなる。
@@uniqueでuser_idとtitleを紐付けることでユーザー間でtitleが被ることは許容できるようになる。titleの方はnullableでも構わない
idは自動生成
model Bookmark {
id String上記のような場合、create時に毎回 id:uuid()などでidの生成を行わないといけないので、自動生成するようにしたほうが良い。
model Bookmark {
id String @id @default(cuid())他には
@default(uuid())、@default(autoincrement())がある。