下面给一个完整示例,把 TypeORM 的主线一次串起来。
#1. data-source.ts
import "reflect-metadata";
import { DataSource } from "typeorm";
import { AuditLog } from "./entities/AuditLog";
import { Post } from "./entities/Post";
import { User } from "./entities/User";
export const AppDataSource = new DataSource({
type: "postgres",
host: "localhost",
port: 5432,
username: "city",
password: "city",
database: "city_blog",
synchronize: false,
logging: false,
entities: [User, Post, AuditLog],
migrations: ["src/migrations/*.{ts,js}"],
});#2. entities/User.ts
import {
Column,
CreateDateColumn,
Entity,
OneToMany,
PrimaryGeneratedColumn,
} from "typeorm";
import { Post } from "./Post";
@Entity({ name: "users" })
export class User {
@PrimaryGeneratedColumn()
id!: number;
@Column({ unique: true })
email!: string;
@Column({ length: 50 })
nickname!: string;
@Column({ default: true })
isActive!: boolean;
@OneToMany(() => Post, (post) => post.author)
posts!: Post[];
@CreateDateColumn()
createdAt!: Date;
}#3. entities/Post.ts
import {
Column,
CreateDateColumn,
Entity,
ManyToOne,
PrimaryGeneratedColumn,
} from "typeorm";
import { User } from "./User";
@Entity({ name: "posts" })
export class Post {
@PrimaryGeneratedColumn()
id!: number;
@Column()
title!: string;
@Column({ type: "text" })
content!: string;
@Column({ default: false })
published!: boolean;
@ManyToOne(() => User, (user) => user.posts, {
nullable: false,
onDelete: "CASCADE",
})
author!: User;
@CreateDateColumn()
createdAt!: Date;
}#4. entities/AuditLog.ts
import { Column, CreateDateColumn, Entity, PrimaryGeneratedColumn } from "typeorm";
@Entity({ name: "audit_logs" })
export class AuditLog {
@PrimaryGeneratedColumn()
id!: number;
@Column()
action!: string;
@Column()
targetType!: string;
@Column()
targetId!: number;
@CreateDateColumn()
createdAt!: Date;
}#5. 业务代码:创建作者、发布文章、记录日志
import { AppDataSource } from "./data-source";
import { AuditLog } from "./entities/AuditLog";
import { Post } from "./entities/Post";
import { User } from "./entities/User";
async function main() {
await AppDataSource.initialize();
const userRepository = AppDataSource.getRepository(User);
const postRepository = AppDataSource.getRepository(Post);
let author = await userRepository.findOneBy({
email: "lin@city.dev",
});
if (!author) {
author = userRepository.create({
email: "lin@city.dev",
nickname: "Lin",
});
await userRepository.save(author);
}
const draft = postRepository.create({
title: "TypeORM 是施工队,不是魔法棒",
content: "先把实体和关系建清楚,再谈查询与迁移。",
author,
});
await postRepository.save(draft);
const publishedPost = await AppDataSource.transaction(async (manager) => {
draft.published = true;
await manager.save(draft);
const log = manager.create(AuditLog, {
action: "PUBLISH_POST",
targetType: "Post",
targetId: draft.id,
});
await manager.save(log);
return manager
.getRepository(Post)
.createQueryBuilder("post")
.leftJoinAndSelect("post.author", "author")
.where("post.id = :id", { id: draft.id })
.getOneOrFail();
});
console.log("发布后的文章:", publishedPost);
const latestPosts = await postRepository
.createQueryBuilder("post")
.leftJoinAndSelect("post.author", "author")
.where("post.published = :published", { published: true })
.andWhere("author.isActive = :isActive", { isActive: true })
.orderBy("post.createdAt", "DESC")
.take(5)
.getMany();
console.log("最近 5 篇已发布文章:", latestPosts);
await AppDataSource.destroy();
}
main().catch(async (error) => {
console.error(error);
if (AppDataSource.isInitialized) {
await AppDataSource.destroy();
}
process.exit(1);
});这个示例里已经串起了:
DataSourceEntityColumnRelationRepositoryQueryBuilderTransaction
还差最后一块:Migration
#6. 一份配套 migration
import { MigrationInterface, QueryRunner } from "typeorm";
export class InitBlogTables1710000000001 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
CREATE TABLE "users" (
"id" SERIAL PRIMARY KEY,
"email" VARCHAR NOT NULL UNIQUE,
"nickname" VARCHAR(50) NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP NOT NULL DEFAULT now()
)
`);
await queryRunner.query(`
CREATE TABLE "posts" (
"id" SERIAL PRIMARY KEY,
"title" VARCHAR NOT NULL,
"content" TEXT NOT NULL,
"published" BOOLEAN NOT NULL DEFAULT false,
"createdAt" TIMESTAMP NOT NULL DEFAULT now(),
"authorId" INTEGER NOT NULL REFERENCES "users"("id") ON DELETE CASCADE
)
`);
await queryRunner.query(`
CREATE TABLE "audit_logs" (
"id" SERIAL PRIMARY KEY,
"action" VARCHAR NOT NULL,
"targetType" VARCHAR NOT NULL,
"targetId" INTEGER NOT NULL,
"createdAt" TIMESTAMP NOT NULL DEFAULT now()
)
`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE "audit_logs"`);
await queryRunner.query(`DROP TABLE "posts"`);
await queryRunner.query(`DROP TABLE "users"`);
}
}这就是完整施工链:
实体蓝图
▼
DataSource 接入工地
▼
Repository / QueryBuilder 干活
▼
Transaction 保证联动施工
▼
Migration 管理结构改造