OrchidORM 由查询构建器(如 Knex 或 Kysely)和关系操作层(类似 Prisma)两部分构成。
查询构建器负责生成并执行 select、insert、update、delete 等 SQL 查询语句。
ORM 则支持定义 belongsTo、hasMany 等关联关系,可对关联数据进行查询、级联操作(创建 / 更新 / 删除)及其他高级查询。
安装
使用以下命令进行安装:
npm i orchid-orm
# 或
pnpm i orchid-orm
orchidORM 初始化函数
orchidORM() 作为 ORM 的初始化入口,接受两个参数:
函数返回一个 ORM 实例,包含:
- 所有注册的表模型(直接访问)
- ORM 内置方法(以
$前缀标识,避免命名冲突)
import { orchidORM } from 'orchid-orm';
// 导入所有表
import { UserTable } from './tables/user';
import { MessageTable } from './tables/message';
export const db = orchidORM(
{
// databaseURL 的详细信息如下
databaseURL: process.env.DATABASE_URL,
// ssl 和 schema 可以在此处设置,也可以作为 databaseURL 参数:
ssl: true,
schema: 'my_schema',
// 数据库启动时重试连接,默认情况下不重试,
// 请参阅下面的 `connectRetry` 部分
connectRetry: true,
// 日志选项,默认值为 false
log: true,
// 自动为关系创建外键
// 请参阅下面的 `autoForeignKeys` 部分
autoForeignKeys: true,
// 选项以隐式方式创建命名的预备语句,默认值为 false
autoPreparedStatements: true,
},
{
user: UserTable,
message: MessageTable,
},
);
也可以通过自定义适配器初始化
import { orchidORM, Adapter } from 'orchid-orm';
export const db = orchidORM(
{
adapter: new Adapter({ databaseURL: process.env.DATABASE_URL }),
log: true,
},
{
// ...tables
},
);
定义基础表
定义一个基础表类用于扩展,建议将此代码与 db 文件分离:
import { createBaseTable } from 'orchid-orm';
export const BaseTable = createBaseTable();
export const { sql } = BaseTable;
导出 sql 以便与 BaseTable 中定义的自定义列类型关联使用。
可选择性自定义列类型行为,使其应用于所有表:
import { createBaseTable } from 'orchid-orm';
// 可选地,使用以下验证集成之一:
import { zodSchemaConfig } from 'orchid-orm-schema-to-zod';
import { valibotSchemaConfig } from 'orchid-orm-valibot';
export const BaseTable = createBaseTable({
// 如果数据库中的列为 snake_case,请设置为 true
snakeCase: true,
// 可选,但推荐:从您的表派生并使用验证模式
schemaConfig: zodSchemaConfig,
// 或
schemaConfig: valibotSchemaConfig,
columnTypes: (t) => ({
// 默认情况下,时间戳返回为字符串,覆盖为 Data
timestamp: () => t.timestamp().asDate(),
// 在 BaseTable 中定义自定义类型以便以后在表中使用
myEnum: () => t.enum('myEnum', ['one', 'two', 'three']),
}),
});
export const { sql } = BaseTable;
自定义列类型详情见覆盖列类型。
定义数据表
数据表通过类形式定义,必须包含 table 和 columns 属性:
table: 数据库表名(标记为readonly用于类型检查)columns: 定义表结构(详见列模式)
import { Selectable, DefaultSelect, Insertable, Updatable } from 'orchid-orm';
// 从上一步的文件导入 BaseTable:
import { BaseTable } from './baseTable';
// 导出各种用例的用户类型:
export type User = Selectable<UserTable>;
export type UserDefault = DefaultSelect<UserTable>;
export type UserNew = Insertable<UserTable>;
export type UserUpdate = Updateable<UserTable>;
export class UserTable extends BaseTable {
readonly table = 'user';
columns = this.setColumns((t) => ({
id: t.identity().primaryKey(),
name: t.string(),
password: t.string(),
...t.timestamps(),
}));
}
注册数据表
定义完成后,需在 db 文件中注册表类:
import { UserTable } from './tables/user';
export const db = orchidORM(
{
databaseURL: process.env.DATABASE_URL,
},
{
user: UserTable, // 注册 UserTable
},
);
使用方式
通过 db 实例访问表类进行查询:
import { db } from './db';
// 正确:通过 db 实例访问
const user = await db.user.findBy({ name: 'John' });
// 错误:直接使用表类无法执行查询
await UserTable.findBy({ name: 'John' }); // ❌ 无效
单独配置表的 snake_case
如需为特定表单独配置 snake_case:
import { BaseTable } from './baseTable';
export class SnakeCaseTable extends BaseTable {
readonly table = 'table';
snakeCase = true; // 单独启用 snake_case
columns = this.setColumns((t) => ({
// 对应数据库中的 snake_column
snakeColumn: t.text(),
}));
}
定义表类
表类类似于其他 ORM 中的模型或实体,但职责不同:
import { BaseTable, sql } from './baseTable';
import { PostTable } from './post.table';
import { SubscriptionTable } from './subscription.table';
export class UserTable extends BaseTable {
schema = 'customSchema'; // 数据库模式(可选)
readonly table = 'user'; // 数据库表名(必填,TypeScript 类型检查需要)
comment = '这是一个用于存储用户的表'; // 表注释(数据库元数据)
noPrimaryKey = true; // 无主键表(需显式声明,否则 ORM 会报错)
snakeCase = true; // 单独启用 snake_case 命名(基类已配置时可省略)
language = 'spanish'; // 全文搜索语言(默认 'english')
readonly softDelete = true; // 启用软删除(默认使用 deletedAt 列,可指定列名)
columns = this.setColumns(
// 第一参数:定义列结构
(t) => ({
id: t.uuid().primaryKey(), // 主键(必填,除非设置 noPrimaryKey)
firstName: t.string(),
lastName: t.string(),
username: t.string().unique(), // 唯一约束
email: t.string().email().unique(), // 邮箱格式验证 + 唯一约束
deletedAt: t.timestamp().nullable(), // 软删除时间戳(可为 null)
subscriptionProvider: t.enum('paymentProvider', ['stripe', 'paypal']), // 枚举类型
subscriptionId: t.uuid(),
startDate: t.timestamp(),
endDate: t.timestamp(),
...t.timestamps(), // 自动添加 createdAt/updatedAt 时间戳
}),
// 第二参数(可选):定义复合约束(主键、索引、外键等)
(t) => [
// 复合主键
t.primaryKey(['firstName', 'lastName']),
// 复合唯一索引
t.unique(['subscriptionProvider', 'subscriptionId']),
// 复合外键(指向 SubscriptionTable 的 provider/id 列)
t.foreignKey(
['subscriptionProvider', 'subscriptionId'],
() => SubscriptionTable,
['provider', 'id'],
),
// Postgres 排除约束(时间范围不重叠)
t.exclude([
{ expression: `tstzrange("startDate", "endDate")`, with: '&&' },
]),
// 数据库级检查约束
t.check(sql`username != email`), // 使用 sql 模板字面量
],
);
// 定义数据库端计算列(虚拟列)
computed = this.setComputed({
fullName: (q) => // q 是当前表的查询构建器上下文
sql`${q.column('firstName')} || ' ' || ${q.column('lastName')}` // 拼接姓名
.type((t) => t.string()), // 显式指定返回类型
});
// 定义查询作用域(可复用的查询条件)
scopes = this.setScopes({
default: (q) => q.where({ hidden: false }), // 默认作用域(自动应用)
active: (q) => q.where({ active: true }), // 自定义作用域
});
// 定义关系(此处为一对多关系:用户拥有多篇文章)
relations = {
posts: this.hasMany(() => PostTable, {
columns: ['id'], // 当前表关联列(用户 ID)
references: ['authorId'], // 目标表关联列(文章作者 ID)
}),
};
}
重要说明
table和softDelete必须标记为readonly,确保 TypeScript 类型推导正确- 列配置详见列模式概述
- 复合约束(主键 / 索引 / 外键等)文档见迁移列方法
- 关系定义详见建模关系
生成迁移
定义或修改表结构后,通过命令生成并应用迁移:
# 生成迁移(自动命名)
pnpm db g
# 生成指定名称的迁移
pnpm db g create-user-table
# 生成后立即应用迁移
pnpm db g up
注意事项
- 危险操作:此工具会删除代码未引用的数据库实体(模式、表等),仅适用于完全由应用管理的数据库
- 重命名表 / 列时,工具会交互式询问是否保留数据(重命名列)或删除旧列(数据丢失)
- 自动处理的数据库对象包括:表、列、模式、枚举、主键、外键、索引、检查约束、排除约束
Postgres 扩展
启用 Postgres 扩展(如 citext)需在配置中声明:
export const db = orchidORM(
{
databaseURL: process.env.DATABASE_URL,
extensions: [
'citext', // 最新版本
{ name: 'citext', version: '1.2.3' }, // 指定版本
'mySchema.citext', // 特定模式下的扩展
],
generatorIgnore: {
tables: ['spatial_ref_sys'], // 忽略扩展自动创建的表(如 postgis 的系统表)
},
},
{ /* 表定义 */ },
);
表实用类型
OrchidORM 提供多种类型别名,用于不同操作场景的类型安全:
Selectable
数据库查询结果类型,包含所有列(包括计算列和 select(false) 列),应用列解析器(如时间戳转 Date):
export type User = Selectable<UserTable>; // 包含所有列的完整类型
DefaultSelect
默认查询结果类型,排除 select(false) 列和计算列,仅包含默认选中的列:
export type UserDefault = DefaultSelect<UserTable>; // 常规查询返回类型
Insertable
插入数据类型,允许的输入字段(列类型支持 encode 转换):
export type UserNew = Insertable<UserTable>; // 创建新记录时的参数类型
Updatable
更新数据类型,与 Insertable 相同,但所有字段可选:
export type UserUpdate = Updatable<UserTable>; // 更新记录时的参数类型
Queryable
查询条件类型,用于 where 等方法的参数,保持原始数据库类型(不应用解析 / 编码):
export type UserQueryable = Queryable<UserTable>; // 构建查询条件时的类型
独立查询构建器(createDb)
若无需 ORM 功能,可单独使用底层查询构建器 pqb:
import { createDb } from 'orchid-orm';
const db = createDb({
databaseURL: process.env.DATABASE_URL,
snakeCase: true, // 数据库列名为 snake_case 时启用
schemaConfig: zodSchemaConfig, // 集成 Zod 验证(可选)
columnTypes: (t) => ({
timestamp: () => t.timestamp().asNumber(), // 自定义列类型
}),
});
// 定义表结构
export const User = db('user', (t) => ({
id: t.identity().primaryKey(),
name: t.string(),
age: t.integer().nullable(),
}));
// 类型安全查询
const users = await User.select('id', 'name').where({ age: { gte: 20 } });
关键配置选项
snakeCase
控制列名转换策略:
- 默认:使用 camelCase(代码中)映射数据库列名
- 启用:
snakeCase: true自动将 camelCase 转换为 snake_case(数据库列名规范) - 覆盖:单个表可通过
snakeCase = true单独启用,或通过t.name('snake_case')显式指定列名
softDelete
启用软删除(逻辑删除):
export class SomeTable extends BaseTable {
readonly softDelete = true; // 使用默认列 deletedAt
// 或自定义列名
readonly softDelete = 'my_deleted_at';
}
- 默认查询自动过滤已删除记录(
deletedAt IS NULL) - 强制查询所有记录:
db.table.includeDeleted() - 物理删除:
db.table.find(1).hardDelete()
scopes
定义可复用的查询条件(作用域):
scopes = this.setScopes({
default: (q) => q.where({ hidden: false }), // 所有查询默认应用
active: (q) => q.where({ active: true }), // 按需应用
});
// 使用方式
await db.table.scope('active'); // 应用 active 作用域
await db.table.unscope('default'); // 移除 default 作用域
原始 SQL 操作
$query
执行原始 SQL 查询(支持事务):
// 安全插值(反引号模板)
const result = await db.$query<{ one: number }>`SELECT ${1} AS one`;
// 手动构造 SQL 对象(适用于动态参数)
import { sql } from './baseTable';
const result = await db.$query(sql`SELECT $1 AS one`, [123]);
$from
基于子查询构建查询:
const subQuery = db.user.select('name', { count: (q) => q.related.count() });
const result = await db.$from(subQuery).where({ count: { gte: 5 } });
最佳实践
- 主键约束:所有表应定义主键,无主键表需显式声明
noPrimaryKey = true - 外键自动生成:通过
createBaseTable({ autoForeignKeys: true })自动创建数据库级外键(需配合迁移工具) - 类型安全:始终通过
db.table访问表类,避免直接使用表类(如UserTable.findBy(...)无效) 所有表文件需在orchidORM实例中注册(见设置部分),已有项目可通过db pull命令反向生成表代码和迁移。