- 后端
- 数据库
- ORM
【免费下载链接】typeorm
TypeScript & JavaScript ORM for Node.js — supports PostgreSQL, MySQL, MariaDB, SQLite, SQL Server, Oracle, and more.
导读
本文以仓库中的 playground/README.md 为核心,完整拆解 TypeORM 官方自带的 SQLite 演示项目:从零初始化一个运行在内存中的 SQLite 数据库(基于 sql.js 驱动),到定义 User 实体、写入示例数据、查询回显,再到优雅关闭连接的全过程。读完本文,你将掌握 TypeORM 在纯 ESM(ECMAScript Modules)工程中的配置方式、sqljs驱动的底层工作原理、synchronize自动建表机制,以及SqljsEntityManager提供的数据库导入导出能力。
一、项目概览:一个最小可运行的 TypeORM 演示工程
playground是 TypeORM 仓库中的一个独立演示项目,其 README 明确描述为"TypeORM SQLite Example",即一个使用 TypeORM 连接 SQLite 数据库的示例。它的特点是:
- 使用ESM(ECMAScript Modules)模块体系;
- 采用现代 async/await 异步模式编写业务代码;
- 展示数据库连接的完整生命周期管理,包括初始化与清理(
initialize/destroy); - 完整演示TypeScript 装饰器与实体定义的用法。
项目使用sql.js作为数据库驱动,sql.js会把 SQLite 编译为 WebAssembly,从而在 Node.js 甚至浏览器环境中创建一个纯内存数据库,无需任何原生(native)依赖。这也是该示例能够运行在 Stackblitz 这类浏览器在线环境中的根本原因(详见 playground/README.md)。
项目目录结构
playground/ ├── src/ │ ├── entity/ │ │ └── User.ts # User 实体定义 │ ├── index.ts # 主应用代码(入口) │ └── ormconfig.ts # 数据库配置(DataSource 定义) ├── package.json ├── tsconfig.json └── README.md整个项目只有三个核心源文件:实体定义、主入口、数据源配置,结构极度精简,是学习 TypeORM 最小闭环的绝佳样板。
二、环境与依赖:如何在 monorepo 中运行 playground
playground隶属于 TypeORM 的 pnpm workspace。仓库根目录的 pnpm-workspace.yaml 中声明了packages/*与playground两个成员;其中playground对typeorm的依赖以workspace:*形式声明(见 playground/package.json),意味着它直接链接仓库内packages/typeorm的构建产物,而非 npm 上的发布版本。
查看 playground/package.json 可以看到完整的依赖清单:
| 依赖 | 版本 | 作用 |
|---|---|---|
sql.js | ^1.13.0 | 纯 JS/WASM 的 SQLite 实现,作为 TypeORM 的sqljs驱动后端 |
ts-node | ^10.9.2 | 在 Node 中直接运行 TypeScript |
typeorm | workspace:* | 链接本仓库的 TypeORM 源码构建产物 |
@types/node | ^22.18.6 | Node.js 类型定义 |
@types/sql.js | ^1.4.9 | sql.js 类型定义 |
typescript | ^5.9.2 | TypeScript 编译器 |
运行步骤如下:
- 安装依赖:在仓库根目录执行
pnpm install(或进入playground目录执行npm i,README 中给出的即为此命令); - 启动应用:执行
npm start。
需要说明的适用前提:由于
playground通过workspace:*依赖typeorm,在 pnpm 工作区中解析typeorm前,需要先在仓库根目录执行pnpm run package构建 TypeORM 包(pnpm-workspace.yaml 中的注释对此有明确说明)。直接在仓库外单独npm i无法获得本地 TypeORM。
可用命令一览
README 给出了三个命令(见 playground/README.md):
# 启动应用(入口:src/index.ts) npm start # 构建 TypeScript 代码(tsc 编译到 dist/) npm run build # 运行 TypeORM CLI(按需使用) npm run typeorm其中npm run typeorm映射到typeorm-ts-node-esm -d ./src/ormconfig.ts,这是 TypeORM 专为 ESM 场景提供的 CLI 入口。packages/typeorm/src/cli-ts-node-esm.ts 展示了它的实现:它会检查环境变量NODE_OPTIONS是否已包含--loader ts-node,若没有则通过spawnSync以--loader ts-node/esm --no-warnings重新拉起自身进程,从而让 CLI 也能解析 ESM 环境下的 TypeScript 配置文件。
三、ESM 工程配置:package.json与tsconfig.json的配合
playground 是一份纯 ESM 示例,因此模块化配置是关键一环。
在 playground/package.json 中:
"type": "module":声明整个包按 ESM 解析,.ts源码经 ts-node 转换后以 ESM 方式加载;"start": "node --loader ts-node/esm src/index.ts":通过 Node 的--loader ts-node/esm钩子直接运行 TypeScript 源码,无需先编译;"build": "tsc":使用 TypeScript 编译器输出到dist/。
在 playground/tsconfig.json 中:
| 配置项 | 值 | 含义 |
|---|---|---|
target | ESNext | 编译目标为最新 ECMAScript |
module/moduleResolution | ESNext/Node | ESM 模块体系 |
strict | true | 开启严格类型检查 |
experimentalDecorators | true | 启用装饰器语法(实体定义必需) |
emitDecoratorMetadata | true | 输出装饰器元数据(TypeORM 反射实体属性类型必需) |
rootDir/outDir | ./src/./dist | 编译输入输出目录 |
ts-node.esm | true | 让 ts-node 在 ESM 模式下工作 |
值得注意的是:src/index.ts与src/ormconfig.ts中导入本地模块时使用了.js后缀(如import { AppDataSource } from "./ormconfig.js"),这是 ESM 环境下 TypeScript 的标准做法——源码中的.js导入路径在编译后依然有效,同时也能被 ts-node 正确解析。
四、数据源配置:ormconfig.ts里的sqljs驱动
playground/src/ormconfig.ts 是整个示例的"心脏",完整代码如下:
import "reflect-metadata" import { DataSource } from "typeorm" import { User } from "./entity/User.js" export const AppDataSource = new DataSource({ type: "sqljs", synchronize: true, logging: true, entities: [User], })逐项拆解:
import "reflect-metadata":TypeORM 依赖reflect-metadata提供的反射元数据能力来读取装饰器信息,必须在导入DataSource前引入;type: "sqljs":指定使用 sql.js 驱动。注意 TypeORM 中 SQLite 相关驱动有三个:sqlite(原生sqlite3包)、better-sqlite3(同步 API 原生绑定)、sqljs(WASM 内存实现)。这里选择sqljs意味着不触碰任何原生二进制文件;synchronize: true:数据源初始化时自动根据实体元数据创建数据库表结构。由于这里没有配置migrations,演示项目依靠该选项让User表随初始化自动创建;logging: true:开启 SQL 日志,运行时终端会打印 TypeORM 实际执行的 SQL 语句;entities: [User]:注册实体类。该配置未设置location,因此 sql.js 驱动会创建一个全新的内存数据库(下文结合源码说明)。
从源码看sqljs驱动的连接建立过程
packages/typeorm/src/driver/sqljs/SqljsDriver.ts 的createDatabaseConnection()展示了连接建立的策略:
protected createDatabaseConnection(): Promise<any> { if (this.options.location) { return this.load(this.options.location, false) } return this.createDatabaseConnectionWithImport(this.options.database) }也就是说:
- 若配置了
location,驱动会尝试从该文件加载已有数据库(文件不存在时不报错,首次写操作时才落盘); - 未配置
location时,直接new sqlite.Database()创建一个全新的空内存数据库,与 playground 的行为完全一致。
在createDatabaseConnectionWithImport()中,驱动创建数据库后还会执行PRAGMA foreign_keys = ON启用外键约束——这意味着即使没有显式配置,sqljs驱动的连接默认也会开启 SQLite 外键检查。
sqljs特有的数据源选项
packages/typeorm/src/driver/sqljs/SqljsDataSourceOptions.ts 定义了sqljs驱动的扩展选项,playground 虽未使用全部,但理解它们有助于将此示例升级为可持久化的应用:
| 选项 | 类型 | 说明 |
|---|---|---|
database | Uint8Array | 打开连接时导入的数据库二进制内容,用于从备份恢复 |
driver | any | 自定义 sql.js 驱动对象,默认require("sql.js") |
sqlJsConfig | any | 初始化 sql.js 时传入的配置 |
autoSave | boolean | 开启自动保存:每次数据库变更后自动写盘或回调 |
autoSaveCallback | Function | 替代内部保存逻辑的回调,autoSave为true时生效 |
location | string | Node 环境下的文件路径或浏览器环境下的 localStorage 键 |
useLocalForage | boolean | 浏览器环境下改用 localforage 异步读写 indexedDB |
poolSize | never | sqljs 驱动不支持连接池 |
一个关键约束在 SqljsDriver.ts 的构造函数中:开启autoSave时必须同时提供location或autoSaveCallback,否则会抛出DriverOptionNotSetError。这与直觉一致——自动保存总需要一个落点(文件、localStorage 或回调)。
五、实体定义:User.ts中的装饰器用法
playground/src/entity/User.ts 定义了一个最典型的 TypeORM 实体:
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm" @Entity() export class User { @PrimaryGeneratedColumn() id!: number @Column() firstName!: string @Column() lastName!: string @Column() email!: string @Column({ default: true }) isActive!: boolean }要点分析:
@Entity():将该类标记为数据库表映射实体,表名默认由类名推导(此处为user);@PrimaryGeneratedColumn():声明自增主键。在 sql.js 场景下,插入后需要回读主键——SqljsDriver.ts 的createGeneratedMap()正是通过执行SELECT last_insert_rowid()拿到新插入行的自增 id(源码注释引用了 sql.js 上游 issue,说明这是获取插入 id 的唯一可靠途径);@Column():映射普通列,默认列类型由 TypeScript 设计时类型(number/string/boolean)推断;@Column({ default: true }):为isActive指定默认值true,建表时对应列会带上DEFAULT true约束;!非空断言:属性通过装饰器元数据赋值,TypeScript 的strict模式下用!声明"该属性在构造后必然被赋值",这是 TypeORM 实体的惯例写法。
正是 playground/tsconfig.json 中的experimentalDecorators与emitDecoratorMetadata两个开关,才让 TypeORM 能够在运行时读取@Column对应属性的类型,从而推断出 SQLite 中的列类型。
六、主流程:index.ts的完整生命周期
playground/src/index.ts 演示了 TypeORM 使用中的标准五步流程:
import { AppDataSource } from "./ormconfig.js" import { User } from "./entity/User.js" async function main() { try { await AppDataSource.initialize() console.log("Database initialized") // 创建新用户 const user = new User() user.firstName = "John" user.lastName = "Doe" user.email = "john@example.com" // 保存用户 await AppDataSource.manager.save(user) console.log("User saved:", user) // 查询所有用户 const users = await AppDataSource.manager.find(User) console.log("All users:", users) await AppDataSource.destroy() } catch (error) { console.error("Error during Data Source initialization:", error) process.exit(1) } } await main()六个关键动作
AppDataSource.initialize():建立数据库连接。sqljs驱动在此阶段创建内存数据库;因为synchronize: true,同时会根据User实体的元数据创建user表;- 实例化实体:
new User()后逐个为属性赋值,id留空由数据库自增生成; AppDataSource.manager.save(user):通过 EntityManager 将实体写入数据库。INSERT 执行后 TypeORM 通过last_insert_rowid()回填id,因此控制台随后打印的user对象已包含完整主键;AppDataSource.manager.find(User):查询该表全部记录并映射回User实例数组;AppDataSource.destroy():关闭数据源。在 sql.js 场景下对应驱动disconnect()中的databaseConnection.close()(见 SqljsDriver.ts),释放内存数据库;- 错误处理:初始化或执行失败时打印错误并
process.exit(1),保证进程以非零码退出,便于 CI 或脚本感知失败。
顶层await main()是 ESM 特有的语法(package.json中"type": "module"使其合法),也是该示例作为 ESM 演示的标志性写法。
七、sql.js 内存数据库的特性与适用场景
README 特别强调了内存数据库的两点特性(见 playground/README.md):
- 数据是临时的:数据库完全驻留内存,进程退出后数据即被清空。因此该示例"非常适合测试与开发用途";
- 无原生依赖:sql.js 以 WASM 实现 SQLite,可运行在浏览器等受限环境中(如 Stackblitz),天然具备跨平台可移植性。
若需要持久化,应该怎么做?
README 只演示了内存模式,但结合仓库源码,可以从 SqljsEntityManager.ts 中看到 sql.js 驱动独有的持久化 API。当数据源使用sqljs驱动时,dataSource.manager实际是SqljsEntityManager,它额外提供:
loadDatabase(fileNameOrLocalStorageOrData):从文件(Node)或 localStorage 键(浏览器)加载数据库,或直接导入Uint8Array;saveDatabase(fileNameOrLocalStorage?):把当前内存数据库导出并写入文件/localStorage;不传参时使用options.location;exportDatabase():导出当前数据库为Uint8Array。
仓库测试 packages/typeorm/test/functional/driver/sqljs/save.test.ts 完整验证了这一闭环:先repository.save(post)写入数据,再dataSource.sqljsManager.saveDatabase(pathToSqlite)落盘,随后loadDatabase(pathToSqlite)重新加载,并能通过findOneBy({ title: "The second title" })读回数据。此外,location+autoSave组合则提供"变更即落盘"的持久化模式(startup.test.ts 验证了文件不存在时正常启动、首次写操作后自动生成文件)。
自动保存的触发机制
packages/typeorm/src/driver/sqljs/SqljsQueryRunner.ts 揭示了自动保存的内部机制:
- 每次执行非 SELECT 语句(insert/update/delete 等)后,查询运行器将内部
isDirty标记为true; - 在
release()(连接归还)与commitTransaction()(事务提交后)时调用flush(),进而触发driver.autoSave(); autoSave()(见 SqljsDriver.ts)会判断options.autoSave且当前不在事务中,再决定调用autoSaveCallback(export())还是save();- 事务进行中不会自动保存,因为
export()会中断当前事务。
仓库测试 packages/typeorm/test/functional/driver/sqljs/auto-save.test.ts 对该机制做了双向验证:开启autoSave时,插入、更新、删除共触发 8 次回调;关闭时一次也不触发。
八、查询执行与日志:sqljs驱动如何跑 SQL
playground 在ormconfig.ts中开启了logging: true,运行时会在终端看到 TypeORM 打印的 SQL 与参数。从 SqljsQueryRunner.ts 的query()方法可以看到 sql.js 驱动的执行细节:
- 参数校验:sql.js 驱动不支持命名占位符(
:name形式),只支持位置参数?,传入对象形式参数会抛出NamedPlaceholdersNotSupportedError; - 执行方式:
databaseConnection.prepare(query)预编译语句,随后statement.bind(parameters)绑定参数(undefined会被规范化为null),最后通过while (statement.step())逐行迭代取出结果——这是 sql.js 的语句执行模型; - 结果统计:
databaseConnection.getRowsModified()提供受影响行数(对应affected); - 慢查询日志:若配置了
maxQueryExecutionTime,执行耗时超过阈值会触发logQuerySlow。仓库测试 packages/typeorm/test/functional/driver/sqljs/query-execution-time.test.ts 用一条 50 万次递归 CTE 的慢查询验证了计时覆盖的是完整执行循环而非仅预编译阶段; - 事件广播:执行前后会通过
Broadcaster广播BeforeQuery/AfterQuery事件,订阅者可以在此挂载查询审计或性能监控。
九、延伸阅读
- playground/README.md:本文对应的原始演示文档;
- playground/src/index.ts、playground/src/ormconfig.ts、playground/src/entity/User.ts:本文剖析的三个核心源文件;
- packages/typeorm/src/driver/sqljs/SqljsDriver.ts:sql.js 驱动的连接、加载、保存与自增 id 回填实现;
- packages/typeorm/src/driver/sqljs/SqljsQueryRunner.ts:sql.js 驱动的查询执行与自动保存触发逻辑;
- packages/typeorm/src/entity-manager/SqljsEntityManager.ts:sql.js 专属的数据库导入/导出 API;
- packages/typeorm/src/driver/sqljs/SqljsDataSourceOptions.ts:
sqljs驱动的全部扩展配置项; - packages/typeorm/test/functional/driver/sqljs/:覆盖启动、加载、保存、自动保存与慢查询计时的完整测试集;
- packages/typeorm/src/cli-ts-node-esm.ts:
typeorm-ts-node-esmCLI 入口实现。
十、总结
playground项目虽然代码量极小,却浓缩了 TypeORM 在 ESM 工程中的完整最佳实践:"type": "module"与 ts-node loader 的模块化配置、experimentalDecorators+emitDecoratorMetadata的装饰器支撑、DataSource的声明式配置、synchronize自动建表、EntityManager 的增查操作,以及initialize/destroy的严谨生命周期管理。
同时,通过阅读仓库中sqljs驱动的源码与测试,可以清晰地看到它在"内存数据库"表象下的工程细节:外键默认开启、事务与自动保存的协调、last_insert_rowid()的主键回填、位置参数而非命名占位符的 SQL 绑定方式,以及SqljsEntityManager提供的持久化扩展点。若你的项目需要零依赖、可移植的数据库(单元测试、CI 环境、浏览器端演示),以本 playground 为起点,再按需接入location+autoSave或saveDatabase/loadDatabase,即可快速演进为一个可持久化、可迁移的 TypeORM SQLite 应用。
- 后端
- 数据库
- ORM
【免费下载链接】typeorm
TypeScript & JavaScript ORM for Node.js — supports PostgreSQL, MySQL, MariaDB, SQLite, SQL Server, Oracle, and more.
相关推荐
MikroORM 与 sql.js 实战:在浏览器与 Node.js 中运行纯内存 SQLite(WASM)
MikroORM 与 sql.js 实战:在浏览器与 Node.js 中运行纯内存 SQLite(WASM) 本文围绕 MikroORM 的 @mikro or
后端camembert-ner常见问题解决指南:10个开发者必知的技术要点
camembert ner常见问题解决指南:10个开发者必知的技术要点 camembert ner是一个基于camemBERT微调的命名实体识别 NER 模型,
Snowpack 快速上手:5 分钟跑通 ESM 无打包前端开发全流程
Snowpack 快速上手:5 分钟跑通 ESM 无打包前端开发全流程 导读 Snowpack 是一款 ESM 驱动的现代前端构建工具,其核心思路是"无打包开发
前端开发工具前端构建
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考