Kysely — TypeScript SQL 查询构建器
是什么
Kysely 是一个 TypeScript 优先的 SQL 查询构建器,核心承诺一句话:“我就想写 SQL,但要类型自动检查”。
日常类比:
- prisma / drizzle 这种 ORM 像给你做好的菜——你说”来一盘宫保鸡丁”,厨房自己处理切配、爆炒、装盘。你不知道菜怎么做的,但拿到能吃。
- Kysely 像一台改装后的电锅——你还是要自己下米、加水、按开关(也就是写 SQL),但每加一种食材,电锅自己核对:“这是大米还是面条?水位够吗?“——食材类型不对它当场报警。
你在 IDE 里写:
db.selectFrom('users').select(['id', 'email']).where('id', '=', 1).executeTakeFirst()TypeScript 编译期就告诉你:返回类型是 { id: number; email: string } | undefined——你拼错列名、where 类型不对,编译期立刻爆红。
为什么重要
理解 Kysely 的位置,要看 TypeScript ORM 光谱上的几个点:
- 比 prisma 简单——Prisma 要写
.prismaDSL 文件、跑prisma generate生成代码、引入 Rust binary 引擎。Kysely 没有这些,纯 TypeScript,import就能用。 - 比 drizzle 更贴近原生 SQL——Drizzle 有 schema-as-code 和 relation API,往 ORM 方向再走半步。Kysely 的链式调用 1:1 对应 SQL 关键字(
selectFrom/innerJoin/where/groupBy),写 builder 几乎等于写 SQL。 - 0 dependencies、包体积超小(约 30KB)——Edge runtime(Cloudflare Workers / Vercel Edge)友好,冷启动快。
- 类型自动推导——你写
select(['id', 'name']),TypeScript 自己推出 row 类型是{ id: number; name: string },不用手动声明。 - 多 dialect 支持——PostgreSQL / MySQL / SQLite / MS SQL Server,加上社区维护的 PlanetScale / Neon / Cloudflare D1 / Bun SQLite。换 dialect 等于换一个 adapter。
核心要点
Kysely 的世界观可以拆成 三块拼图:
-
TypeScript Database 类型(你的 schema 长什么样)
你手写一份
Databaseinterface 告诉 Kysely “我有哪些表、每张表有哪些列、列是什么类型”。也可以用第三方工具 kysely-codegen 从已有 db 反向生成。 -
Query builder 链(你怎么写 query)
链式调用:
db.selectFrom().select().where().orderBy().execute()。每一步返回新 builder(不可变),类型在链上累积。 -
编译时类型推导(IDE 帮你 catch 错误)
TypeScript 用模板字面量类型 + 条件类型,把
'users.email'字符串拆成表名 + 列名,再去Database['users']['email']查类型。select选了哪些字段,就决定了最终 row 长什么样。
整套机制不依赖 codegen、不依赖 runtime engine、不依赖 DSL——纯类型系统就能完成。
实践案例
案例 1:定义 Database 类型
import { Kysely, Generated, ColumnType, PostgresDialect } from 'kysely'import { Pool } from 'pg'
interface Database { users: { id: Generated<number> // 自增主键,insert 不用传 email: string name: string | null created_at: ColumnType<Date, string | undefined, never> // 三栏:select 出来 / insert 时 / update 时 各自的类型 } posts: { id: Generated<number> title: string author_id: number }}
const db = new Kysely<Database>({ dialect: new PostgresDialect({ pool: new Pool({ connectionString: process.env.DATABASE_URL }), }),})Generated<T> 和 ColumnType<S, I, U> 是 Kysely 的两个标记类型——告诉编译器”这一列在 select / insert / update 三种场景下类型不同”。
案例 2:select + where(自动推 row 类型)
const user = await db .selectFrom('users') .select(['id', 'email']) .where('id', '=', 1) .executeTakeFirst()
// user 的类型自动推出来:{ id: number; email: string } | undefined注意几件事:
.select(['id', 'email'])只选两列,最终 row 就只有这两个字段——不是把整张表都返回。.where('id', '=', 1)的1必须是 number——你传'1'字符串,编译期立刻报错。.executeTakeFirst()表示”取第一条或 undefined”;.executeTakeFirstOrThrow()取不到就抛异常;.execute()返回数组。
案例 3:Insert + Returning
const newUser = await db .insertInto('users') .values({ email: 'a@b.com', name: 'Alice' }) .returning('id') .executeTakeFirstOrThrow()
// newUser 的类型:{ id: number }.returning('id') 是 PostgreSQL / SQLite 的特性(MySQL 不支持,要用 last insert id)——告诉数据库”插入后顺便把 id 还给我”。Kysely 把这种 dialect 差异封装在 adapter 层,但你写 query 时还是要知道哪些 dialect 支持。
踩过的坑
-
必须自己保持 Database 类型与真实 db 同步——不像 Prisma 跑
prisma generate自动对齐,Kysely 信任你写的 interface。同事改了 db 但没改 interface?编译期看不出来,运行时拿到email: number(实际 db 改成 int 了)但 TS 说是string——类型谎言。补救:用kysely-codegen定期从 db 反向生成,或者在 API 边界用zod做 runtime 校验。 -
大型查询编译慢——
selectFrom().innerJoin().innerJoin().select([20 列])...这种链如果跨 5+ 表、20+ 列,TypeScript 类型推导要展开几十层条件类型,tsc变慢、IDE 卡顿、错误信息巨大。这是模板类型路线的固有代价(Drizzle 同样有)。缓解办法:把大 query 拆成小函数,或在中间用as类型断言切断推导链。 -
不带 migration 工具——Kysely 不内置 schema 演化方案。你要自己装第三方包:
kysely-migrator/kysely-migration-cli/ 或直接用umzug自己写。Drizzle 的drizzle-kit和 Prisma 的prisma migrate是开箱即用的,Kysely 不是——这是它”做最窄一件事”哲学的代价。 -
与 Prisma / Drizzle 互不兼容——三个项目的 schema 表达方式完全不同(DSL / TS object / TS interface),从 Prisma 迁到 Kysely 要重写全部 query 代码。如果项目深度用了 Prisma 的
include: { posts: true }嵌套结果,迁到 Kysely 还要把”自动嵌套”改成”手动 reduce”,工作量很大。
适用 vs 不适用场景
适用:
- 已有 db / legacy schema,想上 type-safe 但不想引入 Prisma 那一套
- Edge runtime(Cloudflare Workers / Vercel Edge)冷启动敏感
- 团队 SQL 功底强,喜欢”看见每一条 SQL”
- 性能敏感场景(30KB bundle vs Prisma 10MB)
不适用:
- 从零起步、想要 schema + migration + 关系一站式 → 选 Drizzle
- 团队 SQL 弱、想要 DSL 直观 API → 选 Prisma
- 项目里要写大量复杂 join / CTE / window function 嵌套 → builder 链会变难读,混合 raw SQL 反而清晰
- MongoDB → Kysely 只做 SQL 数据库
学到什么
- builder 派 vs ORM 派——Kysely 选了”builder 1:1 SQL”路线,比 ORM 透明、比 raw SQL 安全,是中间地带
- TypeScript 模板类型能做的事比想象多——把字符串字面量当编程语言用,编译期就能完成 SQL 类型推导
- “做最窄的一件事”是有代价的——Kysely 不做 schema / migration / relation,市场份额被一站式的 Drizzle / Prisma 抢走,但保留了”轻、快、透明”的核心优势
- 类型安全不等于运行时安全——Kysely 只在编译期校验,db 真实状态偏离 interface 时 TS 不会救你
延伸阅读
- 官网入门:Kysely 官方文档(30 分钟从安装到第一个 query)
- 类型推导原理:Type-safety 章节(看 Generated / ColumnType / Selection 怎么工作)
- 反向生成 schema:kysely-codegen(从已有 db 生成 Database interface)
关联
- prisma —— ORM 派代表,Kysely 的”远邻”,对比看出 builder vs DSL 两种哲学
- drizzle —— 同样 TypeScript-first builder,但带 schema-as-code 和 relation API,Kysely 的”近邻竞争对手”
- typescript —— Kysely 的整套类型推导依赖 TS 4.x 的模板字面量类型
- postgresql —— Kysely 主推的 dialect,returning / advisory lock 等特性都是 PG 起家