技能创建器
skill-creator
创建、改进和评估自定义技能,把专业流程转化为可重复使用的 AI 能力。
typeorm【数据与研究】TypeORM is an ORM for TypeScript and JavaScript that maps decorated classes to tables in PostgreSQL, MySQL, MariaDB, SQLite, SQL Server, Oracle and other databases. Use when a user asks to define entities and relations, query with repositories or QueryBuilder, set up a DataSource, generate and run migrations, or upgrade a project from TypeORM 0.3 to …
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
TypeORM maps TypeScript classes to database tables with decorators and gives you repositories, a QueryBuilder, transactions and a migration CLI on top. It supports PostgreSQL, MySQL/MariaDB, SQLite, SQL Server, Oracle, CockroachDB, SAP HANA, Spanner and MongoDB. This skill targets TypeORM 1.x (1.0 shipped in May 2026 and removed the APIs deprecated during 0.3); the 0.3 line still gets fixes under the npm legacy tag.
npm install typeorm reflect-metadata
npm install pg # driver: pg, mysql2, better-sqlite3, mssql, oracledb or mongodb
npm install -D typescript@5 ts-node @types/node # ts-node runs the CLI against .ts files
npx typeorm init --name blog-api --database postgres # or scaffold: package.json, tsconfig.json, src/data-source.ts, src/entities/User.ts
tsconfig.json must set "experimentalDecorators": true and "emitDecoratorMetadata": true. All access goes through one DataSource instance — the global createConnection() / getRepository() helpers no longer exist.
// src/data-source.ts
import "reflect-metadata";
import { DataSource } from "typeorm";
import { User, Post, Tag } from "./entities"; // one file or an index re-exporting each entity
export const AppDataSource = new DataSource({
type: "postgres",
url: process.env.DATABASE_URL,
entities: [User, Post, Tag],
migrations: [__dirname + "/migrations/*{.ts,.js}"],
synchronize: false, // schema changes go through migrations
poolSize: 20, // max connections in the pool
});
// At startup: await AppDataSource.initialize(); On shutdown: await AppDataSource.destroy();
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn,
ManyToOne, OneToMany, ManyToMany, JoinTable, Index, BeforeInsert } from "typeorm";
@Entity("users")
export class User {
@PrimaryGeneratedColumn("uuid")
id: string;
@Column({ length: 100 })
name: string;
@Index({ unique: true })
@Column()
email: string;
@Column({ select: false })
passwordHash: string;
@Column({ type: "enum", enum: ["user", "admin"], default: "user" })
role: "user" | "admin";
@Column({ type: "jsonb", nullable: true })
profile: { bio?: string; avatar?: string };
@OneToMany(() => Post, (post) => post.author)
posts: Post[];
@ManyToMany(() => Tag)
@JoinTable()
interests: Tag[];
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
@BeforeInsert()
normalizeEmail() {
this.email = this.email.toLowerCase().trim();
}
}
@Entity("posts")
export class Post {
@PrimaryGeneratedColumn()
id: number;
@Column()
title: string;
@Column({ type: "text" })
body: string;
@Column({ default: false })
published: boolean;
@ManyToOne(() => User, (user) => user.posts)
author: User;
@Column()
authorId: string;
@CreateDateColumn()
createdAt: Date;
}
@Entity("tags")
export class Tag { @PrimaryGeneratedColumn() id: number; @Column({ unique: true }) name: string; }
import { In, IsNull } from "typeorm";
import { AppDataSource as dataSource } from "./data-source";
const users = dataSource.getRepository(User);
// select and relations take objects — the string-array form was removed in 1.0
const admins = await users.find({
select: { id: true, name: true, posts: { id: true, title: true } },
relations: { posts: true },
where: { role: "admin" },
order: { name: "ASC" }, // with take + relations, order by a column that is in select
take: 20,
});
const maya = await users.findOneBy({ email: "maya.okafor@northwind.dev" }); // replaces findOneById
const picked = await users.findBy({ id: In(userIds) }); // replaces findByIds
const noProfile = await users.find({ where: { profile: IsNull() } }); // `profile: null` throws
const hasAdmins = await users.exists({ where: { role: "admin" } }); // replaces exist()
await users.upsert(
{ name: "Maya Okafor", email: "maya.okafor@northwind.dev", passwordHash: hash },
["email"], // conflict target
);
// Custom repository methods: extend() replaces @EntityRepository / getCustomRepository
const UserRepository = users.extend({
findByEmail(email: string) {
return this.findOneBy({ email: email.toLowerCase().trim() });
},
});
const posts = await dataSource
.getRepository(Post)
.createQueryBuilder("post")
.leftJoinAndSelect("post.author", "author")
.where("post.published = :published", { published: true })
.andWhere("author.role = :role", { role: "admin" })
.orderBy("post.createdAt", "DESC")
.skip(20)
.take(10)
.getMany();
// Subquery
const topAuthors = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.addSelect((subQuery) =>
subQuery
.select("COUNT(post.id)", "postCount")
.from(Post, "post")
.where("post.authorId = user.id"),
"postCount"
)
.orderBy("postCount", "DESC")
.limit(10)
.getRawMany();
// A select: false column is only loaded when asked for
const withHash = await dataSource.getRepository(User).createQueryBuilder("user")
.addSelect("user.passwordHash").where("user.email = :email", { email: loginEmail }).getOne();
// Transactions
await dataSource.transaction(async (manager) => {
const user = manager.create(User, { name: "Maya Okafor", email: "maya.okafor@northwind.dev", passwordHash: hash });
await manager.save(user);
const post = manager.create(Post, { title: "Zero-downtime migrations", body: draft, author: user });
await manager.save(post);
});
Plain npx typeorm only loads JavaScript. With a .ts data source, use the bundled ts-node wrapper (typeorm-ts-node-esm in ESM projects), or compile and point -d at the built file.
# Generate a migration from the difference between entities and the database
npx typeorm-ts-node-commonjs migration:generate src/migrations/AddPostSlug -d src/data-source.ts
# Create an empty migration to fill in by hand
npx typeorm migration:create src/migrations/BackfillPostSlugs
# Run pending migrations, list their status, revert the last one
npx typeorm-ts-node-commonjs migration:run -d src/data-source.ts
npx typeorm-ts-node-commonjs migration:show -d src/data-source.ts
npx typeorm-ts-node-commonjs migration:revert -d src/data-source.ts
# CI guard: exit code 1 when entities and schema have drifted, nothing is written
npx typeorm-ts-node-commonjs migration:generate src/migrations/Drift --check -d src/data-source.ts
# Production: run the compiled output (dist/ = your tsconfig outDir; `typeorm init` sets ./build)
npx tsc && npx typeorm migration:run -d dist/data-source.js
Run the official codemod first (npx @typeorm/codemod v1 --dry src/ to preview, then without --dry). It rewrites what it can and leaves TODO(typeorm-v1) comments where a decision is needed. The changes that break most projects:
| 0.3 | 1.x |
|---|---|
Connection, createConnection(), global getRepository() / getManager() | DataSource, dataSource.initialize(), dataSource.getRepository() |
findOneById(id), findByIds(ids), exist() | findOneBy({ id }), findBy({ id: In(ids) }), exists() |
select: ["id"], relations: ["posts"] | select: { id: true }, relations: { posts: true } |
where: { col: null } or undefined was ignored | throws TypeORMError; use IsNull() or set invalidWhereValuesBehavior |
type: "sqlite" (sqlite3), mysql package | type: "better-sqlite3", mysql2 |
@EntityRepository, getCustomRepository() | repository.extend({...}) |
TYPEORM_* environment variables, ormconfig.env | a data source file that reads process.env itself |
qb.printSql(), qb.onConflict() | qb.getSql(), orIgnore() / orUpdate() |
NestJS projects need @nestjs/typeorm 11.0.1 or later. Full list: https://typeorm.io/docs/releases/1.0/upgrading-from-0.3
User request: "Add an optional slug to posts and create the migration for it."
Add the column to the Post entity, then generate and apply the migration:
@Column({ type: "varchar", length: 160, nullable: true })
slug: string | null;
npx typeorm-ts-node-commonjs migration:generate src/migrations/AddPostSlug -d src/data-source.ts
npx typeorm-ts-node-commonjs migration:run -d src/data-source.ts
The first command writes src/migrations/1790847921068-AddPostSlug.ts (the prefix is the current timestamp) whose up() runs ALTER TABLE "posts" ADD "slug" character varying(160) and whose down() drops the column. The second prints Migration AddPostSlug1790847921068 has been executed successfully., and migration:show then lists it as [X]. If nothing changed, generate reports No changes in database schema were found.
User request: "Bump our billing service from typeorm 0.3.20 to the current version."
npx @typeorm/codemod v1 --dry src/ # lists the transforms that would apply
npx @typeorm/codemod v1 src/
npm install
The codemod turns this:
const repo = getRepository(Invoice);
const one = await repo.findOneById(invoiceId);
const list = await repo.find({ select: ["id", "total"], relations: ["customer"] });
into this, and bumps typeorm to ^1.0.0, @nestjs/typeorm to ^11.0.1 and replaces sqlite3 with better-sqlite3 in package.json:
// TODO(typeorm-v1): `dataSource` is not defined — inject or import your DataSource instance
const repo = dataSource.getRepository(Invoice);
const one = await repo.findOneBy({ id: invoiceId });
const list = await repo.find({ select: { id: true, total: true }, relations: { customer: true } });
Resolve each TODO(typeorm-v1) by hand and grep for leftover findOneById(, findByIds( and .exist( — the codemod skips them in a file whose only typeorm import was a removed global helper. Then run the test suite and look for Undefined value encountered in property … of a where condition — every hit is a query that used to return unfiltered rows.
synchronize: true in production; it can drop columns. Generate migrations and read the SQL before running it.null or undefined in find options throws instead of matching every row. Validate ids before querying; do not switch invalidWhereValuesBehavior back to ignore just to silence the error.:name parameters, never string interpolation.select or .select(["user.id", "user.name"]) to avoid fetching large columns.relations: { posts: true } or a join). Avoid eager: true on large graphs; Promise-based lazy relations are documented as experimental.dataSource.transaction() and use the manager it passes in, not the outer repositories.nullable: false relations join with INNER JOIN in 1.x — rows whose foreign key points nowhere disappear from results; fix the data or mark the relation nullable.typeorm-ts-node-* wrappers depend on ts-node, which crashes under TypeScript 7; keep TypeScript 5 for the CLI (what typeorm init pins) or run the CLI against compiled JavaScript.poolSize to match expected concurrency; driver-specific settings go in extra.