Prisma ORM実践ガイド - 型安全なデータベース操作の極意

中級 | 50分 で読める | 2025.12.02

公式ドキュメント

今回やること

Prismaは、Node.js/TypeScript向けの次世代ORMです。型安全なクエリ、直感的なスキーマ定義、強力なマイグレーション機能を備え、モダンなアプリケーション開発に最適です。本記事では、実践的なPrismaの活用方法を解説します。

最初から本番設計を全部覚えるのではなく、スキーマ定義、マイグレーション、基本クエリ、運用時の確認ポイントを順番に押さえることが重要です。

Prismaの基本構成

flowchart TB
 App["Your Application<br/>(TypeScript)"]
 Client["Prisma Client<br/>(Generated Type-Safe API)"]
 Engine["Prisma Engine<br/>(Query Engine + Rust)"]
 DB["Database<br/>(PostgreSQL, MySQL, SQLite, etc.)"]

 App --> Client --> Engine --> DB

セットアップ

1. インストールと初期化

# Prisma CLIとクライアントのインストール
npm install prisma --save-dev
npm install @prisma/client

# Prismaの初期化(PostgreSQLの場合)
npx prisma init --datasource-provider postgresql

2. 環境変数の設定

# .env
DATABASE_URL="postgresql://user:YOUR_DB_PASSWORD@localhost:5432/mydb?schema=public"

# 本番環境用(接続プール付き)
DATABASE_URL="postgresql://user:YOUR_DB_PASSWORD@db.example.com:5432/mydb?schema=public&connection_limit=10&pool_timeout=30"

スキーマ設計

ポイント: Prismaスキーマはモデル定義・リレーション・インデックスを一つのファイルで管理できます。@@mapでテーブル名をカスタマイズし、@@indexで検索パフォーマンスを向上させましょう

基本的なスキーマ定義

// prisma/schema.prisma

generator client {
 provider = "prisma-client-js"
 previewFeatures = ["fullTextSearch", "filteredRelationCount"]
}

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

// ユーザーモデル
model User {
 id String @id @default(cuid())
 email String @unique
 name String?
 password String
 role Role @default(USER)
 createdAt DateTime @default(now())
 updatedAt DateTime @updatedAt

 // リレーション
 posts Post[]
 comments Comment[]
 profile Profile?
 sessions Session[]

 @@index([email])
 @@map("users") // テーブル名をカスタマイズ
}

model Profile {
 id String @id @default(cuid())
 bio String?
 avatar String?
 website String?
 userId String @unique
 user User @relation(fields: [userId], references: [id], onDelete: Cascade)

 @@map("profiles")
}

model Post {
 id String @id @default(cuid())
 title String
 slug String @unique
 content String?
 excerpt String?
 published Boolean @default(false)
 publishedAt DateTime?
 viewCount Int @default(0)
 createdAt DateTime @default(now())
 updatedAt DateTime @updatedAt

 // リレーション
 authorId String
 author User @relation(fields: [authorId], references: [id])
 categories Category[]
 tags Tag[]
 comments Comment[]

 @@index([authorId])
 @@index([slug])
 @@index([published, publishedAt])
 @@map("posts")
}

model Category {
 id String @id @default(cuid())
 name String @unique
 slug String @unique
 posts Post[]

 @@map("categories")
}

model Tag {
 id String @id @default(cuid())
 name String @unique
 posts Post[]

 @@map("tags")
}

model Comment {
 id String @id @default(cuid())
 content String
 createdAt DateTime @default(now())
 updatedAt DateTime @updatedAt

 // リレーション
 postId String
 post Post @relation(fields: [postId], references: [id], onDelete: Cascade)
 authorId String
 author User @relation(fields: [authorId], references: [id])

 // 自己参照リレーション(返信機能)
 parentId String?
 parent Comment? @relation("CommentReplies", fields: [parentId], references: [id])
 replies Comment[] @relation("CommentReplies")

 @@index([postId])
 @@index([authorId])
 @@map("comments")
}

model Session {
 id String @id @default(cuid())
 sessionToken String @unique
 expires DateTime
 userId String
 user User @relation(fields: [userId], references: [id], onDelete: Cascade)

 @@map("sessions")
}

// 列挙型
enum Role {
 USER
 ADMIN
 MODERATOR
}

高度なスキーマ機能

// 複合ユニークキー
model OrderItem {
 orderId String
 productId String
 quantity Int
 price Decimal @db.Decimal(10, 2)

 order Order @relation(fields: [orderId], references: [id])
 product Product @relation(fields: [productId], references: [id])

 @@id([orderId, productId]) // 複合主キー
 @@map("order_items")
}

// JSONフィールドと配列
model Product {
 id String @id @default(cuid())
 name String
 description String?
 price Decimal @db.Decimal(10, 2)
 metadata Json? // JSONフィールド
 images String[] // 配列(PostgreSQL)

 orderItems OrderItem[]

 @@map("products")
}

// フルテキスト検索用インデックス(PostgreSQL)
model Article {
 id String @id @default(cuid())
 title String
 content String

 @@index([title, content], type: Gin) // GINインデックス
 @@map("articles")
}

マイグレーション

注意: prisma migrate resetはデータベースを完全にリセットします。本番環境では絶対に実行しないでください。本番ではprisma migrate deployのみを使用します。

基本的なマイグレーションフロー

# 開発環境:スキーマ変更を検出してマイグレーション作成
npx prisma migrate dev --name init

# マイグレーションのみ作成(適用しない)
npx prisma migrate dev --create-only --name add_user_avatar

# 本番環境:マイグレーション適用
npx prisma migrate deploy

# マイグレーション状態の確認
npx prisma migrate status

# データベースをリセット(開発環境のみ)
npx prisma migrate reset

マイグレーションのカスタマイズ

-- prisma/migrations/20250102_add_full_text_search/migration.sql

-- 手動でSQLを追加
CREATE EXTENSION IF NOT EXISTS pg_trgm;

CREATE INDEX posts_title_trgm_idx ON posts USING gin (title gin_trgm_ops);
CREATE INDEX posts_content_trgm_idx ON posts USING gin (content gin_trgm_ops);

-- 検索用の関数を作成
CREATE OR REPLACE FUNCTION search_posts(search_query TEXT)
RETURNS SETOF posts AS $$
 SELECT *
 FROM posts
 WHERE
 title ILIKE '%' || search_query || '%'
 OR content ILIKE '%' || search_query || '%'
 ORDER BY
 CASE
 WHEN title ILIKE search_query THEN 0
 WHEN title ILIKE search_query || '%' THEN 1
 WHEN title ILIKE '%' || search_query || '%' THEN 2
 ELSE 3
 END,
 published_at DESC NULLS LAST;
$$ LANGUAGE SQL STABLE;

Prisma Clientの使用

実践メモ: 開発時のホットリロードでPrisma Clientのインスタンスが増殖するのを防ぐため、グローバルインスタンスパターンを使いましょう。

基本的なCRUD操作

// lib/prisma.ts
import { PrismaClient } from '@prisma/client';

// グローバルインスタンスを使用(開発時のホットリロード対応)
const globalForPrisma = globalThis as unknown as {
 prisma: PrismaClient | undefined;
};

export const prisma = globalForPrisma.prisma ?? new PrismaClient({
 log: process.env.NODE_ENV === 'development'
 ? ['query', 'error', 'warn']
 : ['error'],
});

if (process.env.NODE_ENV !== 'production') {
 globalForPrisma.prisma = prisma;
}
// services/user.service.ts
import { prisma } from '@/lib/prisma';
import { Prisma, User, Role } from '@prisma/client';
import bcrypt from 'bcryptjs';

// 型定義
type UserCreateInput = {
 email: string;
 name?: string;
 password: string;
};

type UserWithProfile = Prisma.UserGetPayload<{
 include: { profile: true };
}>;

// ユーザー作成
async function createUser(data: UserCreateInput): Promise<User> {
 const hashedPassword = await bcrypt.hash(data.password, 12);

 return prisma.user.create({
 data: {
 email: data.email,
 name: data.name,
 password: hashedPassword,
 profile: {
 create: {}, // 空のプロフィールを同時作成
 },
 },
 });
}

// 単一ユーザー取得
async function getUserById(id: string): Promise<UserWithProfile | null> {
 return prisma.user.findUnique({
 where: { id },
 include: {
 profile: true,
 },
 });
}

// メールでユーザー取得
async function getUserByEmail(email: string): Promise<User | null> {
 return prisma.user.findUnique({
 where: { email },
 });
}

// ユーザー一覧取得(ページネーション付き)
async function getUsers(options: {
 page?: number;
 limit?: number;
 role?: Role;
 search?: string;
}): Promise<{ users: User[]; total: number }> {
 const { page = 1, limit = 10, role, search } = options;
 const skip = (page - 1) * limit;

 const where: Prisma.UserWhereInput = {
 ...(role && { role }),
 ...(search && {
 OR: [
 { email: { contains: search, mode: 'insensitive' } },
 { name: { contains: search, mode: 'insensitive' } },
 ],
 }),
 };

 const [users, total] = await Promise.all([
 prisma.user.findMany({
 where,
 skip,
 take: limit,
 orderBy: { createdAt: 'desc' },
 }),
 prisma.user.count({ where }),
 ]);

 return { users, total };
}

// ユーザー更新
async function updateUser(
 id: string,
 data: Prisma.UserUpdateInput
): Promise<User> {
 return prisma.user.update({
 where: { id },
 data,
 });
}

// ユーザー削除(カスケード削除)
async function deleteUser(id: string): Promise<void> {
 await prisma.user.delete({
 where: { id },
 });
}

export const userService = {
 createUser,
 getUserById,
 getUserByEmail,
 getUsers,
 updateUser,
 deleteUser,
};

リレーションを含むクエリ

// services/post.service.ts
import { prisma } from '@/lib/prisma';
import { Prisma, Post } from '@prisma/client';

// 投稿の型定義(リレーション込み)
const postWithRelations = Prisma.validator<Prisma.PostDefaultArgs>()({
 include: {
 author: {
 select: {
 id: true,
 name: true,
 email: true,
 profile: {
 select: { avatar: true },
 },
 },
 },
 categories: true,
 tags: true,
 _count: {
 select: { comments: true },
 },
 },
});

type PostWithRelations = Prisma.PostGetPayload<typeof postWithRelations>;

// 公開済み投稿の取得
async function getPublishedPosts(options: {
 page?: number;
 limit?: number;
 categorySlug?: string;
 tagName?: string;
}): Promise<{ posts: PostWithRelations[]; total: number }> {
 const { page = 1, limit = 10, categorySlug, tagName } = options;

 const where: Prisma.PostWhereInput = {
 published: true,
 publishedAt: { lte: new Date() },
 ...(categorySlug && {
 categories: {
 some: { slug: categorySlug },
 },
 }),
 ...(tagName && {
 tags: {
 some: { name: tagName },
 },
 }),
 };

 const [posts, total] = await Promise.all([
 prisma.post.findMany({
 where,
 ...postWithRelations,
 skip: (page - 1) * limit,
 take: limit,
 orderBy: { publishedAt: 'desc' },
 }),
 prisma.post.count({ where }),
 ]);

 return { posts, total };
}

// スラッグで投稿取得(閲覧数を増加)
async function getPostBySlug(slug: string): Promise<PostWithRelations | null> {
 const post = await prisma.post.update({
 where: { slug, published: true },
 data: {
 viewCount: { increment: 1 },
 },
 ...postWithRelations,
 });

 return post;
}

// 投稿作成
async function createPost(data: {
 title: string;
 content: string;
 authorId: string;
 categoryIds: string[];
 tagNames: string[];
}): Promise<Post> {
 const slug = generateSlug(data.title);

 return prisma.post.create({
 data: {
 title: data.title,
 slug,
 content: data.content,
 excerpt: data.content.slice(0, 200),
 author: { connect: { id: data.authorId } },
 categories: {
 connect: data.categoryIds.map(id => ({ id })),
 },
 tags: {
 connectOrCreate: data.tagNames.map(name => ({
 where: { name },
 create: { name },
 })),
 },
 },
 });
}

// 投稿の公開
async function publishPost(id: string): Promise<Post> {
 return prisma.post.update({
 where: { id },
 data: {
 published: true,
 publishedAt: new Date(),
 },
 });
}

function generateSlug(title: string): string {
 return title
 .toLowerCase()
 .replace(/[^\w\s-]/g, '')
 .replace(/\s+/g, '-')
 .concat('-', Date.now().toString(36));
}

export const postService = {
 getPublishedPosts,
 getPostBySlug,
 createPost,
 publishPost,
};

トランザクション

// services/order.service.ts
import { prisma } from '@/lib/prisma';
import { Prisma } from '@prisma/client';

interface OrderItem {
 productId: string;
 quantity: number;
}

// 対話型トランザクション
async function createOrder(
 userId: string,
 items: OrderItem[]
): Promise<Order> {
 return prisma.$transaction(async (tx) => {
 // 1. 商品情報と在庫を確認
 const products = await tx.product.findMany({
 where: {
 id: { in: items.map(item => item.productId) },
 },
 });

 // 在庫チェック
 for (const item of items) {
 const product = products.find(p => p.id === item.productId);
 if (!product) {
 throw new Error(`Product not found: ${item.productId}`);
 }
 if (product.stock < item.quantity) {
 throw new Error(`Insufficient stock for: ${product.name}`);
 }
 }

 // 2. 注文を作成
 const total = items.reduce((sum, item) => {
 const product = products.find(p => p.id === item.productId)!;
 return sum + product.price.toNumber() * item.quantity;
 }, 0);

 const order = await tx.order.create({
 data: {
 userId,
 status: 'PENDING',
 total: new Prisma.Decimal(total),
 items: {
 create: items.map(item => {
 const product = products.find(p => p.id === item.productId)!;
 return {
 productId: item.productId,
 quantity: item.quantity,
 price: product.price,
 };
 }),
 },
 },
 include: {
 items: {
 include: { product: true },
 },
 },
 });

 // 3. 在庫を減少
 await Promise.all(
 items.map(item =>
 tx.product.update({
 where: { id: item.productId },
 data: {
 stock: { decrement: item.quantity },
 },
 })
 )
 );

 return order;
 }, {
 maxWait: 5000, // 最大待機時間
 timeout: 10000, // タイムアウト
 isolationLevel: Prisma.TransactionIsolationLevel.Serializable,
 });
}

// バッチトランザクション(複数操作を一括実行)
async function transferPoints(
 fromUserId: string,
 toUserId: string,
 amount: number
): Promise<void> {
 await prisma.$transaction([
 prisma.user.update({
 where: { id: fromUserId },
 data: { points: { decrement: amount } },
 }),
 prisma.user.update({
 where: { id: toUserId },
 data: { points: { increment: amount } },
 }),
 prisma.pointTransaction.create({
 data: {
 fromUserId,
 toUserId,
 amount,
 type: 'TRANSFER',
 },
 }),
 ]);
}

高度なクエリ

// 集計クエリ
async function getPostStats(authorId: string) {
 return prisma.post.aggregate({
 where: { authorId },
 _count: true,
 _sum: { viewCount: true },
 _avg: { viewCount: true },
 _max: { viewCount: true },
 });
}

// グループ化
async function getPostCountByCategory() {
 return prisma.post.groupBy({
 by: ['published'],
 where: { publishedAt: { not: null } },
 _count: { id: true },
 _avg: { viewCount: true },
 orderBy: { _count: { id: 'desc' } },
 });
}

// 生SQLクエリ
async function searchPosts(query: string) {
 return prisma.$queryRaw<Post[]>`
 SELECT p.*,
 ts_rank(to_tsvector('japanese', title || ' ' || content),
 plainto_tsquery('japanese', ${query})) as rank
 FROM posts p
 WHERE to_tsvector('japanese', title || ' ' || content)
 @@ plainto_tsquery('japanese', ${query})
 ORDER BY rank DESC
 LIMIT 20
 `;
}

// フルテキスト検索(プレビュー機能)
async function searchPostsFullText(query: string) {
 return prisma.post.findMany({
 where: {
 OR: [
 { title: { search: query } },
 { content: { search: query } },
 ],
 },
 orderBy: {
 _relevance: {
 fields: ['title', 'content'],
 search: query,
 sort: 'desc',
 },
 },
 });
}

パフォーマンス最適化

注意: N+1問題はORM利用時の最も一般的なパフォーマンス問題です。ループ内でクエリを発行していないか常にチェックしましょう。

N+1問題の解決

// ❌ N+1問題が発生するコード
async function getPostsWithAuthorsBad() {
 const posts = await prisma.post.findMany();

 // 各投稿ごとにクエリが発生(N回)
 for (const post of posts) {
 const author = await prisma.user.findUnique({
 where: { id: post.authorId },
 });
 console.log(post.title, author?.name);
 }
}

// ✅ includeでリレーションを一括取得
async function getPostsWithAuthorsGood() {
 const posts = await prisma.post.findMany({
 include: {
 author: {
 select: { name: true },
 },
 },
 });

 for (const post of posts) {
 console.log(post.title, post.author.name);
 }
}

// ✅ selectで必要なフィールドのみ取得
async function getPostTitlesWithAuthors() {
 return prisma.post.findMany({
 select: {
 id: true,
 title: true,
 author: {
 select: { name: true },
 },
 },
 });
}

接続プールの設定

// 本番環境向け設定
const prisma = new PrismaClient({
 datasources: {
 db: {
 url: process.env.DATABASE_URL,
 },
 },
 // 接続プール設定はURLパラメータで指定
 // ?connection_limit=10&pool_timeout=30
});

// サーバーレス環境向け(Prisma Accelerate)
// schema.prismaで設定
// datasource db {
// provider = "postgresql"
// url = env("DATABASE_URL")
// directUrl = env("DIRECT_URL") // マイグレーション用
// }

クエリログとデバッグ

// 詳細なクエリログ
const prisma = new PrismaClient({
 log: [
 { emit: 'event', level: 'query' },
 { emit: 'stdout', level: 'error' },
 { emit: 'stdout', level: 'warn' },
 ],
});

// クエリイベントのリスニング
prisma.$on('query', (e) => {
 console.log('Query:', e.query);
 console.log('Params:', e.params);
 console.log('Duration:', e.duration, 'ms');
});

// スロークエリの検出
prisma.$on('query', (e) => {
 if (e.duration > 100) {
 console.warn(`Slow query (${e.duration}ms):`, e.query);
 }
});

テスト戦略

テスト用のセットアップ

// tests/helpers/prisma.ts
import { PrismaClient } from '@prisma/client';
import { execSync } from 'child_process';

const TEST_DATABASE_URL = process.env.TEST_DATABASE_URL ||
 'postgresql://test:YOUR_TEST_DB_PASSWORD@localhost:5432/test_db';

export function createTestPrismaClient(): PrismaClient {
 return new PrismaClient({
 datasources: {
 db: { url: TEST_DATABASE_URL },
 },
 });
}

export async function resetDatabase(prisma: PrismaClient): Promise<void> {
 // テーブルを削除順序を考慮してクリア
 const tablenames = await prisma.$queryRaw<
 Array<{ tablename: string }>
 >`SELECT tablename FROM pg_tables WHERE schemaname='public'`;

 for (const { tablename } of tablenames) {
 if (tablename !== '_prisma_migrations') {
 await prisma.$executeRawUnsafe(
 `TRUNCATE TABLE "public"."${tablename}" CASCADE;`
 );
 }
 }
}

// テストファクトリー
export function createUserFactory(prisma: PrismaClient) {
 return {
 create: async (overrides: Partial<Prisma.UserCreateInput> = {}) => {
 return prisma.user.create({
 data: {
 email: `test-${Date.now()}@example.com`,
 name: 'Test User',
 password: 'hashed_password',
 ...overrides,
 },
 });
 },
 };
}

統合テスト

// tests/integration/user.test.ts
import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest';
import { PrismaClient } from '@prisma/client';
import { createTestPrismaClient, resetDatabase, createUserFactory } from '../helpers/prisma';
import { userService } from '@/services/user.service';

describe('UserService', () => {
 let prisma: PrismaClient;
 let userFactory: ReturnType<typeof createUserFactory>;

 beforeAll(async () => {
 prisma = createTestPrismaClient();
 userFactory = createUserFactory(prisma);
 });

 beforeEach(async () => {
 await resetDatabase(prisma);
 });

 afterAll(async () => {
 await prisma.$disconnect();
 });

 describe('createUser', () => {
 it('should create a user with profile', async () => {
 const user = await userService.createUser({
 email: 'new@example.com',
 name: 'New User',
 password: 'password123',
 });

 expect(user.email).toBe('new@example.com');
 expect(user.name).toBe('New User');

 const userWithProfile = await prisma.user.findUnique({
 where: { id: user.id },
 include: { profile: true },
 });

 expect(userWithProfile?.profile).toBeDefined();
 });

 it('should throw on duplicate email', async () => {
 await userFactory.create({ email: 'exists@example.com' });

 await expect(
 userService.createUser({
 email: 'exists@example.com',
 password: 'password123',
 })
 ).rejects.toThrow();
 });
 });

 describe('getUsers', () => {
 it('should return paginated users', async () => {
 // 15人のユーザーを作成
 await Promise.all(
 Array.from({ length: 15 }, (_, i) =>
 userFactory.create({ email: `user${i}@example.com` })
 )
 );

 const result = await userService.getUsers({ page: 1, limit: 10 });

 expect(result.users).toHaveLength(10);
 expect(result.total).toBe(15);
 });
 });
});

本番運用のベストプラクティス

エラーハンドリングは本番運用で不可欠です。Prismaのエラーコードを適切に分類して、ユーザーにわかりやすいメッセージを返しましょう。

エラーハンドリング

// lib/prisma-errors.ts
import { Prisma } from '@prisma/client';

export class DatabaseError extends Error {
 constructor(
 message: string,
 public code: string,
 public originalError?: Error
 ) {
 super(message);
 this.name = 'DatabaseError';
 }
}

export function handlePrismaError(error: unknown): never {
 if (error instanceof Prisma.PrismaClientKnownRequestError) {
 switch (error.code) {
 case 'P2002':
 throw new DatabaseError(
 `Unique constraint violation on ${error.meta?.target}`,
 'UNIQUE_CONSTRAINT',
 error
 );
 case 'P2025':
 throw new DatabaseError(
 'Record not found',
 'NOT_FOUND',
 error
 );
 case 'P2003':
 throw new DatabaseError(
 'Foreign key constraint failed',
 'FOREIGN_KEY_CONSTRAINT',
 error
 );
 default:
 throw new DatabaseError(
 `Database error: ${error.message}`,
 error.code,
 error
 );
 }
 }

 if (error instanceof Prisma.PrismaClientValidationError) {
 throw new DatabaseError(
 'Invalid data provided',
 'VALIDATION_ERROR',
 error
 );
 }

 throw error;
}

// 使用例
async function safeCreateUser(data: UserCreateInput) {
 try {
 return await userService.createUser(data);
 } catch (error) {
 handlePrismaError(error);
 }
}

ヘルスチェックと監視

// lib/health.ts
import { prisma } from './prisma';

export async function checkDatabaseHealth(): Promise<{
 healthy: boolean;
 latency: number;
 error?: string;
}> {
 const start = Date.now();

 try {
 await prisma.$queryRaw`SELECT 1`;
 return {
 healthy: true,
 latency: Date.now() - start,
 };
 } catch (error) {
 return {
 healthy: false,
 latency: Date.now() - start,
 error: error instanceof Error ? error.message : 'Unknown error',
 };
 }
}

// メトリクス収集
export async function getDatabaseMetrics() {
 const [
 userCount,
 postCount,
 activeConnections,
 ] = await Promise.all([
 prisma.user.count(),
 prisma.post.count(),
 prisma.$queryRaw<[{ count: bigint }]>`
 SELECT count(*) FROM pg_stat_activity
 WHERE datname = current_database()
 `.then(r => Number(r[0].count)),
 ]);

 return {
 userCount,
 postCount,
 activeConnections,
 timestamp: new Date().toISOString(),
 };
}

動作確認

Prismaを導入したら、次の順番で確認します。

確認項目コマンド例見ること
スキーマの妥当性npx prisma validateschema.prismaに構文エラーがないか
型生成npx prisma generatePrisma Clientが生成できるか
マイグレーションnpx prisma migrate devDBに変更が反映されるか
DB閲覧npx prisma studio作成したテーブルとデータを確認できるか

エラーが出た場合は、まず DATABASE_URL、DBサーバーの起動状態、マイグレーション履歴を確認します。

練習

学習用プロジェクトで次を実装してください。

  1. UserPost モデルを作る
  2. User から複数の Post を参照できるリレーションを定義する
  3. npx prisma migrate dev でテーブルを作る
  4. createfindMany を使ってデータを登録・取得する
  5. include を使って投稿者情報も一緒に取得する

練習では本番DBを使わず、SQLiteやローカルPostgreSQLなど失敗しても戻せる環境で試してください。

次のステップ

SQL寄りのORMと比較したい場合は Drizzle ORM実践ガイド を確認してください。データベースの基礎から整理したい場合は PostgreSQL入門SQL SELECT練習 が先に役立ちます。

まとめ

Prismaは、型安全性と開発者体験を重視したモダンなORMです。

Prismaの強み

特徴メリット
型安全なクエリコンパイル時のエラー検出
直感的なスキーマPrisma Schemaで一元管理
自動マイグレーション安全なスキーマ変更
リレーション処理N+1問題の回避が容易

採用時の注意点

  • 複雑な生SQLが必要な場合は$queryRawを活用
  • 大量データの一括処理はcreateMany/updateManyを使用
  • サーバーレス環境では接続プールに注意

Prismaを活用することで、型安全で保守性の高いデータベース操作が実現できます。

参考リンク

参考リソース

← 一覧に戻る
PR
PR
PR
PR