AI

OSSのTypeScript ORM比較:Drizzle vs Prisma vs TypeORM で型安全なDB操作を実現する

オープンソースラボ編集部2026年6月14日

OSSのTypeScript ORM比較:Drizzle vs Prisma vs TypeORM で型安全なDB操作を実現する

Laravel Eloquent(PHP)・ActiveRecord(Ruby)のJavaScript世界における対応ツールとして、Drizzle ORM(最軽量・SQLに近いTypeScript ORM)・Prisma(最もポピュラーなNode.js ORM)・TypeORM(デコレータベース・JavaのJPAに近い)はOSSのTypeScript/JavaScript ORMです。

ORMが解決する問題

SQLを直接書くとエラーが実行時まで見つからない・型が不安定になります:

  • 型安全なクエリ: テーブルカラム名のtypo→コンパイルエラーで事前検出
  • マイグレーション管理: スキーマ変更をバージョン管理してチームで共有
  • リレーション管理: JOINを型安全なInclude/Joinで表現
  • DB非依存: コードを変えずにPostgreSQL/MySQLを切り替え

主要ツールの概要

Drizzle ORM

2023年にリリースされたTypeScript製の軽量ORMです。GitHubスター25k+。SQLに最も近いTypeScript ORMで最速です。スキーマ定義がTypeScriptコード(pgTable()等)で行われ、生成されたSQLが予測可能です。バンドルサイズが小さくEdge Runtime(Vercel Edge・Cloudflare Workers)で動作し、Drizzle Kitで自動マイグレーション生成が可能です。

// schema.ts - Drizzle スキーマ定義
import { pgTable, text, timestamp, integer, boolean, uuid, varchar } from "drizzle-orm/pg-core";
import { relations } from "drizzle-orm";

export const users = pgTable("users", {
  id: uuid("id").defaultRandom().primaryKey(),
  email: varchar("email", { length: 255 }).notNull().unique(),
  name: text("name"),
  role: text("role", { enum: ["user", "admin", "editor"] }).default("user").notNull(),
  emailVerified: boolean("email_verified").default(false).notNull(),
  createdAt: timestamp("created_at").defaultNow().notNull(),
  updatedAt: timestamp("updated_at").defaultNow().notNull(),
});

export const posts = pgTable("posts", {
  id: uuid("id").defaultRandom().primaryKey(),
  title: text("title").notNull(),
  slug: text("slug").notNull().unique(),
  content: text("content"),
  status: text("status", { enum: ["draft", "published", "archived"] }).default("draft"),
  authorId: uuid("author_id").notNull().references(() => users.id, { onDelete: "cascade" }),
  viewCount: integer("view_count").default(0).notNull(),
  publishedAt: timestamp("published_at"),
  createdAt: timestamp("created_at").defaultNow().notNull(),
});

export const categories = pgTable("categories", {
  id: uuid("id").defaultRandom().primaryKey(),
  name: text("name").notNull(),
  slug: text("slug").notNull().unique(),
});

export const postCategories = pgTable("post_categories", {
  postId: uuid("post_id").notNull().references(() => posts.id),
  categoryId: uuid("category_id").notNull().references(() => categories.id),
});

// リレーション定義
export const usersRelations = relations(users, ({ many }) => ({
  posts: many(posts),
}));

export const postsRelations = relations(posts, ({ one, many }) => ({
  author: one(users, { fields: [posts.authorId], references: [users.id] }),
  postCategories: many(postCategories),
}));
// db.ts - データベース接続
import { drizzle } from "drizzle-orm/postgres-js";
import postgres from "postgres";
import * as schema from "./schema";

const client = postgres(process.env.DATABASE_URL!);
export const db = drizzle(client, { schema });

// lib/posts.ts - クエリ関数
import { db } from "@/lib/db";
import { posts, users, categories, postCategories } from "@/lib/schema";
import { eq, desc, and, count, sql } from "drizzle-orm";

// 公開記事一覧(JOIN・フィルタ・ページネーション)
export async function getPublishedPosts(page = 1, limit = 10) {
  const offset = (page - 1) * limit;
  return db
    .select({
      id: posts.id,
      title: posts.title,
      slug: posts.slug,
      publishedAt: posts.publishedAt,
      authorName: users.name,
    })
    .from(posts)
    .leftJoin(users, eq(posts.authorId, users.id))
    .where(eq(posts.status, "published"))
    .orderBy(desc(posts.publishedAt))
    .limit(limit)
    .offset(offset);
}

// withでリレーションを自動JOINする(Prismaのincludeに相当)
export async function getPostWithRelations(slug: string) {
  return db.query.posts.findFirst({
    where: eq(posts.slug, slug),
    with: {
      author: { columns: { id: true, name: true, email: true } },
      postCategories: {
        with: { category: true },
      },
    },
  });
}

// 集計クエリ
export async function getPostStats() {
  const [result] = await db
    .select({
      total: count(),
      published: count(sql`CASE WHEN ${posts.status} = 'published' THEN 1 END`),
      totalViews: sql<number>`SUM(${posts.viewCount})`,
    })
    .from(posts);
  return result;
}

// インサート(戻り値も型安全)
export async function createPost(data: typeof posts.$inferInsert) {
  const [post] = await db.insert(posts).values(data).returning();
  return post;
}

// アップデート
export async function publishPost(id: string) {
  const [updated] = await db
    .update(posts)
    .set({ status: "published", publishedAt: new Date() })
    .where(eq(posts.id, id))
    .returning();
  return updated;
}
# drizzle-kit でマイグレーション管理
# package.json の scripts に追加
# "db:generate": "drizzle-kit generate",
# "db:migrate": "drizzle-kit migrate",
# "db:push": "drizzle-kit push",  # 開発時(マイグレーションファイル不要)
# "db:studio": "drizzle-kit studio"

# drizzle.config.ts
# import { defineConfig } from "drizzle-kit";
# export default defineConfig({
#   schema: "./src/lib/schema.ts",
#   out: "./migrations",
#   dialect: "postgresql",
#   dbCredentials: { url: process.env.DATABASE_URL! },
# });

# スキーマ変更からマイグレーションファイルを生成
npx drizzle-kit generate

# マイグレーションを実行
npx drizzle-kit migrate

# Drizzle Studio (ブラウザUIでDBを確認)
npx drizzle-kit studio
# WebUI: https://local.drizzle.studio

Prisma

2019年に公開されたTypeScript製のOSSデータベースツールキットです。GitHubスター40k+。Prisma Schema(独自DSL)でスキーマを定義し、Prisma Clientを自動生成します。型安全性が非常に高く、VSCodeでのオートコンプリートが快適。Prisma Migrateでマイグレーション管理、Prisma Studioでデータブラウジングができます。ただしバンドルサイズが大きくEdge Runtimeは限定的です。

// schema.prisma - Prismaスキーマ定義
generator client {
  provider = "prisma-client-js"
}

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

model User {
  id            String    @id @default(uuid())
  email         String    @unique
  name          String?
  role          Role      @default(USER)
  emailVerified Boolean   @default(false)
  posts         Post[]
  createdAt     DateTime  @default(now())
  updatedAt     DateTime  @updatedAt
}

enum Role { USER ADMIN EDITOR }

model Post {
  id          String    @id @default(uuid())
  title       String
  slug        String    @unique
  content     String?
  status      PostStatus @default(DRAFT)
  author      User      @relation(fields: [authorId], references: [id], onDelete: Cascade)
  authorId    String
  categories  Category[] @relation("PostCategories")
  viewCount   Int       @default(0)
  publishedAt DateTime?
  createdAt   DateTime  @default(now())
}

enum PostStatus { DRAFT PUBLISHED ARCHIVED }

model Category {
  id    String @id @default(uuid())
  name  String
  slug  String @unique
  posts Post[] @relation("PostCategories")
}
// lib/prisma.ts - Prisma Clientのシングルトン(Next.jsのHMR対応)
import { PrismaClient } from "@prisma/client";

const globalForPrisma = globalThis as unknown as { prisma: PrismaClient };
export const prisma = globalForPrisma.prisma ?? new PrismaClient({
  log: process.env.NODE_ENV === "development" ? ["query", "warn", "error"] : ["error"],
});
if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;

// 記事一覧(Prismaスタイル)
const posts = await prisma.post.findMany({
  where: { status: "PUBLISHED" },
  include: {
    author: { select: { name: true, email: true } },
    categories: true,
  },
  orderBy: { publishedAt: "desc" },
  take: 10,
  skip: 0,
});

機能比較表

比較項目Drizzle ORMPrismaTypeORM
スキーマ定義TypeScript独自DSLデコレータ
Edge Runtime✅(設計から)限定的
バンドルサイズ34KB数MB数MB
マイグレーションdrizzle-kitprisma-migrate組み込み
Studio UI
GitHub Stars25k+40k+34k+

ORMで取得したデータをAPIとして公開するにはSecurityカテゴリ/categories/securityのAuth.jsで認証を追加します。DBマイグレーションのCI/CDにはDevOpsカテゴリ/categories/devopsのGitHub ActionsでPull Request単位でマイグレーションをチェックします。

FAQ

Q. DizzleとPrismaどちらを新規Next.jsプロジェクトで採用すべきですか?

A. 現在(2026年)の推奨はDrizzle ORMです。理由: ①Edge Runtime(Vercel Edge Functions・Cloudflare Workers)で動く②バンドルサイズが小さい(Vercel/CloudflareのコールドスタートにPrismaは重い)③SQL力が身につく設計(ORMが生成するSQLが予測しやすい)④PrismaのスキーマDSLより純粋なTypeScriptで書ける。Prismaを選ぶ理由: ①チームがDrizzleを知らないがPrismaは知っている②Prismaのより豊富なドキュメント・コミュニティが必要③Mongooseに慣れたチームがPostgresに移行する場合。

Q. DrizzleでN+1クエリ問題を防ぐには?

A. db.query.posts.findMany({ with: { author: true } })のようなwith句(リレーション)を使えばDrizzleが自動的に2つのSELECT(posts→users)に分解してN+1を回避します。もしくはleftJoinを使って1クエリで完結させます。パフォーマンス確認はEXPLAIN ANALYZEをDrizzle Studioのクエリビルダーで実行するのが最も簡単です。

Q. PrismaからDrizzleへの移行は難しいですか?

A. スキーマ変換は比較的簡単: schema.prismaからschema.ts(Drizzle)への変換ツールがコミュニティに存在します。既存のマイグレーションは既にDB反映済みなので、Drizzleのdrizzle-kit introspectで既存DBからスキーマを逆生成できます。クエリはAPIが異なるため書き直しが必要ですが、Copilot/Claudeを使えば機械的な変換が効率化されます。新規ファイルから始めて既存Prismaコードを並行で残しながら徐々に移行するのが安全です。

Q. Next.js App RouterのServer ActionでDrizzleを使う例を教えてください

A. Server Actionはサーバーサイドのみで動くためDrizzleを直接呼べます。

// app/blog/new/actions.ts
"use server";
import { db } from "@/lib/db";
import { posts } from "@/lib/schema";
import { auth } from "@/auth";
import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";

export async function createPostAction(formData: FormData) {
  const session = await auth();
  if (!session?.user) throw new Error("Unauthorized");

  const title = formData.get("title") as string;
  const content = formData.get("content") as string;
  const slug = title.toLowerCase().replace(/\s+/g, "-").replace(/[^\w-]/g, "");

  const [post] = await db.insert(posts).values({
    title, content, slug,
    authorId: session.user.id!,
    status: "draft",
  }).returning({ id: posts.id, slug: posts.slug });

  revalidatePath("/blog");
  redirect(`/blog/${post.slug}`);
}

まとめ

ユースケース推奨ツール
Next.js・Edge Runtime・SQLファーストDrizzle ORM
豊富なドキュメント・Prisma生態系Prisma
Javaライクなデコレータ・엔터프라이즈バックエンドTypeORM

関連外部リソース

他の記事も読む

Let's Build Together

OSS導入、自社だけで悩まない。

ツール選定から構築・運用・AI活用まで、オープンソースラボ運営元のClasslessが伴走します。初回のご相談は無料です。