返回博客列表
后端
2025年7月19日
16 分钟阅读

Nest.js 数据库集成实战:TypeORM 与 Prisma

Nest.js 数据库集成实战:TypeORM 与 Prisma

数据库是现代 Web 应用的核心。Nest.js 提供了多种数据库集成方案,最流行的是 TypeORM 和 Prisma。本文将介绍如何在 Nest.js 中集成这两种 ORM,实现数据的持久化操作。

TypeORM 集成

安装依赖

npm install @nestjs/typeorm typeorm mysql2
# 或使用 PostgreSQL
npm install @nestjs/typeorm typeorm pg
# 或使用 SQLite
npm install @nestjs/typeorm typeorm sqlite3

配置 TypeORM

// app.module.ts
import { Module } from "@nestjs/common";
import { TypeOrmModule } from "@nestjs/typeorm";
 
@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: "mysql", // 数据库类型
      host: "localhost",
      port: 3306,
      username: "root",
      password: "password",
      database: "testdb",
      entities: [__dirname + "/**/*.entity{.ts,.js}"], // 实体文件路径
      synchronize: true, // 开发环境自动同步,生产环境应设为 false
    }),
  ],
})
export class AppModule {}

定义实体(Entity)

// users/entities/user.entity.ts
import { Entity, Column, PrimaryGeneratedColumn } from "typeorm";
 
@Entity("users") // 表名
export class User {
  @PrimaryGeneratedColumn() // 主键,自增
  id: number;
 
  @Column({ type: "varchar", length: 100 })
  name: string;
 
  @Column({ type: "varchar", length: 255, unique: true })
  email: string;
 
  @Column({ type: "varchar", length: 255 })
  password: string;
 
  @Column({ type: "timestamp", default: () => "CURRENT_TIMESTAMP" })
  createdAt: Date;
 
  @Column({ type: "timestamp", nullable: true })
  updatedAt: Date;
}

创建 Repository

// users/users.module.ts
import { Module } from "@nestjs/common";
import { TypeOrmModule } from "@nestjs/typeorm";
import { User } from "./entities/user.entity";
import { UsersController } from "./users.controller";
import { UsersService } from "./users.service";
 
@Module({
  imports: [TypeOrmModule.forFeature([User])], // 注册实体
  controllers: [UsersController],
  providers: [UsersService],
})
export class UsersModule {}

在服务中使用 Repository

// users/users.service.ts
import { Injectable } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { Repository } from "typeorm";
import { User } from "./entities/user.entity";
import { CreateUserDto } from "./dto/create-user.dto";
import { UpdateUserDto } from "./dto/update-user.dto";
 
@Injectable()
export class UsersService {
  constructor(
    @InjectRepository(User)
    private usersRepository: Repository<User>
  ) {}
 
  async create(createUserDto: CreateUserDto): Promise<User> {
    const user = this.usersRepository.create(createUserDto);
    return await this.usersRepository.save(user);
  }
 
  async findAll(): Promise<User[]> {
    return await this.usersRepository.find();
  }
 
  async findOne(id: number): Promise<User> {
    return await this.usersRepository.findOne({ where: { id } });
  }
 
  async findByEmail(email: string): Promise<User> {
    return await this.usersRepository.findOne({ where: { email } });
  }
 
  async update(id: number, updateUserDto: UpdateUserDto): Promise<User> {
    await this.usersRepository.update(id, updateUserDto);
    return this.findOne(id);
  }
 
  async remove(id: number): Promise<void> {
    await this.usersRepository.delete(id);
  }
}

实体关系

一对一关系

// users/entities/user.entity.ts
import {
  Entity,
  Column,
  PrimaryGeneratedColumn,
  OneToOne,
  JoinColumn,
} from "typeorm";
import { Profile } from "../../profiles/entities/profile.entity";
 
@Entity("users")
export class User {
  @PrimaryGeneratedColumn()
  id: number;
 
  @Column()
  name: string;
 
  @OneToOne(() => Profile, (profile) => profile.user)
  @JoinColumn()
  profile: Profile;
}
 
// profiles/entities/profile.entity.ts
import { Entity, Column, PrimaryGeneratedColumn, OneToOne } from "typeorm";
import { User } from "../../users/entities/user.entity";
 
@Entity("profiles")
export class Profile {
  @PrimaryGeneratedColumn()
  id: number;
 
  @Column()
  bio: string;
 
  @OneToOne(() => User, (user) => user.profile)
  user: User;
}

一对多关系

// users/entities/user.entity.ts
import { Entity, Column, PrimaryGeneratedColumn, OneToMany } from "typeorm";
import { Post } from "../../posts/entities/post.entity";
 
@Entity("users")
export class User {
  @PrimaryGeneratedColumn()
  id: number;
 
  @OneToMany(() => Post, (post) => post.author)
  posts: Post[];
}
 
// posts/entities/post.entity.ts
import {
  Entity,
  Column,
  PrimaryGeneratedColumn,
  ManyToOne,
  JoinColumn,
} from "typeorm";
import { User } from "../../users/entities/user.entity";
 
@Entity("posts")
export class Post {
  @PrimaryGeneratedColumn()
  id: number;
 
  @Column()
  title: string;
 
  @ManyToOne(() => User, (user) => user.posts)
  @JoinColumn({ name: "authorId" })
  author: User;
}

多对多关系

// posts/entities/post.entity.ts
import {
  Entity,
  Column,
  PrimaryGeneratedColumn,
  ManyToMany,
  JoinTable,
} from "typeorm";
import { Category } from "../../categories/entities/category.entity";
 
@Entity("posts")
export class Post {
  @PrimaryGeneratedColumn()
  id: number;
 
  @ManyToMany(() => Category, (category) => category.posts)
  @JoinTable()
  categories: Category[];
}
 
// categories/entities/category.entity.ts
import { Entity, Column, PrimaryGeneratedColumn, ManyToMany } from "typeorm";
import { Post } from "../../posts/entities/post.entity";
 
@Entity("categories")
export class Category {
  @PrimaryGeneratedColumn()
  id: number;
 
  @Column()
  name: string;
 
  @ManyToMany(() => Post, (post) => post.categories)
  posts: Post[];
}

查询构建器

// users/users.service.ts
import { Injectable } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { Repository } from "typeorm";
import { User } from "./entities/user.entity";
 
@Injectable()
export class UsersService {
  constructor(
    @InjectRepository(User)
    private usersRepository: Repository<User>
  ) {}
 
  // 使用查询构建器
  async findActiveUsers(): Promise<User[]> {
    return await this.usersRepository
      .createQueryBuilder("user")
      .where("user.active = :active", { active: true })
      .orderBy("user.createdAt", "DESC")
      .getMany();
  }
 
  // 复杂查询
  async findUsersWithPosts(): Promise<User[]> {
    return await this.usersRepository
      .createQueryBuilder("user")
      .leftJoinAndSelect("user.posts", "post")
      .where("post.published = :published", { published: true })
      .getMany();
  }
}

Prisma 集成

安装 Prisma

npm install prisma @prisma/client
npx prisma init

定义 Schema

// prisma/schema.prisma
generator client {
  provider = "prisma-client-js"
}
 
datasource db {
  provider = "mysql"
  url      = env("DATABASE_URL")
}
 
model User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  name      String?
  password  String
  posts     Post[]
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}
 
model Post {
  id        Int      @id @default(autoincrement())
  title     String
  content   String?
  published Boolean  @default(false)
  authorId  Int
  author    User     @relation(fields: [authorId], references: [id])
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

生成 Prisma Client

npx prisma generate
npx prisma migrate dev

创建 Prisma Service

// prisma/prisma.service.ts
import { Injectable, OnModuleInit, OnModuleDestroy } from "@nestjs/common";
import { PrismaClient } from "@prisma/client";
 
@Injectable()
export class PrismaService
  extends PrismaClient
  implements OnModuleInit, OnModuleDestroy
{
  async onModuleInit() {
    await this.$connect();
  }
 
  async onModuleDestroy() {
    await this.$disconnect();
  }
}

创建 Prisma Module

// prisma/prisma.module.ts
import { Global, Module } from "@nestjs/common";
import { PrismaService } from "./prisma.service";
 
@Global()
@Module({
  providers: [PrismaService],
  exports: [PrismaService],
})
export class PrismaModule {}

在服务中使用 Prisma

// users/users.service.ts
import { Injectable } from "@nestjs/common";
import { PrismaService } from "../prisma/prisma.service";
import { CreateUserDto } from "./dto/create-user.dto";
import { UpdateUserDto } from "./dto/update-user.dto";
 
@Injectable()
export class UsersService {
  constructor(private prisma: PrismaService) {}
 
  async create(createUserDto: CreateUserDto) {
    return await this.prisma.user.create({
      data: createUserDto,
    });
  }
 
  async findAll() {
    return await this.prisma.user.findMany();
  }
 
  async findOne(id: number) {
    return await this.prisma.user.findUnique({
      where: { id },
    });
  }
 
  async findByEmail(email: string) {
    return await this.prisma.user.findUnique({
      where: { email },
    });
  }
 
  async update(id: number, updateUserDto: UpdateUserDto) {
    return await this.prisma.user.update({
      where: { id },
      data: updateUserDto,
    });
  }
 
  async remove(id: number) {
    return await this.prisma.user.delete({
      where: { id },
    });
  }
}

Prisma 关系查询

// users/users.service.ts
async findUserWithPosts(id: number) {
  return await this.prisma.user.findUnique({
    where: { id },
    include: {
      posts: true, // 包含关联的 posts
    },
  });
}
 
async findUsersWithPublishedPosts() {
  return await this.prisma.user.findMany({
    where: {
      posts: {
        some: {
          published: true,
        },
      },
    },
    include: {
      posts: {
        where: {
          published: true,
        },
      },
    },
  });
}

Prisma 事务

// users/users.service.ts
async createUserWithProfile(userData: CreateUserDto, profileData: CreateProfileDto) {
  return await this.prisma.$transaction(async (tx) => {
    const user = await tx.user.create({
      data: userData,
    });
 
    const profile = await tx.profile.create({
      data: {
        ...profileData,
        userId: user.id,
      },
    });
 
    return { user, profile };
  });
}

使用环境变量配置

创建配置文件

// config/database.config.ts
import { registerAs } from "@nestjs/config";
 
export default registerAs("database", () => ({
  type: process.env.DB_TYPE || "mysql",
  host: process.env.DB_HOST || "localhost",
  port: parseInt(process.env.DB_PORT, 10) || 3306,
  username: process.env.DB_USERNAME || "root",
  password: process.env.DB_PASSWORD,
  database: process.env.DB_NAME || "testdb",
  synchronize: process.env.NODE_ENV === "development",
}));

使用配置

// app.module.ts
import { Module } from "@nestjs/common";
import { ConfigModule, ConfigService } from "@nestjs/config";
import { TypeOrmModule } from "@nestjs/typeorm";
import databaseConfig from "./config/database.config";
 
@Module({
  imports: [
    ConfigModule.forRoot({
      load: [databaseConfig],
    }),
    TypeOrmModule.forRootAsync({
      imports: [ConfigModule],
      useFactory: (configService: ConfigService) => ({
        ...configService.get("database"),
        entities: [__dirname + "/**/*.entity{.ts,.js}"],
      }),
      inject: [ConfigService],
    }),
  ],
})
export class AppModule {}

实际应用:用户管理系统

完整的用户实体和服务

// users/entities/user.entity.ts (TypeORM)
import {
  Entity,
  Column,
  PrimaryGeneratedColumn,
  CreateDateColumn,
  UpdateDateColumn,
} from "typeorm";
 
@Entity("users")
export class User {
  @PrimaryGeneratedColumn()
  id: number;
 
  @Column({ length: 100 })
  name: string;
 
  @Column({ length: 255, unique: true })
  email: string;
 
  @Column({ length: 255 })
  password: string;
 
  @Column({ default: true })
  active: boolean;
 
  @CreateDateColumn()
  createdAt: Date;
 
  @UpdateDateColumn()
  updatedAt: Date;
}
// users/users.service.ts
import { Injectable, NotFoundException } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { Repository } from "typeorm";
import { User } from "./entities/user.entity";
import { CreateUserDto } from "./dto/create-user.dto";
import { UpdateUserDto } from "./dto/update-user.dto";
 
@Injectable()
export class UsersService {
  constructor(
    @InjectRepository(User)
    private usersRepository: Repository<User>
  ) {}
 
  async create(createUserDto: CreateUserDto): Promise<User> {
    const user = this.usersRepository.create(createUserDto);
    return await this.usersRepository.save(user);
  }
 
  async findAll(): Promise<User[]> {
    return await this.usersRepository.find({
      where: { active: true },
      order: { createdAt: "DESC" },
    });
  }
 
  async findOne(id: number): Promise<User> {
    const user = await this.usersRepository.findOne({ where: { id } });
    if (!user) {
      throw new NotFoundException(`User with ID ${id} not found`);
    }
    return user;
  }
 
  async update(id: number, updateUserDto: UpdateUserDto): Promise<User> {
    const user = await this.findOne(id);
    Object.assign(user, updateUserDto);
    return await this.usersRepository.save(user);
  }
 
  async remove(id: number): Promise<void> {
    const user = await this.findOne(id);
    await this.usersRepository.remove(user);
  }
}

最佳实践

  1. 环境配置:使用环境变量管理数据库配置
  2. 实体验证:使用 class-validator 验证实体数据
  3. 迁移管理:使用数据库迁移而不是 synchronize(生产环境)
  4. 连接池:合理配置数据库连接池
  5. 错误处理:捕获和正确处理数据库错误
  6. 查询优化:避免 N+1 查询问题,合理使用 relations
  7. 事务管理:对关键操作使用事务保证数据一致性
  8. 类型安全:充分利用 TypeScript 类型系统

通过本文的学习,你已经掌握了在 Nest.js 中集成数据库的基本方法。无论是选择 TypeORM 还是 Prisma,都能帮助你高效地进行数据库操作。