☰
TypeORM 官方 playground 实战指南:用 ESM + sql.js 跑通内存版 SQLite 全流程
2026/10/10 11:37:37 网站建设 项目流程
  • 后端
  • 数据库
  • ORM

【免费下载链接】typeorm

TypeScript & JavaScript ORM for Node.js — supports PostgreSQL, MySQL, MariaDB, SQLite, SQL Server, Oracle, and more.

项目地址:https://gitcode.com/GitHub_Trending/ty/typeorm
点击查看免费下载

导读

本文以仓库中的 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
typeormworkspace:*链接本仓库的 TypeORM 源码构建产物
@types/node^22.18.6Node.js 类型定义
@types/sql.js^1.4.9sql.js 类型定义
typescript^5.9.2TypeScript 编译器

运行步骤如下:

  1. 安装依赖:在仓库根目录执行pnpm install(或进入playground目录执行npm i,README 中给出的即为此命令);
  2. 启动应用:执行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 中:

配置项值含义
targetESNext编译目标为最新 ECMAScript
module/moduleResolutionESNext/NodeESM 模块体系
stricttrue开启严格类型检查
experimentalDecoratorstrue启用装饰器语法(实体定义必需)
emitDecoratorMetadatatrue输出装饰器元数据(TypeORM 反射实体属性类型必需)
rootDir/outDir./src/./dist编译输入输出目录
ts-node.esmtrue让 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 虽未使用全部,但理解它们有助于将此示例升级为可持久化的应用:

选项类型说明
databaseUint8Array打开连接时导入的数据库二进制内容,用于从备份恢复
driverany自定义 sql.js 驱动对象,默认require("sql.js")
sqlJsConfigany初始化 sql.js 时传入的配置
autoSaveboolean开启自动保存:每次数据库变更后自动写盘或回调
autoSaveCallbackFunction替代内部保存逻辑的回调,autoSave为true时生效
locationstringNode 环境下的文件路径或浏览器环境下的 localStorage 键
useLocalForageboolean浏览器环境下改用 localforage 异步读写 indexedDB
poolSizeneversqljs 驱动不支持连接池

一个关键约束在 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()

六个关键动作

  1. AppDataSource.initialize():建立数据库连接。sqljs驱动在此阶段创建内存数据库;因为synchronize: true,同时会根据User实体的元数据创建user表;
  2. 实例化实体:new User()后逐个为属性赋值,id留空由数据库自增生成;
  3. AppDataSource.manager.save(user):通过 EntityManager 将实体写入数据库。INSERT 执行后 TypeORM 通过last_insert_rowid()回填id,因此控制台随后打印的user对象已包含完整主键;
  4. AppDataSource.manager.find(User):查询该表全部记录并映射回User实例数组;
  5. AppDataSource.destroy():关闭数据源。在 sql.js 场景下对应驱动disconnect()中的databaseConnection.close()(见 SqljsDriver.ts),释放内存数据库;
  6. 错误处理:初始化或执行失败时打印错误并process.exit(1),保证进程以非零码退出,便于 CI 或脚本感知失败。

顶层await main()是 ESM 特有的语法(package.json中"type": "module"使其合法),也是该示例作为 ESM 演示的标志性写法。


七、sql.js 内存数据库的特性与适用场景

README 特别强调了内存数据库的两点特性(见 playground/README.md):

  1. 数据是临时的:数据库完全驻留内存,进程退出后数据即被清空。因此该示例"非常适合测试与开发用途";
  2. 无原生依赖: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 驱动的执行细节:

  1. 参数校验:sql.js 驱动不支持命名占位符(:name形式),只支持位置参数?,传入对象形式参数会抛出NamedPlaceholdersNotSupportedError;
  2. 执行方式:databaseConnection.prepare(query)预编译语句,随后statement.bind(parameters)绑定参数(undefined会被规范化为null),最后通过while (statement.step())逐行迭代取出结果——这是 sql.js 的语句执行模型;
  3. 结果统计:databaseConnection.getRowsModified()提供受影响行数(对应affected);
  4. 慢查询日志:若配置了maxQueryExecutionTime,执行耗时超过阈值会触发logQuerySlow。仓库测试 packages/typeorm/test/functional/driver/sqljs/query-execution-time.test.ts 用一条 50 万次递归 CTE 的慢查询验证了计时覆盖的是完整执行循环而非仅预编译阶段;
  5. 事件广播:执行前后会通过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.

项目地址:https://gitcode.com/GitHub_Trending/ty/typeorm
点击查看免费下载
上一篇:Pulumi迁移指南:从CloudFormation、Terraform迁移到Pulumi
下一篇:如何快速配置Bagisto多语言支持:打造全球化电商平台的完整指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询