LabHub

ブログ

TypeScript ORM・クエリビルダー 2026 — Drizzle・Kysely・Prisma・postgres.js 徹底比較(SQL にどれだけ近づけるか)(日本語)

한국어English日本語

プロローグ — 「Prisma か raw SQL か」の時代は終わった

2026 年でも、新規 TypeScript プロジェクトのキックオフで必ず出る質問がある。

「DB アクセス、何使う?」

2022 年なら答えはだいたい二つ。Prisma(一番有名、フル ORM)か raw pg/mysql2(SQL は自分で書く、型は自分の問題)。間に TypeORM や Sequelize がいたが、TypeScript との相性とマイグレーション体験のせいで、新規プロジェクトからは徐々に消えていった。

2026 年は違う。二つの新しい強者が定着した。

ここに Prisma が新カードを二枚追加。

そして全員を揺さぶる別の波。

この記事は 2026 年 5 月時点でこれらを直接比較する。抽象度・マイグレーション・エッジ・バンドル・エスケープハッチ・マルチ DB の 6 軸。そして同じクエリを四つのツールで並べる。


1 章 · 風景 — ツール地図

まず分類する。すべてが同じ位置にあるわけではない。

分類ツール一行要約
フル ORM(Active Record・Data Mapper)Prisma・MikroORM・TypeORM・Sequelizeモデル・関係・フェッチ・キャッシュまで面倒を見る
ヘッドレス ORMDrizzleスキーマはコード、クエリは SQL に 1:1
クエリビルダーKysely・Knexスキーマなしで型安全な SQL 合成
Raw クライアント + 型ヘルパpostgres.js・pg・mysql2 + Zodタグ付きテンプレート、ランタイム検証
リレーショナル DSLEdgeQL(EdgeDB)・SurrealQLDB が独自クエリ言語を持つ

この記事の焦点は太字 4 グループの代表 — Prisma・Drizzle・Kysely・postgres.js。MikroORM・TypeORM は最後の章で短く扱う。

なぜこの 4 つか


2 章 · 抽象度のスペクトラム — どこまで隠すか

データアクセスライブラリは「DB とコードの間にどのくらい厚い層を置くか」の選択だ。左ほど SQL に近く、右ほどオブジェクトに近い。

SQL                                                              オブジェクト
 |                                                                       |
postgres.js   --   Kysely   --   Drizzle   --   Prisma   --   MikroORM/TypeORM
(raw)             (ビルダー)     (ヘッドレス ORM) (フル ORM)    (Active Record)

抽象度が厚いほど良いか?断じて違う。トレードオフだ。

2026 年の空気は 「SQL に近く、しかし型は安全に」 に寄っている。Drizzle・Kysely・postgres.js の成長がその証拠。ただし Prisma 6 のエンジン書き直しで厚い側も軽くなり、「ORM だから重い」論はもう古い。


3 章 · Drizzle — 「TypeScript で書く SQL」

Drizzle は 2026 年の新規プロジェクトで最もよく選ばれる DB ツールの一つ。コア哲学は 3 つ。

  1. スキーマは TS コードschema.ts でテーブル・カラム・関係を宣言する。真実の源は DB ではなく TS ファイル。
  2. クエリは SQL に 1:1db.select().from(users).where(eq(users.email, '...')) のような形。SQL を知っている人なら、何にコンパイルされるかが一目でわかる。
  3. ヘッドレスで小さい — ランタイム依存がほぼゼロ。Cloudflare Workers・Vercel Edge・Deno Deploy にそのまま乗る。圧縮 7~15KB 程度。

Drizzle のスキーマ

// db/schema.ts
import { pgTable, serial, text, timestamp, integer } from 'drizzle-orm/pg-core'

export const users = pgTable('users', {
  id: serial('id').primaryKey(),
  email: text('email').notNull().unique(),
  name: text('name').notNull(),
  createdAt: timestamp('created_at').defaultNow().notNull(),
})

export const posts = pgTable('posts', {
  id: serial('id').primaryKey(),
  authorId: integer('author_id').notNull().references(() => users.id),
  title: text('title').notNull(),
  body: text('body').notNull(),
})

Drizzle Kit でマイグレーション

# drizzle.config.ts を書いた後
npx drizzle-kit generate   # スキーマ変更から SQL マイグレーション生成
npx drizzle-kit migrate    # 実 DB に適用
npx drizzle-kit studio     # ローカル GUI

Drizzle の強み

Drizzle の弱み


4 章 · Kysely — 「スキーマレスな純粋クエリビルダー」

Kysely は別の道。スキーマを強制しない。DB スキーマは自分の責任(SQL ファイル・Atlas・Sqitch・Liquibase など何でも)で、Kysely はその上に型安全なクエリビルダーだけを提供する。

// db/types.ts — DB スキーマに対応する TS 型
import type { ColumnType, Generated } from 'kysely'

export interface Database {
  user: UserTable
  post: PostTable
}

export interface UserTable {
  id: Generated<number>
  email: string
  name: string
  created_at: ColumnType<Date, string | undefined, never>
}

export interface PostTable {
  id: Generated<number>
  author_id: number
  title: string
  body: string
}

Kysely の核は、上の Database インターフェースをジェネリックで受け取り、すべてのクエリでカラム名と型を自動推論することだ。

kysely-codegen で型を自動生成

スキーマインターフェースを手で書きたくないなら kysely-codegen を使う。ライブ DB をイントロスペクトして上のような TS 型を生成する。

npx kysely-codegen --url postgres://user:pw@localhost/db --out-file db/types.ts

Kysely の強み

Kysely の弱み


5 章 · Prisma — 新エンジンと Prisma Postgres

Prisma は 2024~2025 で大きな変化が二つあった。

5.1 エンジン書き直し — Rust から TS ネイティブ + Go へ

Prisma 5 時代のクエリエンジンは Rust 製の別プロセス(または WASM)。コールドスタートが遅く、エッジランタイムで不自然だった。Prisma 6 以降の段階的書き直しで次が変わった。

5.2 TypedSQL — Prisma の中で raw SQL を型安全に

Prisma の弱点は常に「複雑な SQL を書けない」だった。TypedSQL はその弱点を真正面から叩く。

-- prisma/sql/findActiveUsers.sql
SELECT id, email, name
FROM "User"
WHERE last_seen_at > $1
ORDER BY last_seen_at DESC
LIMIT $2;
import { PrismaClient } from '@prisma/client'
import { findActiveUsers } from '@prisma/client/sql'

const prisma = new PrismaClient()
const since = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000)
const users = await prisma.$queryRawTyped(findActiveUsers(since, 50))
//    users: SQL パラメータから推論された行の配列

.sql ファイルはビルド時に検査され、パラメータと結果の型が自動生成される。SQL は SQL のまま、型安全は保つ

5.3 Prisma Postgres — マネージドサービス

Prisma 社が直接運営する Postgres。差別化は 3 点。

マネージド Postgres 市場は既に Neon・Supabase・PlanetScale Postgres・Vercel Postgres があり競争は激しいが、Prisma ユーザーには最も親和性の高い選択肢になった。

Prisma の強み(2026)

Prisma の弱み(2026)


6 章 · postgres.js・pg・mysql2 — ORM なしで行く

最も薄い道。タグ付きテンプレートと TypeScript の推論・satisfies だけで十分という陣営。

// db/index.ts
import postgres from 'postgres'

export const sql = postgres(process.env.DATABASE_URL!, {
  max: 10,
  idle_timeout: 20,
})

// クエリ — タグ付きテンプレート
type User = { id: number; email: string; name: string }
const users = await sql<User[]>`
  SELECT id, email, name
  FROM users
  WHERE email = ${'foo@example.com'}
  LIMIT 10
`
//   users: User[]

postgres.js はパラメータバインドによる SQL インジェクション防止と、prepared statement のキャッシュを自前で行う。型は手で宣言するか、kysely-codegen 等で生成するか、Zod でランタイム検証する

Zod でランタイム検証

import { z } from 'zod'

const UserRow = z.object({
  id: z.number(),
  email: z.string().email(),
  name: z.string(),
})

const rows = await sql<unknown[]>`SELECT id, email, name FROM users LIMIT 10`
const users = rows.map((r) => UserRow.parse(r))
//   users: z.infer<typeof UserRow>[]

このパターンの利点は DB が実際に返したものを一度検証すること。ORM やビルダーの型はコンパイル時の約束に過ぎず、実 DB が約束を破った(NULL カラム・型ドリフト)かを捕まえはしない。

Raw クライアントの強み

Raw クライアントの弱み


7 章 · 同じクエリを四つのツールで — 「email でユーザを引く」

最もシンプルな例。email でユーザを 1 件引き、最新ポスト 5 件を一緒に返す

7.1 Drizzle

import { db } from './db'
import { users, posts } from './db/schema'
import { eq, desc } from 'drizzle-orm'

async function findUserWithPosts(email: string) {
  const user = await db.query.users.findFirst({
    where: eq(users.email, email),
    with: {
      posts: {
        orderBy: [desc(posts.id)],
        limit: 5,
      },
    },
  })
  return user
  // user: ユーザ行と posts: ポスト行の配列、または undefined
}

db.query.users.findFirst は Drizzle Relations API。内部では json_agg か LATERAL join を伴う 1 本の SQL になる。

7.2 Kysely

import { db } from './db'
import { jsonArrayFrom } from 'kysely/helpers/postgres'

async function findUserWithPosts(email: string) {
  const user = await db
    .selectFrom('user')
    .where('email', '=', email)
    .select((eb) => [
      'id',
      'email',
      'name',
      jsonArrayFrom(
        eb
          .selectFrom('post')
          .whereRef('post.author_id', '=', 'user.id')
          .orderBy('post.id', 'desc')
          .limit(5)
          .select(['post.id', 'post.title', 'post.body'])
      ).as('posts'),
    ])
    .executeTakeFirst()
  return user
}

Kysely はサブセレクトヘルパで nested JSON を組む。jsonArrayFrom は Postgres json_agg に直接コンパイルされる。SQL が頭の中で正確に描ける。

7.3 Prisma

import { prisma } from './db'

async function findUserWithPosts(email: string) {
  const user = await prisma.user.findUnique({
    where: { email },
    include: {
      posts: {
        orderBy: { id: 'desc' },
        take: 5,
      },
    },
  })
  return user
}

最も読みやすい。Prisma は効率的なクエリを内部で選ぶ(以前は N+1 懸念があったが、新エンジンでは join strategy を明示的に選べる)。

7.4 postgres.js(raw)

import { sql } from './db'

type UserRow = {
  id: number
  email: string
  name: string
  posts: Array<{ id: number; title: string; body: string }>
}

async function findUserWithPosts(email: string) {
  const rows = await sql<UserRow[]>`
    SELECT
      u.id, u.email, u.name,
      COALESCE(
        json_agg(json_build_object('id', p.id, 'title', p.title, 'body', p.body))
          FILTER (WHERE p.id IS NOT NULL),
        '[]'::json
      ) AS posts
    FROM users u
    LEFT JOIN LATERAL (
      SELECT id, title, body
      FROM posts
      WHERE author_id = u.id
      ORDER BY id DESC
      LIMIT 5
    ) p ON TRUE
    WHERE u.email = ${email}
    GROUP BY u.id
  `
  return rows[0]
}

長いが、何が走るかが正確にわかる。COALESCE(json_agg(...) FILTER (WHERE ...)) のようなイディオムは raw なら毎回手で書く(ヘルパに切り出せば短くなる)。

比較 — 同じクエリ、違う抽象度

ツール行数読みやすさSQL 可視性型推論
Drizzle短い易しい自動
Kysely高い自動
Prisma最も短い最も易しい低い自動
postgres.js最も長いSQL 依存最も高い手動 / Zod

8 章 · 6 軸比較 — 素早く選ぶために

8.1 抽象度

ツール抽象度
postgres.jsほぼなし
KyselySQL ビルダー
Drizzleスキーマ + SQL ビルダー
Prismaフル ORM
MikroORM・TypeORMActive Record・Data Mapper

8.2 マイグレーション

ツールビルトインマイグレーション自動生成備考
Prismaprisma migrateありシャドウ DB が必要
Drizzledrizzle-kitあり保守的な自動生成
KyselyKysely マイグレーション + 外部ツールなしAtlas・dbmate 推奨
postgres.jsなしなしAtlas・Sqitch・Flyway 等

8.3 エッジランタイム互換性(Cloudflare Workers・Vercel Edge・Bun)

ツールエッジ互換備考
Drizzle非常に良いすべてのアダプタがエッジファースト
Kysely良いドライバ依存、概ね問題なし
postgres.js良いNeon HTTP・Hyperdrive 併用で非常に良い
Prisma良い新エンジン以降安定
MikroORM・TypeORM普通~制限ありデコレータ・reflect-metadata 依存

8.4 バンドルサイズ(圧縮、クライアントコアのみ)

おおよその感覚(リリースで変動)。

ツール圧縮後サイズ(概算)
postgres.js非常に小さい
Kysely小さい
Drizzle小さい
Prisma中(新エンジン以降大幅に縮小)
MikroORM・TypeORM大きい

8.5 SQL エスケープハッチ

ツールエスケープハッチなめらかさ
postgres.js自分で SQL最高
Kyselysql タグ非常に良い
Drizzlesql タグ非常に良い
PrismaTypedSQL・$queryRaw良い(TypedSQL 以降正常)
MikroORM・TypeORMraw query普通

8.6 マルチ DB 対応

ツールPostgresMySQLSQLiteその他
DrizzleありありありD1・LibSQL・Bun・Neon・Planetscale 他
Kyselyありありありアダプタ多数
PrismaありありありSQL Server・MongoDB(限定的)
postgres.jsありなしなしPostgres 専用

9 章 · 実マイグレーション体験談 — Prisma → Drizzle / Kysely

9.1 Prisma → Drizzle(最も多いパス)

この経路を選ぶチームが増えた。理由は二つ。

  1. エッジとバンドル — コールドスタートとレスポンスタイムに差が見える。新 Prisma エンジンで差は縮んだがゼロではない。
  2. SQL エスケープハッチ — sql タグが Prisma の TypedSQL より自然。TypedSQL は別 .sql ファイルが必要で、動的 SQL に制約がある。

典型的なマイグレーション手順。

うまくいかない部分

9.2 Prisma → Kysely(SQL 派チームの経路)

DB スキーマ運用を既に Atlas・Sqitch 等で行うチーム、または SQL を直接書きたいが型は欲しいチームがよく選ぶ。

うまくいかない部分


10 章 · MikroORM・TypeORM — まだ生きているが

MikroORM

エンティティクラスベースの Data Mapper ORM。identity mapunit of work・豊富な関係 API が強み。DDD スタイルのプロジェクトやドメインオブジェクト中心に書くチームが好む。SQL からは最も遠い。

TypeORM

最古参の TS ORM の一つ。デコレータベース Active Record + Data Mapper。メンテナンスが安定せず既知のバグが長く残る傾向があり、新規プロジェクトの第一選択ではない。ただし既に TypeORM で書かれたコードを持っているなら、すぐに剥がせない。

新規プロジェクトで自発的に TypeORM を選ぶケースは 2026 年では稀。既存コードベースは別途コスト・利益分析が必要。


11 章 · 何を、いつ選ぶか — 率直な結論

正解は?いつもの通り 「状況による」。ただしパターンはある。

Drizzle を選ぶとき

Kysely を選ぶとき

Prisma を選ぶとき

postgres.js・pg(raw)を選ぶとき

アンチチェックリスト(避けるべきパターン)


エピローグ — 抽象は道具であって宗教ではない

ORM・クエリビルダー選びは 抽象度とエスケープハッチのなめらかさのトレードオフだ。どちらが優越という話ではない。ただし 2026 年 5 月の風景は明確だ。

決定チェックリスト

アンチパターン

  1. 測定せず ORM 乗り換え — 数値の後で決める。
  2. 一つのコードベースに ORM 二つを並存 — 行き先を決めてマイグレーション日程を組む。
  3. ORM 使いながら $queryRaw が 9 割 — それは重い raw クライアント。
  4. スキーマの真実の源が二つ — DB とコードの真実は一つ。
  5. マイグレーション自動化なしで codegen — 型が嘘をつく。
  6. エッジファースト環境で重いデコレータ ORM — ビルドとランタイム両方にコスト。
  7. 関係が複雑なのに raw — 手で書く SQL が山積み。

次回予告

候補: Drizzle Relations v2 を深く — json_agg・LATERAL join がどう生成されるかPrisma TypedSQL を一ヶ月使ってみた — 何に使い何に使わないかCloudflare Workers + Hyperdrive + postgres.js のフルスタック事例

「SQL を知ればすべてのツールは親切になる。知らなければどのツールも魔法ではない。」

— TypeScript ORM・クエリビルダー 2026、終わり。


参考 / References

コメント

まだコメントはありません。

ログインするとコメントできます