tl-kysely-patterns

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese
<!-- Copyright (c) 2026 Todd Levy. Licensed under MIT. SPDX-License-Identifier: MIT -->
<!-- Copyright (c) 2026 Todd Levy. Licensed under MIT. SPDX-License-Identifier: MIT -->

Kysely: Type-Safe SQL Patterns

Kysely:类型安全的SQL模式

Kysely (pronounced "Key-Seh-Lee") is a type-safe TypeScript SQL query builder. It generates plain SQL with zero runtime ORM overhead. Every query is validated at compile time with full autocompletion.
Kysely is not an ORM -- no relations, no lazy loading, no magic. Just SQL with types.
Kysely(发音为"Key-Seh-Lee")是一个类型安全的TypeScript SQL查询构建器。它生成纯SQL,无任何ORM运行时开销。每个查询都会在编译时进行验证,并支持完整的自动补全。
Kysely 不是ORM——没有关联关系、没有懒加载、没有魔法特性。它只是带类型校验的SQL工具。

When to Use

使用场景

  • "write a Kysely query"
  • "create database migration"
  • "add a new table"
  • "query with joins / subqueries / CTEs"
  • "JSONB or array column operations"
  • Working with an existing Kysely + PostgreSQL codebase
  • Debugging Kysely type inference issues
  • "编写Kysely查询"
  • "创建数据库迁移"
  • "添加新表"
  • "使用连接/子查询/CTE进行查询"
  • "JSONB或数组列操作"
  • 处理现有的Kysely + PostgreSQL代码库
  • 调试Kysely类型推断问题

Outcomes

预期成果

  • Artifact: Type-safe queries using ExpressionBuilder patterns
  • Artifact: Migration files via kysely-ctl
  • Decision: When to use query builder vs
    sql
    template tag
  • 产出物:使用ExpressionBuilder模式的类型安全查询
  • 产出物:通过kysely-ctl生成的迁移文件
  • 决策:何时使用查询构建器 vs
    sql
    模板标签

Core Philosophy

核心理念

Prefer Kysely's query builder for everything it can express. Fall back to
sql
template tag only when the builder lacks support.
Use CaseApproach
Schema definitionsKysely migrations (
db.schema.createTable
)
Simple CRUDQuery builder (
selectFrom
,
insertInto
,
updateTable
,
deleteFrom
)
JOINs (any complexity)Query builder (callback format for complex joins)
Aggregations / GROUP BYQuery builder with
eb.fn
CTEsQuery builder (
.with()
)
Relations / nested JSON
jsonArrayFrom
/
jsonObjectFrom
helpers
Conditional queries
$if()
or dynamic filter arrays
Reusable fragments
Expression<T>
helper functions
Dynamic columns/tables
db.dynamic.ref()
/
db.dynamic.table()
with allowlisted values
Dynamic SQL fragments
sql.raw()
with allowlisted values,
sql.join()
for arrays
Dialect-specific syntax
sql
template tag
Unsupported operators
sql
template tag
Need a query?
  Can Kysely's builder express it?
    YES -> Use the query builder (type-safe, composable)
    NO  -> Use sql`` template tag (always type your output: sql<Type>`...`)
优先使用Kysely查询构建器实现所有可表达的逻辑。仅当构建器不支持时,才回退到
sql
模板标签。
使用场景实现方式
Schema定义Kysely迁移(
db.schema.createTable
简单CRUD查询构建器(
selectFrom
,
insertInto
,
updateTable
,
deleteFrom
JOIN(任意复杂度)查询构建器(复杂连接使用回调格式)
聚合 / GROUP BY
eb.fn
的查询构建器
CTE查询构建器(
.with()
关联关系 / 嵌套JSON
jsonArrayFrom
/
jsonObjectFrom
辅助函数
条件查询
$if()
或动态过滤数组
可复用片段
Expression<T>
辅助函数
动态列/表
db.dynamic.ref()
/
db.dynamic.table()
(配合白名单值)
动态SQL片段
sql.raw()
(配合白名单值),数组使用
sql.join()
方言特定语法
sql
模板标签
不支持的运算符
sql
模板标签
需要编写查询?
  Kysely构建器能否实现?
    是 -> 使用查询构建器(类型安全、可组合)
    否 -> 使用sql``模板标签(务必为输出指定类型:sql<Type>`...`)

ExpressionBuilder (eb) Cheat Sheet

ExpressionBuilder (eb) 速查表

The
eb
callback parameter is the foundation of type-safe query building:
MethodPurposeExample
eb.ref("col")
Column reference
eb.ref("user.email")
eb.val(value)
Parameterized value ($1)
eb.val("hello")
eb.lit(value)
SQL literal (numbers, bools, null only)
eb.lit(0)
,
eb.lit(null)
eb.fn<T>("name", [...])
Typed function call
eb.fn<string>("upper", [eb.ref("email")])
eb.fn.count("col")
COUNT aggregate
eb.fn.count("id").as("count")
eb.fn.sum / avg / min / max
Other aggregates
eb.fn.sum("amount").as("total")
eb.fn.coalesce(col, fallback)
COALESCE
eb.fn.coalesce("col", eb.val(0))
eb.case().when().then().else().end()
CASE expressionsee query-patterns.md
eb.and([...])
/
eb.or([...])
Combine conditions
eb.or([eb("a","=",1), eb("b","=",2)])
eb.exists(subquery)
EXISTS check
eb.exists(db.selectFrom(...))
eb.not(expr)
Negate expression
eb.not(eb.exists(...))
eb.cast(expr, "type")
SQL CAST
eb.cast(eb.val("x"), "text")
eb(left, op, right)
Binary expression
eb("qty", "*", eb.ref("price"))
For full query examples, see references/query-patterns.md.
eb
回调参数是类型安全查询构建的基础:
方法用途示例
eb.ref("col")
列引用
eb.ref("user.email")
eb.val(value)
参数化值($1)
eb.val("hello")
eb.lit(value)
SQL字面量(仅支持数字、布尔值、null)
eb.lit(0)
,
eb.lit(null)
eb.fn<T>("name", [...])
带类型的函数调用
eb.fn<string>("upper", [eb.ref("email")])
eb.fn.count("col")
COUNT聚合函数
eb.fn.count("id").as("count")
eb.fn.sum / avg / min / max
其他聚合函数
eb.fn.sum("amount").as("total")
eb.fn.coalesce(col, fallback)
COALESCE函数
eb.fn.coalesce("col", eb.val(0))
eb.case().when().then().else().end()
CASE表达式查看 query-patterns.md
eb.and([...])
/
eb.or([...])
组合条件
eb.or([eb("a","=",1), eb("b","=",2)])
eb.exists(subquery)
EXISTS检查
eb.exists(db.selectFrom(...))
eb.not(expr)
表达式取反
eb.not(eb.exists(...))
eb.cast(expr, "type")
SQL CAST转换
eb.cast(eb.val("x"), "text")
eb(left, op, right)
二元表达式
eb("qty", "*", eb.ref("price"))
完整查询示例请查看 references/query-patterns.md

Database Types

数据库类型

typescript
import { Generated, Insertable, Selectable, Updateable } from "kysely"

interface Database {
  users: UsersTable
  posts: PostsTable
}

interface UsersTable {
  id: Generated<number>
  email: string
  name: string
  created_at: Generated<Date>
}

// Helper types make Generated fields optional for inserts/updates
type NewUser = Insertable<UsersTable>
type UserUpdate = Updateable<UsersTable>
type User = Selectable<UsersTable>
Use
kysely-codegen
to generate these types from your database. See references/migrations.md.
typescript
import { Generated, Insertable, Selectable, Updateable } from "kysely"

interface Database {
  users: UsersTable
  posts: PostsTable
}

interface UsersTable {
  id: Generated<number>
  email: string
  name: string
  created_at: Generated<Date>
}

// 辅助类型让Generated字段在插入/更新时可选
type NewUser = Insertable<UsersTable>
type UserUpdate = Updateable<UsersTable>
type User = Selectable<UsersTable>
使用
kysely-codegen
从数据库生成这些类型。查看 references/migrations.md

Pitfalls

常见陷阱

These are the most common mistakes when writing Kysely code.
以下是编写Kysely代码时最容易犯的错误。

1. eb.val() vs eb.lit() confusion

1. eb.val() 与 eb.lit() 混淆

eb.val()
creates parameterized values ($1) -- use for user input.
eb.lit()
creates SQL literals -- only accepts numbers, booleans, null (not strings). For string literals, use
sql\
'value'``.
typescript
eb.val("safe input")              // $1 -- parameterized, safe
eb.lit(42)                        // 42 -- literal in SQL
eb.lit("text")                    // THROWS "unsafe immediate value"
eb.cast(eb.val("text"), "text")   // $1::text -- workaround for typed string params
eb.val()
创建参数化值($1)——用于用户输入。
eb.lit()
创建SQL字面量——仅接受数字、布尔值、null(不接受字符串)。字符串字面量请使用
sql\
'value'``。
typescript
eb.val("safe input")              // $1 -- 参数化,安全
eb.lit(42)                        // 42 -- SQL中的字面量
eb.lit("text")                    // 抛出 "unsafe immediate value" 错误
eb.cast(eb.val("text"), "text")   // $1::text -- 带类型字符串参数的解决方案

2. Forgetting .execute()

2. 忘记调用.execute()

Queries are lazy builders. Without an execute method, nothing runs.
typescript
db.selectFrom("user").selectAll()                  // does nothing
await db.selectFrom("user").selectAll().execute()   // runs the query
查询是延迟构建的。如果不调用执行方法,不会执行任何操作。
typescript
db.selectFrom("user").selectAll()                  // 无任何操作
await db.selectFrom("user").selectAll().execute()   // 执行查询

3. .where() vs .whereRef() for column comparisons

3. 列比较时使用.where() 而非 .whereRef()

.where("a", "=", "b")
compares column
a
to the string
"b"
. Use
.whereRef()
for column-to-column comparisons.
typescript
.where("table.col", "=", "other.col")       // compares to string literal
.whereRef("table.col", "=", "other.col")    // compares two columns
.where("a", "=", "b")
将列
a
字符串
"b"
比较。列与列比较请使用
.whereRef()
typescript
.where("table.col", "=", "other.col")       // 与字符串字面量比较
.whereRef("table.col", "=", "other.col")    // 比较两个列

4. Always type sql`` template literals

4. 务必为sql``模板字面量指定类型

sql
template literals infer as
unknown
. Always provide an explicit type parameter.
typescript
sql`now()`                      // Expression<unknown> -- bad
sql<Date>`now()`                // Expression<Date> -- good
sql
模板字面量默认推断为
unknown
。务必提供显式类型参数。
typescript
sql`now()`                      // Expression<unknown> -- 不推荐
sql<Date>`now()`                // Expression<Date> -- 推荐

5. selectAll() breaks nested json helper type inference (#1059)

5. selectAll() 破坏嵌套json辅助函数的类型推断(#1059

Bare
.selectAll()
inside json helper subqueries merges outer table columns into the type. Use table-qualified
.selectAll("table_name")
instead. See references/relations-helpers.md.
在json辅助函数的子查询中直接使用
.selectAll()
会将外部表列合并到类型中。请改用带表限定的
.selectAll("table_name")
。查看 references/relations-helpers.md

6. DATE columns cause timezone drift

6. DATE列导致时区偏移

The
pg
driver converts DATE to JS
Date
, causing timezone issues. Parse DATE as string instead. See references/migrations.md.
pg
驱动将DATE转换为JS
Date
,引发时区问题。请将DATE解析为字符串。查看 references/migrations.md

7. "Type instantiation is excessively deep"

7. "Type instantiation is excessively deep"

Complex queries with many CTEs can exceed TypeScript's type depth. Use
$assertType<T>()
on intermediate CTEs. See references/relations-helpers.md.
包含多个CTE的复杂查询可能超出TypeScript的类型深度限制。在中间CTE上使用
$assertType<T>()
。查看 references/relations-helpers.md

8. PostgreSQL does NOT auto-index foreign keys

8. PostgreSQL不会自动为外键创建索引

Always create indexes on FK columns manually in migrations. See references/migrations.md.
务必在迁移中手动为FK列创建索引。查看 references/migrations.md

9. CamelCasePlugin causes drift with raw SQL

9. CamelCasePlugin 导致原生SQL命名偏移

CamelCasePlugin
converts snake_case DB columns to camelCase in the builder. But raw
sql
template queries bypass the plugin, creating inconsistent naming between builder and raw queries in the same codebase. If you use significant raw SQL alongside the builder, avoid this plugin and keep snake_case throughout. See references/migrations.md.
CamelCasePlugin
将数据库中的snake_case列转换为构建器中的camelCase。但原生
sql
模板查询会绕过该插件,导致同一代码库中构建器与原生查询的命名不一致。如果在使用构建器的同时大量使用原生SQL,请避免使用此插件,全程保持snake_case命名。查看 references/migrations.md

10. JSONB inserts need JSON.stringify only in sql templates (#209)

10. JSONB插入仅在sql模板中需要JSON.stringify(#209

The
pg
driver auto-serializes objects for
.values()
/
.set()
JSONB params (pg types). You only need explicit
JSON.stringify
inside
sql
template expressions or with non-pg drivers. See references/jsonb-arrays.md.
pg
驱动会自动为
.values()
/
.set()
的JSONB参数序列化对象(参考pg types)。仅在
sql
模板表达式中或使用非pg驱动时才需要显式调用
JSON.stringify
。查看 references/jsonb-arrays.md

11. Pool queries use different connections (API, #330)

11. 连接池查询使用不同连接(API#330

Each query may use a different pooled connection.
SET
, session variables, and RLS context do not persist across queries. Use
db.transaction()
or
db.connection()
to pin multiple statements to one connection. See references/advanced-patterns.md.
每个查询可能使用连接池中的不同连接。
SET
、会话变量和RLS上下文不会在查询间持久化。使用
db.transaction()
db.connection()
将多个语句绑定到同一个连接。查看 references/advanced-patterns.md

12. WHERE does not narrow result types (#310)

12. WHERE不会缩小结果类型(#310

.where('col', 'is not', null)
does not remove
null
from the result type. Use
$narrowType
to manually assert the narrowed shape. See references/advanced-patterns.md.
.where('col', 'is not', null)
不会从结果类型中移除
null
。使用
$narrowType
手动断言缩小后的类型。查看 references/advanced-patterns.md

13. Team migration ordering (#697)

13. 团队迁移排序问题(#697

Migrations added on parallel branches may fail strict ordering when merged. Set
allowUnorderedMigrations: true
on the
Migrator
. See references/migrations.md.
并行分支上添加的迁移在合并时可能因严格排序失败。在
Migrator
上设置
allowUnorderedMigrations: true
。查看 references/migrations.md

14. JSON aggregation changes runtime types (#1412)

14. JSON聚合改变运行时类型(#1412

Date
columns inside
jsonArrayFrom
/
jsonObjectFrom
/
json_agg
results become strings at runtime because JSON has no Date type. TypeScript types still say
Date
. Parse dates manually at the boundary. See references/jsonb-arrays.md.
jsonArrayFrom
/
jsonObjectFrom
/
json_agg
结果中的
Date
列在运行时会变成字符串,因为JSON没有Date类型。但TypeScript类型仍显示为
Date
。请在边界处手动解析日期。查看 references/jsonb-arrays.md

Official Resources

官方资源

ResourceURL
LLM-friendly docs (full)
https://kysely.dev/llms-full.txt
API documentation
https://kysely-org.github.io/kysely-apidoc
Playground
https://kyse.link
GitHub
https://github.com/kysely-org/kysely
Awesome Kysely (ecosystem)
https://github.com/kysely-org/awesome-kysely
When using Cursor
@Docs
, reference
https://kysely.dev/llms-full.txt
for the most complete context.
资源链接
适配LLM的完整文档
https://kysely.dev/llms-full.txt
API文档
https://kysely-org.github.io/kysely-apidoc
在线 playground
https://kyse.link
GitHub仓库
https://github.com/kysely-org/kysely
Awesome Kysely(生态)
https://github.com/kysely-org/awesome-kysely
使用Cursor
@Docs
时,引用
https://kysely.dev/llms-full.txt
可获取最完整的上下文。

Reference Files

参考文件

Consult these for detailed code patterns:
ReferenceWhen to Use
query-patterns.mdSELECT, WHERE, JOINs, aggregations, ORDER BY, mutations, $if, subqueries, transactions
jsonb-arrays.mdJSONB columns, array columns, JSONPath, querying JSON/array data
relations-helpers.mdjsonArrayFrom, jsonObjectFrom, reusable Expression<T> helpers, CTEs, compile/InferResult
migrations.mdkysely-ctl setup, migration files, column types, type generation, plugins, Neon dialect, DATE fix
advanced-patterns.mdDynamic columns, withSchema, connection pinning, RLS, $narrowType, streaming, MERGE, views, FTS, testing
ecosystem.mdPagination, auth adapters, Fastify plugin, community dialects
如需详细代码模式,请查阅以下文件:
参考文件使用场景
query-patterns.mdSELECT、WHERE、JOIN、聚合、ORDER BY、数据变更、$if、子查询、事务
jsonb-arrays.mdJSONB列、数组列、JSONPath、JSON/数组数据查询
relations-helpers.mdjsonArrayFrom、jsonObjectFrom、可复用Expression<T>辅助函数、CTE、compile/InferResult
migrations.mdkysely-ctl配置、迁移文件、列类型、类型生成、插件、Neon方言、DATE问题修复
advanced-patterns.md动态列、withSchema、连接绑定、RLS、$narrowType、流式查询、MERGE、视图、FTS、测试
ecosystem.md分页、认证适配器、Fastify插件、社区方言