- 后端
【免费下载链接】mikro-orm
TypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, MariaDB, MS SQL Server, PostgreSQL and SQLite/libSQL databases.
本篇技术指南基于 MikroORM 官方文档《Chapter 3: Project Setup》展开,面向已经完成实体定义(User/Article/Tag 等)的开发者,系统讲解如何将 MikroORM 接入 Fastify Web 服务器、使用 Vitest 进行接口测试,并深入剖析RequestContext请求上下文、EntityRepository仓储模式、SchemaGenerator模式同步、Migrator数据库迁移以及Seeder种子数据填充的完整落地流程。读完本文,你将掌握一套可复制的、具备单元测试与迁移能力的 MikroORM + Fastify 项目骨架。
一、为什么需要“项目级”装配
前两章中,你只是围绕实体定义做简单验证;从这一章开始,要构建真实可运行的服务。核心诉求有三:
- 为每个 HTTP 请求提供独立的
EntityManager上下文(Identity Map 的隔离,避免跨请求状态污染); - 为接口提供可自动化验证的测试通道(内存数据库 + 并行测试);
- 为 schema 演进提供安全手段(SchemaGenerator 快速原型 + Migrator 生产级迁移)。
本章选用 Fastify 作为 Web 服务器、Vitest 作为测试框架,二者都是 Node.js 生态中轻量、高性能且与 TypeScript 配合良好的选择。
二、用 Fastify 搭起服务骨架:app.ts与server.ts
2.1RequestContext:请求级 EntityManager 的核心机制
前面章节里,你通过手动fork()全局EntityManager来绕过全局上下文校验。而在 Web 服务器场景下,MikroORM 提供了官方助手类RequestContext,它能在每个请求内自动创建并持有独立的 EM fork。
核心原理(源码级):RequestContext内部使用 Node.js 核心模块AsyncLocalStorage(见 packages/core/src/utils/AsyncContext.ts)来跟踪异步调用链上的上下文。RequestContext.create(em, done)会为当前EntityManager执行em.fork({ useContext: true }),并将 fork 结果存入AsyncLocalStorage的 store(见 packages/core/src/utils/RequestContext.ts)。
EntityManager本身对RequestContext是“感知”的:所有涉及 Identity Map 的方法(如em.find()、em.getReference())在执行前都会调用em.getContext()。该方法优先返回TransactionContext中的 EM,其次通过config.get('context')回调(即RequestContext.getEntityManager())解析当前异步上下文中的 fork(见 packages/core/src/EntityManager.ts)。
因此,你在业务代码里写的:
const res = await orm.em.find(Book, {});实际解析为:
const res = await orm.em.getContext().find(Book, {}); // 进一步解析为 const res = await RequestContext.getEntityManager().find(Book, {});RequestContext.getEntityManager()从AsyncLocalStorage的 store 中取出当前请求对应的 fork(见 packages/core/src/utils/RequestContext.ts)。这样,EM fork 的创建(在中间件/hook 中)与使用(在任意深度的业务代码中)被彻底解耦。
从源码结构看,
RequestContext还提供了enter()(适用于没有next回调的框架中间件,如 Elysia)以及currentRequestContext()等方法;create()也支持传入EntityManager[]数组来同时管理多个命名上下文。
2.2 编写app.ts
在src目录新建app.ts,导出一个bootstrap函数,在其中初始化 ORM 并创建 Fastify 实例,注册onRequest钩子来为每个请求创建上下文:
import { MikroORM, RequestContext } from '@mikro-orm/core'; import { fastify } from 'fastify'; import config from './mikro-orm.config.js'; export async function bootstrap(port = 3001) { const orm = await MikroORM.init(config); const app = fastify(); // 为每个请求创建独立的 EM 上下文 app.addHook('onRequest', (request, reply, done) => { RequestContext.create(orm.em, done); }); // 应用关闭时同步关闭数据库连接 app.addHook('onClose', async () => { await orm.close(); }); // 在此注册路由 // ... const url = await app.listen({ port }); return { app, url }; }注意RequestContext.create(orm.em, done)中的done是 Fastify 钩子的回调——AsyncLocalStorage.run(ctx, next)会在这个回调内执行后续的请求处理链路,从而让整个请求的生命周期都处于该上下文中。
2.3 编写server.ts启动入口
用以下内容替换之前实验用的server.ts:
import { bootstrap } from './app.js'; try { const { url } = await bootstrap(); console.log(`server started at ${url}`); } catch (e) { console.error(e); }重新执行npm start,预期看到类似输出:
[info] MikroORM version: 7.0.0 [discovery] ORM entity discovery started [discovery] - processing entity User [discovery] - processing entity Article [discovery] - processing entity Tag [discovery] - processing entity BaseEntity [discovery] - entity discovery finished, found 5 entities, took 5 ms [info] MikroORM successfully connected to database sqlite.db server started at http://127.0.0.1:3001按CTRL + C即可停止服务。
三、第一个接口:GET /article与findAndCount
3.1 为什么用findAndCount而不是count+find
接口需求是:返回文章分页列表,并同时给出文章总数。虽然可以先用em.count()再em.find(),但 MikroORM 提供了更贴合的em.findAndCount()——它正是为“分页列表 + 总数”场景设计。
从 packages/core/src/EntityManager.ts 的源码看,findAndCount内部会先执行一次tryFlush(确保未提交的变更进入数据库再计数),然后通过Promise.all([em.find(...), em.count(...)])并行执行查询,并显式将flushMode设为'commit'以避免重复自动 flush。返回类型为[实体数组, 总数]的元组。
app.get('/article', async request => { const { limit, offset } = request.query as { limit?: number; offset?: number }; const [items, total] = await orm.em.findAndCount(ArticleSchema, {}, { limit, offset, }); return { items, total }; });limit/offset直接透传给查询选项即可完成分页。
四、简易 DI 容器:db.ts与initORM()
在写测试之前,先做一次面向未来的重构:把 ORM 初始化收敛到一个“简易依赖注入(DI)容器”中。
4.1 为什么不用顶层export const orm = await MikroORM.init(...)
虽然借助 top-level await 可以直接初始化并导出实例,但后续做测试时往往需要覆盖部分配置(如切换内存数据库、关闭 debug 日志)。封装一个initORM(options?)函数,既能缓存实例避免重复初始化,又能在首次初始化前注入覆盖项。
4.2 从驱动包导入类型化导出
注意:EntityManager、EntityRepository、MikroORM、Options都应从驱动包(如@mikro-orm/sqlite)导入,而不是从@mikro-orm/core。原因是@mikro-orm/core只提供驱动无关的基类,而 SQL 驱动包会导出扩展后的SqlEntityManager(提供createQueryBuilder()等 SQL 专属方法),并以EntityManager别名再导出,从而让 TypeScript 自动获得与SqliteDriver绑定的类型,无需手写泛型。
从源码结构看,
SqlEntityManager定义于@mikro-orm/sql包,并在每个依赖它的 SQL 驱动包中被再导出。运行时orm.em实际就是SqlEntityManager实例,你可以用console.log(orm.em)验证。EntityRepository/SqlEntityRepository同理。
import { EntityManager, EntityRepository, MikroORM, Options } from '@mikro-orm/sqlite'; import { UserSchema, type User } from './modules/user/user.entity.js'; import { ArticleSchema, type IArticle } from './modules/article/article.entity.js'; import { TagSchema, type ITag } from './modules/article/tag.entity.js'; import config from './mikro-orm.config.js'; export interface Services { orm: MikroORM; em: EntityManager; article: EntityRepository<IArticle>; user: EntityRepository<User>; tag: EntityRepository<ITag>; } let cache: Services; export function initORM(options?: Partial<Options>): Services { if (cache) { return cache; } const orm = new MikroORM({ ...config, ...options, }); // 先缓存再返回,保证后续调用拿到同一实例 return cache = { orm, em: orm.em, article: orm.em.getRepository(ArticleSchema), user: orm.em.getRepository(UserSchema), tag: orm.em.getRepository(TagSchema), }; }4.3 改造app.ts使用 DI 容器
import { RequestContext } from '@mikro-orm/core'; import { fastify } from 'fastify'; import { initORM } from './db.js'; export async function bootstrap(port = 3001) { const db = initORM(); const app = fastify(); app.addHook('onRequest', (request, reply, done) => { RequestContext.create(db.em, done); }); app.addHook('onClose', async () => { await db.orm.close(); }); app.get('/article', async request => { const { limit, offset } = request.query as { limit?: number; offset?: number }; const [items, total] = await db.article.findAndCount({}, { limit, offset, }); return { items, total }; }); const url = await app.listen({ port }); return { app, url }; }五、什么是EntityRepository
仓储是EntityManager之上的一层“薄封装”:
- 它充当扩展点:可以添加自定义方法,甚至改写现有方法;
- 默认实现只是把调用转发给底层
EntityManager; - 它携带实体类型,因此调用
find/findOne时无需重复传入实体类; - 不存在“flush 一个仓储”——
repo.flush()只是em.flush()的快捷方式,MikroORM 始终 flush 整个 Unit of Work,而不是某个仓储代表的单个实体。
在 DI 容器中一次性取出article/user/tag等仓储,后续业务代码即可直接db.article.findAndCount(...)。
六、用 Vitest 测试接口
6.1 测试策略:app.inject()+ 内存数据库
Fastify 提供了app.inject()方法,可以在不真正监听端口的情况下模拟 HTTP 请求,非常适合集成测试。但直接复用生产数据库显然不合适,因此测试要走“独立实例”路线:
- 每个测试用例使用独立的 SQLite 内存数据库(
dbName: ':memory:'); - 每个测试用例运行在独立的端口上,从而支持并行执行;
- 通过
beforeAll初始化、afterAll关闭,避免进程悬挂。
6.2 编写测试工具test/utils.ts
在test目录创建utils.ts(不带.test.ts后缀,避免被 Vitest 当作测试文件),导出initTestApp:一次性完成“覆盖配置初始化 ORM → 创建 schema → 启动 Fastify”。
import { bootstrap } from '../src/app.js'; import { initORM } from '../src/db.js'; import config from '../src/mikro-orm.config.js'; export async function initTestApp(port: number) { // 创建并缓存所有 ORM 服务 const { orm } = initORM({ // 先继承主配置 ...config, // 关闭调试输出,避免污染测试日志 debug: false, // 使用内存数据库,便于测试并行化 dbName: ':memory:', }); // 创建 schema 以便数据库可用 await orm.schema.create(); const { app } = await bootstrap(port); return app; }6.3 编写测试用例test/article.test.ts
import { afterAll, beforeAll, expect, test } from 'vitest'; import { FastifyInstance } from 'fastify'; import { initTestApp } from './utils.js'; let app: FastifyInstance; beforeAll(async () => { // 使用不同端口以支持并行测试 app = await initTestApp(30001); }); afterAll(async () => { // 只关闭 fastify app —— 它会通过 onClose 钩子自动关闭数据库连接 await app.close(); }); test('list all articles', async () => { // 通过 app.inject() 模拟 http 请求 const res = await app.inject({ method: 'get', url: '/article', }); // 断言响应成功 expect(res.statusCode).toBe(200); // 断言响应结构符合预期 expect(res.json()).toMatchObject({ items: [], total: 0, }); });目前内存库为空,因此
/article返回空数组与total: 0;稍后接入 Seeder 后会填充数据。beforeAll/afterAll的配对很重要:app.close()会触发onClose钩子进而调用orm.close(),否则进程会悬挂。
执行npm test,预期输出:
✓ test/article.test.ts (1) Test Files 1 passed (1) Tests 1 passed (1) Start at 15:56:41 Duration 876ms (transform 264ms, setup 0ms, collect 300ms, tests 147ms) PASS Waiting for file changes... press h to show help, press q to quit6.4 单元测试的注意事项
即使某些单元测试不需要连接数据库,也不要轻易跳过MikroORM初始化。init阶段除了建立连接,更重要的是元数据发现(discovery):ORM 会检查所有实体定义,为各类元数据选项设置默认值(主要是命名策略与双向关系的补全)。而发现阶段是传播(propagation)机制能够工作的前提(详见 docs/versioned_docs/version-7.1/guide/../propagation.md)。因此即便只是new MikroORM({...})而不连库,也要保留这一初始化过程。
七、用 Seeder 填充测试数据
7.1 安装与注册
npm install @mikro-orm/seeder然后在 ORM 配置中注册SeedManager扩展,之后即可通过orm.seeder使用:
import { defineConfig } from '@mikro-orm/sqlite'; import { SeedManager } from '@mikro-orm/seeder'; export default defineConfig({ // ... extensions: [SeedManager], });除
SeedManager外,MikroORM 还提供SchemaGenerator、Migrator、EntityGenerator等扩展。SchemaGenerator(以及MongoSchemaGenerator)因不依赖任何第三方包,会被自动注册,无需手动加入extensions。
从 packages/seeder/src/SeedManager.ts 源码可见,SeedManager.register(orm)会把扩展注册为@mikro-orm/seeder;构造时它会对em执行一次fork(),并强制设置persistOnCreate: true——这正是下面em.create()无需手动persist的原因。
7.2 生成 Seeder 骨架
npx mikro-orm seeder:create test该命令会在src/seeders目录生成TestSeeder.ts,初始骨架如下(该模板由SeedManager.generate生成,见 packages/seeder/src/SeedManager.ts):
import type { EntityManager } from '@mikro-orm/core'; import { Seeder } from '@mikro-orm/seeder'; export class TestSeeder extends Seeder { async run(em: EntityManager): Promise<void> {} }7.3 填充数据
前面章节提到过em.create():它会先调用em.persist(entity)再返回创建的实体,所以只需调用一次即可完成“创建 + 入队持久化”。填充 3 篇文章及其标签:
export class TestSeeder extends Seeder { async run(em: EntityManager): Promise<void> { em.create(UserSchema, { fullName: 'Foo Bar', email: 'foo@bar.com', password: 'password123', articles: [ { title: 'title 1/3', description: 'desc 1/3', text: 'text text text 1/3', tags: [{ id: 1, name: 'foo1' }, { id: 2, name: 'foo2' }], }, { title: 'title 2/3', description: 'desc 2/3', text: 'text text text 2/3', tags: [{ id: 2, name: 'foo2' }], }, { title: 'title 3/3', description: 'desc 3/3', text: 'text text text 3/3', tags: [{ id: 2, name: 'foo2' }, { id: 3, name: 'foo3' }], }, ], }); } }从 packages/seeder/src/SeedManager.ts 源码可见,
seed()会依次实例化 Seeder、执行其run(em)、随后em.flush()并em.clear()——即每个 Seeder 运行完自动落库并清空上下文。Seeder 同样适用于生产库初始化(如默认标签集、初始管理员),可构建 Seeder 层级或逐个调用。
7.4 在测试初始化中执行 Seeder
修改test/utils.ts,在orm.schema.create()之后执行种子:
await orm.schema.create(); await orm.seeder.seed(TestSeeder);7.5 更新测试断言
现在内存库中有 3 篇文章,调整断言:
expect(res.json()).toMatchObject({ items: [ { author: 1, slug: 'title-13', title: 'title 1/3' }, { author: 1, slug: 'title-23', title: 'title 2/3' }, { author: 1, slug: 'title-33', title: 'title 3/3' }, ], total: 3, });再次运行npm test验证即可。
八、SchemaGenerator:从实体元数据到 DDL
8.1 职责与用法
SchemaGenerator负责基于实体元数据生成 SQL(即把实体定义翻译成 DDL),并且能够读取当前数据库 schema 并与元数据比对,产出使二者同步所需的查询语句。它既可编程调用,也可通过 CLI 使用。
编程方式:
// 仅获取查询语句(不执行) const diff = await orm.schema.getUpdateSchemaSQL(); console.log(diff); // 直接执行同步查询 await orm.schema.update();
orm.schema.update()可以实现类似 TypeORMsynchronize: true的行为——在应用初始化/bootstrap 后立即调用即可。但请牢记:这种方式可能是破坏性的,强烈不建议在生产环境使用;执行前务必先检查SchemaGenerator生成的查询内容。
CLI 方式(用--run替换--dump即真正执行):
npx mikro-orm schema:create --dump # 输出创建 schema 的 SQL npx mikro-orm schema:update --dump # 输出更新 schema 的 SQL npx mikro-orm schema:drop --dump # 输出删除 schema 的 SQL8.2 同步生产库示例
测试中一直使用内存库,项目根目录的sqlite.db生产库可能已与实体不同步。先--dump(或-d)查看将要执行的 SQL,确认无误后再--run(或-r)真正执行:
# 先检查生成的 SQL npx mikro-orm schema:update --dump # 确认无误后同步 schema npx mikro-orm schema:update --run若生成的查询有问题,可以先用
schema:drop --run清空后重新创建 schema。
SchemaGenerator在原型验证和测试场景(需要大量“最新 schema”的数据库)中非常顺手,但对真实生产库有风险——这正是下一节 Migrations 的用武之地。
九、Migrations:生产级的 schema 演进
9.1 安装与注册
npm install @mikro-orm/migrations对 MongoDB 请改用
@mikro-orm/migrations-mongodb包。
注册Migrator扩展:
import { defineConfig } from '@mikro-orm/sqlite'; import { SeedManager } from '@mikro-orm/seeder'; import { Migrator } from '@mikro-orm/migrations'; export default defineConfig({ // ... extensions: [SeedManager, Migrator], });Migrator类在 packages/migrations/src/Migrator.ts 中定义,它基于AbstractMigrator实现:内部持有SqlSchemaGenerator(用于比对差异),并根据options.emit选择TSMigrationGenerator或JSMigrationGenerator生成迁移文件。
MikroORM 对迁移的内置支持包括:
- 根据当前 schema 差异生成迁移;
- 管理迁移执行;
- 默认情况下,每个迁移在独立事务中执行,全部迁移再被包裹进一个主事务——任一迁移失败则整体回滚。
9.2 创建首个迁移
npx mikro-orm migration:create如果此前刚执行过schema:update --run,schema 已是最新,会看到:
No changes required, schema is up-to-date此时有两种选择:先schema:drop清库,或采用**初始迁移(initial migration)**这一破坏性更小的方案。
9.3 初始迁移--initial
--initial标志适用于“schema 已存在但想开始使用迁移”的场景:它保留现有 schema,仅基于实体元数据生成首个迁移。使用前提是 schema 为空或完全最新;若 schema 已存在,生成的迁移会被自动标记为已执行,否则需要手动migration:up执行。
初始迁移只有在没有任何已生成或已执行的迁移时才能创建。如果是全新项目、尚无 schema,则无需
--initial,普通迁移即可。
npx mikro-orm migration:create --initial这会在src/migrations目录生成初始迁移,内容来自schema:create的查询;由于 schema 已同步,迁移会自动标记为已执行。
9.4 迁移类结构
生成的迁移继承自@mikro-orm/migrations的抽象类Migration:
import { Migration } from '@mikro-orm/migrations'; export class Migration20220913202829 extends Migration { async up(): Promise<void> { this.addSql('create table `tag` (`id` integer not null primary key autoincrement, `created_at` datetime not null, `updated_at` datetime not null, `name` text not null);'); // ... } }要点:
- 默认只生成
up();如需回滚,可实现down()(默认实现直接抛错); - 自动生成 down 迁移:除 SQLite 驱动(受限于其能力)外,其他驱动会自动生成 down 迁移——但初始迁移除外(出于安全考虑);
- 在
up()/down()中可通过this.execute('...')执行查询,它会与迁移的其余部分处于同一事务;this.addSql()也接受原生 QueryBuilder 实例或raw()SQL 片段。
更多迁移细节见 docs/versioned_docs/version-7.1/guide/../migrations。
9.5 加一个实体来验证迁移:Comment
在src/modules/article/comment.entity.ts新增Comment实体(使用defineEntity与p属性构建器):
import { defineEntity, type InferEntity, p } from '@mikro-orm/core'; import { ArticleSchema } from './article.entity.js'; import { UserSchema } from '../user/user.entity.js'; import { BaseSchema } from '../common/base.entity.js'; export const CommentSchema = defineEntity({ name: 'Comment', extends: BaseSchema, properties: { text: p.string(), article: () => p.manyToOne(ArticleSchema).ref(), author: () => p.manyToOne(UserSchema).ref(), }, }); export type IComment = InferEntity<typeof CommentSchema>;同时在Article实体上补充 OneToMany 反向侧:
comments: () => p.oneToMany(CommentSchema).mappedBy('article').eager().orphanRemoval(),这里用到了两个新构建器方法:
.eager():自动填充该关系,等价于显式populate: ['comments'];.orphanRemoval():一种特殊级联——从该集合中移除的实体将被从数据库删除,而不是仅仅脱离关系(把外键置空)。
别忘了把comment仓储加进 DI 容器:
export interface Services { orm: MikroORM; em: EntityManager; user: UserRepository; article: EntityRepository<IArticle>; comment: EntityRepository<IComment>; // 新增 tag: EntityRepository<ITag>; } export function initORM(options?: Partial<Options>): Services { // ... return cache = { orm, em: orm.em, user: orm.em.getRepository(UserSchema), article: orm.em.getRepository(ArticleSchema), comment: orm.em.getRepository(CommentSchema), // 新增 tag: orm.em.getRepository(TagSchema), }; }9.6 迁移相关 CLI 命令全流程
# 基于 schema 差异创建新迁移 npx mikro-orm migration:create # 列出待执行迁移 npx mikro-orm migration:pending # 执行待执行的迁移 npx mikro-orm migration:up # 列出已执行的迁移 npx mikro-orm migration:list预期输出示例:
npx mikro-orm migration:create Migration20220913205718.ts successfully creatednpx mikro-orm migration:pending ┌─────────────────────────┐ │ Name │ ├─────────────────────────┤ │ Migration20220913205718 │ └─────────────────────────┘npx mikro-orm migration:up Processing 'Migration20220913205718' Applied 'Migration20220913205718' Successfully migrated up to the latest versionnpx mikro-orm migration:list ┌─────────────────────────┬──────────────────────────┐ │ Name │ Executed at │ ├─────────────────────────┼──────────────────────────┤ │ Migration20220913202829 │ 2022-09-13T18:57:12.000Z │ │ Migration20220913205718 │ 2022-09-13T18:57:27.000Z │ └─────────────────────────┴──────────────────────────┘9.7 迁移快照(Migration snapshots)
创建新迁移时,MikroORM 会自动把目标 schema 快照保存到迁移目录。之后再次创建迁移时会基于该快照计算差异,而非当前数据库 schema——这意味着在尚未执行 pending 迁移的情况下再次创建迁移,仍能得到正确的 schema diff。
快照应与普通迁移文件一样纳入版本控制。
如需关闭快照功能,可在 ORM 配置中设置migrations.snapshot: false。
9.8 应用启动时自动执行迁移
在生产环境,希望在应用开始接收连接前完成迁移,因此把它放进bootstrap,放在 ORM 初始化之后:
export async function bootstrap(port = 3001, migrate = true) { const db = initORM(); if (migrate) { // 同步 schema await db.orm.migrator.up(); } // ... }必须条件化执行:生产库走Migrator,而测试库直接使用SchemaGenerator+Seeder。因此测试工具中要传入false:
export async function initTestApp(port: number) { const { orm } = initORM({ ... }); await orm.schema.create(); await orm.seeder.seed(TestSeeder); const { app } = await bootstrap(port, false); // <-- 测试不执行迁移 return app; }十、Checkpoint 3:阶段性成果
至此你已完成:
- 4 个实体:
User、Article、Tag,以及新增的Comment; - 一个可运行的 Web 应用:带
GET /article分页接口; - 一个基础测试用例:通过
app.inject()验证接口响应; - 迁移与种子数据基础设施:
Migrator负责 schema 演进,Seeder负责数据填充。
注:测试中使用的
:memory:是 SQLite 提供的内存数据库特性,通过特殊库名启用,每个测试用例独立、互不干扰,天然支持并行执行。
关键经验回顾
| 关注点 | 方案 | 依据 |
|---|---|---|
| 请求级上下文隔离 | RequestContext+ FastifyonRequest钩子 | packages/core/src/utils/RequestContext.ts |
| 分页 + 计数 | em.findAndCount()(内部并行find+count) | packages/core/src/EntityManager.ts |
| 类型化 EM/仓储 | 从驱动包导入EntityManager/EntityRepository(SqlEntityManager别名) | @mikro-orm/sqlite导出 |
| 测试隔离 | dbName: ':memory:'+ 独立端口 +app.inject() | test/utils.ts |
| schema 快速同步 | orm.schema.create()/update()/drop()(仅限原型/测试) | 本章 CLI 示例 |
| 生产 schema 演进 | Migrator扩展 +migration:create/up/pending/list | packages/migrations/src/Migrator.ts |
| 数据填充 | SeedManager扩展 +orm.seeder.seed() | packages/seeder/src/SeedManager.ts |
下一章将继续深入高级特性与类型安全实践,进一步打磨这套项目骨架。
- 后端
【免费下载链接】mikro-orm
TypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, MariaDB, MS SQL Server, PostgreSQL and SQLite/libSQL databases.
相关推荐
MikroORM 项目实战搭建指南:Fastify 服务、RequestContext、测试、Seeder 与迁移(Chapter 3: Project Setup)
MikroORM 项目实战搭建指南:Fastify 服务、RequestContext、测试、Seeder 与迁移(Chapter 3: Project Set
后端MikroORM 实战:用 Fastify + Vitest 搭建项目、测试接口并管理 Schema 与迁移
MikroORM 实战:用 Fastify + Vitest 搭建项目、测试接口并管理 Schema 与迁移 本篇指南承接 MikroORM 的实体定义章节,完
后端MikroORM 项目搭建实战:Fastify + Vitest 下的请求上下文、Seeder 与迁移管理
MikroORM 项目搭建实战:Fastify + Vitest 下的请求上下文、Seeder 与迁移管理 本篇技术指南基于 MikroORM 官方入门指南第三
后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考