ORM 选型

要点

  • Hono 项目常用的 ORM:Drizzle、Prisma、Kysely
  • Drizzle 类型推导好、性能高、轻量,是 TypeScript 项目的首选
  • Prisma 生态完善、开发体验好,但运行时有额外开销
  • Kysely 是类型安全的 SQL 构建器,适合喜欢写 SQL 的开发者
  • ORM 选型需要考虑团队熟悉度、数据库支持、性能要求
  • AI 项目通常需要 JSON 查询和向量操作,ORM 需要支持这些特性

1. 三大 ORM 概览

维度DrizzlePrismaKysely
定位TypeScript ORMTypeScript ORM类型安全 SQL 构建器
Schema 定义TypeScript 代码.prisma 文件TypeScript 类型
类型推导优秀优秀优秀
性能高(接近原生)中等(有运行时开销)高(接近原生)
迁移工具Drizzle KitPrisma Migrate无(需要手写或第三方)
数据库支持PostgreSQL、MySQL、SQLite 等PostgreSQL、MySQL、SQLite 等PostgreSQL、MySQL、SQLite 等
学习曲线中等中等
社区生态成长中成熟较小
包大小

2. Drizzle ORM

Drizzle 是 TypeScript 优先的 ORM,schema 用 TypeScript 代码定义,类型推导完整。

2.1 Schema 定义

// src/schema.ts
import { pgTable, uuid, text, timestamp, jsonb, integer } from 'drizzle-orm/pg-core'
 
export const users = pgTable('users', {
  id: uuid('id').primaryKey().defaultRandom(),
  email: text('email').notNull().unique(),
  name: text('name').notNull(),
  metadata: jsonb('metadata').$type<{ role?: string; tags?: string[] }>(),
  createdAt: timestamp('created_at').defaultNow(),
})
 
export const conversations = pgTable('conversations', {
  id: uuid('id').primaryKey().defaultRandom(),
  userId: uuid('user_id').references(() => users.id).notNull(),
  title: text('title').notNull(),
  model: text('model').notNull(),
  messageCount: integer('message_count').default(0),
  createdAt: timestamp('created_at').defaultNow(),
})

2.2 查询

import { db } from './lib/db'
import { users, conversations } from './schema'
import { eq, and, desc } from 'drizzle-orm'
 
// 查询所有用户
const allUsers = await db.select().from(users)
 
// 条件查询
const user = await db.select().from(users).where(eq(users.email, '[email protected]')).get()
 
// 多条件
const activeUsers = await db
  .select()
  .from(users)
  .where(and(eq(users.name, 'Alice'), desc(users.createdAt)))
 
// 关联查询
const userWithConversations = await db
  .select()
  .from(users)
  .leftJoin(conversations, eq(users.id, conversations.userId))
  .where(eq(users.id, 'user-1'))
 
// 插入
const [newUser] = await db.insert(users).values({
  email: '[email protected]',
  name: 'Bob',
}).returning()
 
// 更新
await db.update(users).set({ name: 'Alice Smith' }).where(eq(users.id, 'user-1'))
 
// 删除
await db.delete(users).where(eq(users.id, 'user-1'))

2.3 迁移

# 生成迁移
pnpm drizzle-kit generate
 
# 应用迁移
pnpm drizzle-kit migrate
 
# 开发环境直接推送
pnpm drizzle-kit push

2.4 优势

  • 类型推导完整:查询结果、插入值、更新值都有类型提示
  • 性能高:生成的 SQL 接近手写,运行时开销小
  • 轻量:包大小小,启动快
  • 灵活:支持原生 SQL 片段(sql 模板字符串)
  • 多数据库:PostgreSQL、MySQL、SQLite、Turso 等

2.5 劣势

  • 生态不如 Prisma 成熟:文档、教程、第三方工具较少
  • Studio 功能较弱:Drizzle Studio 不如 Prisma Studio 完善

3. Prisma

Prisma 是最流行的 TypeScript ORM,schema 用独立的 .prisma 文件定义。

3.1 Schema 定义

// prisma/schema.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
  metadata      Json?
  conversations Conversation[]
  createdAt     DateTime       @default(now())
}
 
model Conversation {
  id        String   @id @default(uuid())
  userId    String
  user      User     @relation(fields: [userId], references: [id])
  title     String
  model     String
  messages  Message[]
  createdAt DateTime @default(now())
}
 
model Message {
  id             String       @id @default(uuid())
  conversationId String
  conversation   Conversation @relation(fields: [conversationId], references: [id])
  role           String
  content        String
  tokens         Int
  createdAt      DateTime     @default(now())
}

3.2 查询

import { prisma } from './lib/db'
 
// 查询所有用户
const users = await prisma.user.findMany()
 
// 条件查询
const user = await prisma.user.findUnique({
  where: { email: '[email protected]' },
})
 
// 关联查询
const userWithConversations = await prisma.user.findUnique({
  where: { id: 'user-1' },
  include: { conversations: true },
})
 
// 嵌套查询
const userWithMessages = await prisma.user.findUnique({
  where: { id: 'user-1' },
  include: {
    conversations: {
      include: { messages: true },
    },
  },
})
 
// 插入
const newUser = await prisma.user.create({
  data: {
    email: '[email protected]',
    name: 'Bob',
  },
})
 
// 更新
await prisma.user.update({
  where: { id: 'user-1' },
  data: { name: 'Alice Smith' },
})
 
// 删除
await prisma.user.delete({
  where: { id: 'user-1' },
})

3.3 迁移

# 生成并应用迁移
pnpx prisma migrate dev --name init
 
# 生产环境应用迁移
pnpx prisma migrate deploy

3.4 优势

  • 开发体验好:Prisma Studio 可视化查看数据、Prisma Client 类型推导完整
  • 生态成熟:文档完善、教程多、社区活跃
  • 关系建模清晰:schema 里直接定义关系,查询时 include 关联数据
  • 迁移工具完善:Prisma Migrate 稳定可靠

3.5 劣势

  • 运行时开销:Prisma Client 有额外的运行时开销(查询引擎)
  • 包大小大:Prisma Client 体积较大
  • Schema 与代码分离:需要在 .prisma 文件和 TypeScript 之间切换
  • 向量支持有限:pgvector 支持不如 Drizzle 灵活

4. Kysely

Kysely 是类型安全的 SQL 构建器,不是完整的 ORM。适合喜欢写 SQL 的开发者。

4.1 类型定义

// src/database/types.ts
import { Generated, ColumnType } from 'kysely'
 
export interface Database {
  users: UsersTable
  conversations: ConversationsTable
}
 
export interface UsersTable {
  id: Generated<string>
  email: string
  name: string
  metadata: Json | null
  created_at: ColumnType<Date, Date | undefined, Date | undefined>
}
 
export interface ConversationsTable {
  id: Generated<string>
  user_id: string
  title: string
  model: string
  message_count: number
  created_at: ColumnType<Date, Date | undefined, Date | undefined>
}
 
type Json = Value<Record<string, unknown> | Record<string, unknown>[] | string | number | boolean | null>

4.2 查询

import { Kysely, PostgresDialect } from 'kysely'
import { Pool } from 'pg'
import { Database } from './database/types'
 
const db = new Kysely<Database>({
  dialect: new PostgresDialect({
    pool: new Pool({ connectionString: process.env.DATABASE_URL }),
  }),
})
 
// 查询
const users = await db.selectFrom('users').selectAll().execute()
 
// 条件查询
const user = await db
  .selectFrom('users')
  .selectAll()
  .where('email', '=', '[email protected]')
  .executeTakeFirst()
 
// 关联查询
const userWithConversations = await db
  .selectFrom('users')
  .innerJoin('conversations', 'users.id', 'conversations.user_id')
  .selectAll()
  .where('users.id', '=', 'user-1')
  .execute()
 
// 插入
await db.insertInto('users').values({
  email: '[email protected]',
  name: 'Bob',
}).execute()

4.3 优势

  • 接近原生 SQL:生成的 SQL 可读性高,容易调试
  • 性能高:没有运行时开销
  • 类型安全:查询结果、条件值都有类型推导
  • 灵活:可以用 sql 模板字符串写原生 SQL

4.4 劣势

  • 不是完整 ORM:没有迁移工具、没有关系建模
  • 学习曲线:需要了解 SQL 和 Kysely API
  • 代码量大:复杂查询需要写更多代码

5. 选型决策

是否需要完整的 ORM(迁移、关系建模)?
├── 是 → 团队偏好?
│        ├── TypeScript 优先、轻量 → Drizzle
│        └── 开发体验优先、生态完善 → Prisma
└── 否 → 是否喜欢写 SQL?
         ├── 是 → Kysely
         └── 否 → Drizzle(更简单的 API)

5.1 选 Drizzle 的场景

  • 追求性能和轻量
  • 团队熟悉 TypeScript,喜欢代码即 schema
  • 需要 pgvector 或其他扩展的灵活支持
  • 项目需要多数据库支持(包括 Edge 环境)

5.2 选 Prisma 的场景

  • 团队 ORM 经验少,需要完善的文档和工具
  • 需要 Prisma Studio 可视化查看数据
  • 项目以关系建模为主,不需要复杂的扩展
  • 团队熟悉 Prisma

5.3 选 Kysely 的场景

  • 团队 SQL 经验丰富,喜欢控制生成的 SQL
  • 项目有复杂的查询需求
  • 不需要迁移工具(用其他方式管理 schema)

6. AI 项目的 ORM 选型

AI 项目通常需要:

  1. JSON 查询:存储 prompt 元数据、模型配置
  2. 向量操作:embedding 存储和相似度检索
  3. 高性能:AI API 调用慢,数据库查询不能成为瓶颈
  4. Edge 兼容:可能部署在 Cloudflare Workers

基于这些需求:

  • Drizzle:推荐。支持 pgvector、JSON 查询灵活、Edge 兼容好
  • Prisma:可以。pgvector 支持有限,但基本 CRUD 没问题
  • Kysely:可以。需要自己处理迁移和向量操作

7. 混合使用

有些项目会混合使用 ORM 和原生 SQL:

// 简单 CRUD 用 ORM
const users = await db.select().from(users).where(eq(users.id, id))
 
// 复杂查询用原生 SQL
import { sql } from 'drizzle-orm'
 
const results = await db.execute(sql`
  SELECT u.*, COUNT(c.id) as conversation_count
  FROM users u
  LEFT JOIN conversations c ON u.id = c.user_id
  GROUP BY u.id
  ORDER BY conversation_count DESC
  LIMIT 10
`)

Drizzle 和 Kysely 都支持原生 SQL 片段,Prisma 也支持 $queryRaw

总结

Hono 项目的 ORM 选型主要在 Drizzle、Prisma、Kysely 之间。

这一节涉及到的几个判断:

  1. Drizzle:TypeScript 优先、性能高、轻量、pgvector 支持好,适合 AI 项目
  2. Prisma:生态成熟、开发体验好、适合关系建模为主的项目
  3. Kysely:类型安全的 SQL 构建器,适合喜欢写 SQL 的开发者
  4. 选型依据:团队熟悉度、性能要求、数据库支持、扩展需求

AI 项目推荐 Drizzle——类型推导好、pgvector 支持灵活、Edge 兼容好。如果团队熟悉 Prisma,用 Prisma 也可以。

下一篇看 Prisma 实践——深入 Prisma 的 schema 定义、查询 API、迁移管理。