Prisma ORM完全ガイド2026 — TypeScript最強のORM
Prismaは、TypeScriptとNode.jsのための次世代ORMです。型安全、直感的なAPI、優れた開発者体験により、多くのプロジェクトで採用されています。本記事では、Prismaの基礎から実践的な活用法まで解説します。
Prismaとは
PrismaはTypeScriptファーストのORMで、以下の特徴があります。
主な特徴
- 型安全: スキーマから自動生成されるTypeScriptの型
- 直感的なAPI: SQLを書かずにデータベース操作
- Prisma Studio: データベースのGUIツール
- マイグレーション: スキーマ変更を管理
- 複数DB対応: PostgreSQL、MySQL、SQLite、SQL Server、MongoDB
従来のORMとの違い
従来のORM(TypeORM、Sequelizeなど):
- デコレーター/クラスベース
- 実行時の型チェック
- 複雑な設定
Prisma:
- スキーマファイルで定義
- コンパイル時の型チェック
- シンプルな設定
- 自動生成されるクライアント
セットアップ
インストール
npm install prisma --save-dev
npm install @prisma/client
初期化
npx prisma init
これで以下のファイルが作成されます:
.
├── prisma/
│ └── schema.prisma
└── .env
.env:
DATABASE_URL="postgresql://user:password@localhost:5432/mydb?schema=public"
Prismaスキーマ定義
prisma/schema.prisma:
// データソース設定
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
// クライアント生成設定
generator client {
provider = "prisma-client-js"
}
// モデル定義
model User {
id String @id @default(cuid())
email String @unique
name String?
role Role @default(USER)
posts Post[]
profile Profile?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([email])
}
model Profile {
id String @id @default(cuid())
bio String?
avatar String?
userId String @unique
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
model Post {
id String @id @default(cuid())
title String
content String?
published Boolean @default(false)
authorId String
author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
tags Tag[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([authorId])
@@index([published])
}
model Tag {
id String @id @default(cuid())
name String @unique
posts Post[]
}
enum Role {
USER
ADMIN
}
スキーマの主要要素
フィールド型:
model Example {
// 基本型
string String
int Int
float Float
boolean Boolean
dateTime DateTime
json Json
bytes Bytes
// オプショナル
optional String?
// 配列
tags String[]
// リレーション
posts Post[]
}
フィールド属性:
model User {
id String @id @default(uuid()) // プライマリキー
email String @unique // ユニーク制約
name String @db.VarChar(255) // DB固有の型
@@index([email]) // インデックス
@@unique([email, name]) // 複合ユニーク
}
リレーション:
// 1対1
model User {
id String @id
profile Profile?
}
model Profile {
id String @id
userId String @unique
user User @relation(fields: [userId], references: [id])
}
// 1対多
model User {
id String @id
posts Post[]
}
model Post {
id String @id
authorId String
author User @relation(fields: [authorId], references: [id])
}
// 多対多
model Post {
id String @id
tags Tag[]
}
model Tag {
id String @id
posts Post[]
}
// 多対多(明示的な中間テーブル)
model Post {
id String @id
postTags PostTag[]
}
model Tag {
id String @id
postTags PostTag[]
}
model PostTag {
postId String
tagId String
post Post @relation(fields: [postId], references: [id])
tag Tag @relation(fields: [tagId], references: [id])
@@id([postId, tagId])
}
マイグレーション
スキーマの変更をデータベースに反映します。
開発環境
# マイグレーションを作成・適用
npx prisma migrate dev --name init
# マイグレーション名の例:
# - init
# - add_user_role
# - create_posts_table
これで以下が実行されます:
- マイグレーションファイル生成(
prisma/migrations/) - データベースに適用
- Prisma Clientの再生成
本番環境
# マイグレーションのみ適用(生成しない)
npx prisma migrate deploy
その他のコマンド
# スキーマをDBと同期(開発のみ、マイグレーション履歴なし)
npx prisma db push
# DBからスキーマを生成(既存DBをPrismaに移行)
npx prisma db pull
# マイグレーション状態の確認
npx prisma migrate status
# マイグレーションのリセット(全データ削除!)
npx prisma migrate reset
Prisma Client
自動生成されるタイプセーフなクライアントです。
クライアントの生成
npx prisma generate
スキーマ変更後は必ず再生成してください。
基本的な使い方
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
// アプリ終了時にクリーンアップ
process.on('beforeExit', async () => {
await prisma.$disconnect();
});
シングルトンパターン(推奨):
// lib/prisma.ts
import { PrismaClient } from '@prisma/client';
const globalForPrisma = global as unknown as { prisma: PrismaClient };
export const prisma =
globalForPrisma.prisma ||
new PrismaClient({
log: ['query', 'error', 'warn'],
});
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;
CRUD操作
Create(作成)
// 単一レコード作成
const user = await prisma.user.create({
data: {
email: 'alice@example.com',
name: 'Alice',
role: 'USER',
},
});
// リレーション付きで作成
const userWithProfile = await prisma.user.create({
data: {
email: 'bob@example.com',
name: 'Bob',
profile: {
create: {
bio: 'Hello, I am Bob',
},
},
posts: {
create: [
{ title: 'First Post', content: 'Hello World' },
{ title: 'Second Post', content: 'Learning Prisma' },
],
},
},
include: {
profile: true,
posts: true,
},
});
// 複数レコード作成
const users = await prisma.user.createMany({
data: [
{ email: 'user1@example.com', name: 'User 1' },
{ email: 'user2@example.com', name: 'User 2' },
],
skipDuplicates: true, // 重複を無視
});
Read(読み取り)
// 全件取得
const allUsers = await prisma.user.findMany();
// 条件付き取得
const users = await prisma.user.findMany({
where: {
email: {
contains: '@example.com',
},
role: 'USER',
},
orderBy: {
createdAt: 'desc',
},
take: 10, // LIMIT
skip: 0, // OFFSET
});
// 単一レコード取得
const user = await prisma.user.findUnique({
where: { id: '123' },
include: {
posts: true,
profile: true,
},
});
// 最初のレコード
const firstUser = await prisma.user.findFirst({
where: { role: 'ADMIN' },
});
// 存在チェック(findFirstより高速)
const exists = await prisma.user.findUnique({
where: { email: 'test@example.com' },
select: { id: true },
});
// カウント
const userCount = await prisma.user.count({
where: { role: 'USER' },
});
Update(更新)
// 単一レコード更新
const updatedUser = await prisma.user.update({
where: { id: '123' },
data: {
name: 'Alice Updated',
},
});
// 複数レコード更新
const updated = await prisma.user.updateMany({
where: {
role: 'USER',
},
data: {
role: 'ADMIN',
},
});
// リレーション更新
const userWithNewPosts = await prisma.user.update({
where: { id: '123' },
data: {
posts: {
create: {
title: 'New Post',
},
disconnect: {
id: 'post-to-remove',
},
},
},
});
// Upsert(存在すれば更新、なければ作成)
const user = await prisma.user.upsert({
where: { email: 'alice@example.com' },
update: {
name: 'Alice Updated',
},
create: {
email: 'alice@example.com',
name: 'Alice',
},
});
Delete(削除)
// 単一レコード削除
const deletedUser = await prisma.user.delete({
where: { id: '123' },
});
// 複数レコード削除
const deleted = await prisma.user.deleteMany({
where: {
createdAt: {
lt: new Date('2023-01-01'),
},
},
});
// 全件削除(危険!)
await prisma.user.deleteMany();
高度なクエリ
フィルター
// AND条件
const users = await prisma.user.findMany({
where: {
AND: [
{ role: 'USER' },
{ email: { contains: '@example.com' } },
],
},
});
// OR条件
const users = await prisma.user.findMany({
where: {
OR: [
{ role: 'ADMIN' },
{ email: { endsWith: '@admin.com' } },
],
},
});
// NOT条件
const users = await prisma.user.findMany({
where: {
NOT: {
role: 'GUEST',
},
},
});
// リレーションフィルター
const users = await prisma.user.findMany({
where: {
posts: {
some: { // 1つでも条件に合う投稿がある
published: true,
},
},
},
});
const users = await prisma.user.findMany({
where: {
posts: {
every: { // すべての投稿が条件に合う
published: true,
},
},
},
});
const users = await prisma.user.findMany({
where: {
posts: {
none: { // 条件に合う投稿がない
published: false,
},
},
},
});
Select と Include
// Select: 特定フィールドのみ取得
const users = await prisma.user.findMany({
select: {
id: true,
email: true,
posts: {
select: {
title: true,
},
},
},
});
// Include: リレーションを含める
const users = await prisma.user.findMany({
include: {
posts: true,
profile: true,
},
});
// 組み合わせ
const users = await prisma.user.findMany({
select: {
id: true,
email: true,
_count: {
select: {
posts: true, // 投稿数をカウント
},
},
},
});
ページネーション
// カーソルベース(推奨)
async function getPosts(cursor?: string, take = 10) {
const posts = await prisma.post.findMany({
take: take + 1,
...(cursor && {
cursor: { id: cursor },
skip: 1,
}),
orderBy: { createdAt: 'desc' },
});
const hasMore = posts.length > take;
const items = hasMore ? posts.slice(0, -1) : posts;
const nextCursor = hasMore ? items[items.length - 1].id : null;
return { items, nextCursor, hasMore };
}
// オフセットベース
async function getPosts(page = 1, pageSize = 10) {
const skip = (page - 1) * pageSize;
const [posts, total] = await Promise.all([
prisma.post.findMany({
skip,
take: pageSize,
orderBy: { createdAt: 'desc' },
}),
prisma.post.count(),
]);
return {
posts,
total,
page,
pageSize,
totalPages: Math.ceil(total / pageSize),
};
}
集計
// グループ化と集計
const stats = await prisma.post.groupBy({
by: ['authorId'],
_count: {
id: true,
},
_avg: {
viewCount: true,
},
having: {
id: {
_count: {
gt: 5, // 5投稿以上のユーザーのみ
},
},
},
});
// 集計のみ
const aggregates = await prisma.post.aggregate({
_count: true,
_avg: {
viewCount: true,
},
_sum: {
viewCount: true,
},
_min: {
createdAt: true,
},
_max: {
createdAt: true,
},
});
トランザクション
複数の操作をアトミックに実行します。
インタラクティブトランザクション(推奨)
const result = await prisma.$transaction(async (tx) => {
// ユーザー作成
const user = await tx.user.create({
data: {
email: 'alice@example.com',
name: 'Alice',
},
});
// プロファイル作成
const profile = await tx.profile.create({
data: {
userId: user.id,
bio: 'Hello',
},
});
// 投稿作成
const post = await tx.post.create({
data: {
title: 'First Post',
authorId: user.id,
},
});
return { user, profile, post };
});
// エラーが発生すると全てロールバック
バッチトランザクション
const [user, posts] = await prisma.$transaction([
prisma.user.create({ data: { email: 'test@example.com' } }),
prisma.post.findMany({ where: { published: true } }),
]);
タイムアウト設定
await prisma.$transaction(
async (tx) => {
// 長時間の処理
},
{
maxWait: 5000, // 最大待機時間(ms)
timeout: 10000, // タイムアウト(ms)
}
);
Next.js統合
Next.jsでPrismaを使う際のベストプラクティスです。
API Routes
// pages/api/users.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import { prisma } from '@/lib/prisma';
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method === 'GET') {
const users = await prisma.user.findMany();
return res.status(200).json(users);
}
if (req.method === 'POST') {
const user = await prisma.user.create({
data: req.body,
});
return res.status(201).json(user);
}
res.status(405).json({ error: 'Method not allowed' });
}
App Router(Server Components)
// app/users/page.tsx
import { prisma } from '@/lib/prisma';
export default async function UsersPage() {
const users = await prisma.user.findMany({
include: {
posts: true,
},
});
return (
<div>
{users.map(user => (
<div key={user.id}>
<h2>{user.name}</h2>
<p>{user.posts.length} posts</p>
</div>
))}
</div>
);
}
// ISR
export const revalidate = 60; // 60秒ごとに再生成
Server Actions
// app/actions.ts
'use server';
import { prisma } from '@/lib/prisma';
import { revalidatePath } from 'next/cache';
export async function createPost(formData: FormData) {
const title = formData.get('title') as string;
const content = formData.get('content') as string;
await prisma.post.create({
data: {
title,
content,
authorId: 'user-id',
},
});
revalidatePath('/posts');
}
Prisma Studio
データベースのGUIツールです。
npx prisma studio
ブラウザで http://localhost:5555 が開き、データの閲覧・編集ができます。
パフォーマンス最適化
コネクションプール
const prisma = new PrismaClient({
datasources: {
db: {
url: process.env.DATABASE_URL,
},
},
// コネクションプール設定(PostgreSQL)
// DATABASE_URL="postgresql://user:pass@host:5432/db?connection_limit=10"
});
クエリ最適化
// N+1問題を回避
const users = await prisma.user.findMany({
include: {
posts: true, // 1クエリで取得
},
});
// 不要なデータを取得しない
const users = await prisma.user.findMany({
select: {
id: true,
email: true,
},
});
バッチ読み込み
// 悪い例(N+1)
for (const userId of userIds) {
const user = await prisma.user.findUnique({ where: { id: userId } });
}
// 良い例
const users = await prisma.user.findMany({
where: {
id: { in: userIds },
},
});
まとめ
Prismaは型安全で生産性の高いORM開発を実現します。
重要なポイント:
- スキーマファーストで型安全な開発
- 直感的なAPIで複雑なクエリも簡単
- マイグレーションで安全なスキーマ管理
- トランザクションでデータの整合性を保証
- Next.jsとの親和性が高い
2026年現在、TypeScriptプロジェクトでのデータベース操作には、Prismaが最も推奨される選択肢です。本記事を参考に、効率的なデータベース開発を実践してください。