比较四个流行的NestJS ORMs

3,877 阅读10分钟

NestJS是一个流行的Node.js服务器端框架。它是高度可定制的,带有丰富的生态系统,并与大多数Node.js库兼容,包括ORM库。

NestJS中的ORMs

对象关系映射(ORM)是一种技术,它将你的数据库表抽象为内存中的数据对象。它允许你使用数据对象查询和写数据到数据库。ORM被用来使数据库访问更容易,因为开发人员不需要写原始查询。

ORM有其局限性,如复杂查询的性能问题,但如果用在正确的地方,它们仍然可以使你的生活更容易。

NestJS是数据库无关的。为了方便,NestJS提供了与TypeORM和Sequelize的紧密集成,开箱即用@nestjs/typeorm和@nestjs/sequelize包。你也可以直接使用任何通用的Node.js数据库集成库或ORM,但NestJS ORM的生态系统是如此庞大,要为你的项目选择合适的ORM可能是令人生畏的。

我们的目的是总结它们的共同特点,如受欢迎程度、特点、文档和成熟度,对于每个ORM,我们将提供一个代码片段,给你一个应用该框架的最简单的例子。

NestJS和Sequelize

Sequelize大约在2014年推出,是一个易于使用和基于承诺的Node.js的ORM。

它支持许多数据库,包括PostgreSQL、MySQL、MariaDB、SQLite、DB2和MSSQL。它的局限性包括缺乏对NoSQL的支持和只支持Active Record模式。

特点

Sequelize提供了一套丰富的功能:交易和迁移支持、模型验证、急迫和懒惰加载以及读取复制,等等。Sequelize有合理的文档,有丰富的信息和很好的例子,但有时我发现要搜索一个特定的主题并不容易。

Sequelize带有一个CLI,可以创建一个数据库,初始化配置和播种机,或者管理迁移。它还使用Active Record模式。在Active Record模式中,数据库行被映射成应用程序中的一个对象,而数据库表则由一个类来表示。因此,当我们创建一个实体对象并调用save 方法时,一个新的行被添加到数据库表中。

Active Record模式的主要好处是它的简单性:你可以直接使用实体类来代表数据库表并与之交互。

设置

Sequelize很容易设置和使用。下面是一个基本数据库配置和操作的例子:

// Firstly, we create a Sequelize instance with an options object
export const databaseProviders = [  {    provide: 'SEQUELIZE',    useFactory: async () => {      const sequelize = new Sequelize({        dialect: 'postgres',        host: 'localhost',        port: 5432,        username: 'postgres',        password: 'postgres',        database: 'postgres',      });      sequelize.addModels([Cat]); // Add all models
      await sequelize.sync(); // Sync database tables
      return sequelize;
    },
  },
];

// Then, export the provider to make it accessible
@Module({
  providers: [...databaseProviders],
  exports: [...databaseProviders],
})
export class DatabaseModule {}

// Define model entity, each represent a table in the database
@Table
export class Cat extends Model {
  @Column
  name: string;

  @Column
  age: number;

  @Column
  breed: string;
}
// Create a repository provider
export const catsProviders = [
  {
    provide: 'CATS_REPOSITORY',
    useValue: Cat,
  },
];
// In CatsService, we inject the repository
export class CatsService {
  constructor(
    @Inject('CATS_REPOSITORY')
    private catsRepository: typeof Cat,
  ) {}

// Then, we can perform database operations
this.catsRepository.findAll<Cat>();

在某些情况下,只是更容易执行原始SQL查询,你可以使用函数sequelize.query

// SQL Script. Source https://sequelize.org/docs/v6/core-concepts/raw-queries/
const [results, metadata] = await sequelize.query("UPDATE users SET y = 42 WHERE x = 12");
// Results will be an empty array and metadata will contain the number of affected rows.

Sequelize正在努力实现对TypeScript的正式支持,但就目前而言,我们仍然建议你使用 [sequelize-typescript package](https://www.npmjs.com/package/sequelize-typescript)来在你的项目中使用TypeScript。

社区和普及

Sequelize是一个非常成熟和稳定的ORM。它有一个活跃的社区和一系列必要的工具。作为最受欢迎的ORM框架之一,Sequelize在GitHub上有26K颗星和4K个叉。它也被NestJS直接支持,有@nestjs/sequelize

使用案例

Sequelize是一个用于Node.js应用程序的通用ORM。如果你正在寻找一个稳定的、易于使用的ORM,它值得你考虑。

NestJS和TypeORM

TypeORM是另一个成熟的Node.js的ORM。它有一个丰富的功能集,包括实体管理器、连接池、复制和查询缓存。它也被NestJS直接支持,有自己的包,@nestjs/typeorm

2016年发布的TypeORM支持方言PostgreSQL、MySQL、MariaDB、SQLite、MSSQL和MongoDB。这意味着,你可以用TypeORM同时使用NoSQL和SQL数据库。

特点

TypeORM提供了一个CLI,可以创建实体,项目和订阅者或管理迁移。它支持Active RecordData Mapper模式。

数据映射器模式在你的应用程序的业务领域和数据库之间增加了一个数据访问层(DAL)。使用数据映射器模式可以提供更多的灵活性和更好的性能,因为它比天真的Active Record实现更有效地利用数据库。提供这两种方法使你可以选择适合你的应用程序的模式。

和一些开源项目一样,它的文档也有改进的余地。其中一个常见的问题是缺乏必要的API细节。

设置

下面是使用Active Record模式的TypeORM的基本数据库配置和操作:

// Firstly, we setup Database Connection
@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'postgres',
      host: 'localhost',
      port: 5432,
      username: 'postgres',
      password: 'postgres',
      database: 'postgres',
      entities: [Cat],
      synchronize: true, // Sync the entities with the database every time the application runs
    }),
    CatsModule,
  ],
})

// Then you can define the data entity
@Entity()
export class Cat {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;

  @Column()
  breed: string;
}
// We can use the repository design pattern which means each entity has its own repository.
// Here, we inject the catRepository into your service
export class CatsService {
  constructor(
    @InjectRepository(Cat)
    private catsRepository: Repository<Cat>,
  ) {}
  
// Then you can use the injected repo to perform database operation
  this.catsRepository.find();
  this.catsRepository.save<Cat>(cat);

要运行一个原始的SQL查询,你可以使用@InjectConnection 装饰器并使用query 方法来运行一个自定义的查询脚本:

// Inject the connection
constructor(@InjectConnection() private readonly connection: Connection) 
{} 
// Run the query
this.connection.query('SELECT * FROM [TableName];');

你可以使用JavaScript或TypeScript与TypeORM。与其他ORM相比,用TypeScript使用TypeORM更自然,因为它是由TypeScript编写的。

社区和受欢迎程度

它是最受欢迎的ORM库之一,在GitHub上有28.2K颗星和5.1K个分支。

凭借其丰富的功能集和灵活性,TypeORM是NestJS的最佳ORM之一。

使用案例

TypeORM可以运行在Node.js和其他一些平台上。如果你的应用程序需要一个或多个以下的功能,它是一个很好的选择:

  • 可扩展到大型企业级的应用程序
  • 支持多个数据库
  • 同时支持SQL和NoSQL数据库
  • 支持多种平台,包括Node.js、Ionic、Cordova、React Native、NativeScript、Expo或Electron

NestJS和MikroORM

MikroORM是另一个用于Node.js的TypeScript ORM,基于数据映射器、工作单元和身份映射模式。

它于2018年推出,是一个相对年轻的ORM。MikroORM同时支持SQL和NoSQL数据库,包括MongoDB、MySQL、PostgreSQL和SQLite数据库。MikroORM也有自己的NestJS支持。 [@mikro-orm/nestjs package](https://mikro-orm.io/docs/usage-with-nestjs/),这是一个第三方包,不由NestJS团队管理。

特点

MikroORM提供了一个令人印象深刻的功能列表,包括交易、对身份映射模式的支持、级联持久化和删除、查询生成器等等。它是一个功能齐全的ORM,具有所有主要的数据库选项,如果你想转换,可以很容易地从TypeORM迁移到它。

它带有一个CLI工具,可以创建/更新/删除数据库模式,管理数据库迁移,并生成实体。

MikroORM支持数据映射器模式。它也是基于工作单元身份映射模式建立的。实现工作单元使我们能够自动处理事务。它还通过身份映射模式对事务进行了优化,这使得它可以防止对数据库的不必要的往返。这些模式也帮助MikroORM实现了良好的性能:最近的一个基准测试显示,用SQLite插入10K实体只需要70ms左右。

设置

MikroORM的语法是非常简单和直接的。下面是一个在NestJS中使用MikroORM进行最简单的数据库操作的例子:

// Firstly, import MikroOrmModule in App.module
@Module({
  imports: [MikroOrmModule.forRoot(), CatsModule],
})
// The Database configuration is store in mikro-orm.config.ts
const config: Options = {
  entities: [Cat],
  dbName: 'postgres',
  type: 'postgresql',
  port: 5432,
  host: 'localhost',
  debug: true,
  user: 'postgres',
  password: 'postgres',
} as Options;

// The config paths are defined in package.json
  "mikro-orm": {
    "useTsNode": true,
    "configPaths": [
      "./src/mikro-orm.config.ts",
      "./dist/mikro-orm.config.js"
    ]
  }
// To use repository pattern, we register entities via forFeature() in feature module
@Module({
  imports: [MikroOrmModule.forFeature({ entities: [Cat] })],
})
export class CatsModule {}

// We inject the repository into service
  constructor(
    @InjectRepository(Cat)
    private readonly catRepository: EntityRepository<Cat>,
  ) {}

 // Then, we can perform database operation
this.catRepository.findOne(findOneOptions);

社区和普及

MikroORM的文档得到了积极的维护,并且易于浏览。虽然它是最年轻的ORM之一,但它在GitHub中并没有一长串的开放问题。回报是积极维护的,问题通常会很快得到解决。

MikroORM是为了克服其他Node.js ORM的一些现有问题而建立的,比如缺乏事务支持。它发展迅速,除了一系列伟大的功能外,还提供了强大的类型安全。

使用案例

MikroORM因其独特的功能、强大的类型化和良好的支持而脱颖而出。如果你正在寻找一个ORM,它值得考虑:

  • 需要良好交易支持的绿地应用
  • 一个需要定期批量数据更新和快速性能的应用程序

NestJS和Prisma

Prisma是一个开源的Node.js的ORM。它目前支持PostgreSQL、MySQL、SQL Server、SQLite、MongoDB和CockroachDB(仍然只提供预览)。

Prisma顺利地与NestJS集成。与其他ORM不同,没有必要为NestJS-ORM的集成准备一个单独的包。相反,Prisma CLI包是我们在NestJS中使用Prisma唯一需要的东西。

特点

与本文讨论的其他ORM相比,Prisma是一个独特的库。它不像其他ORM那样使用实体模型,而是使用一种模式定义语言。开发人员可以用Prisma模式来定义数据模型,这些模型被用来生成所选数据库的迁移文件。它还可以生成强类型的代码,与数据库进行交互。

模式文件被用作数据库和应用程序的单一真理来源,基于它的代码生成有助于建立类型安全的查询,以在编译时捕捉错误。

Prisma提供良好的工具,包括Prisma CLI和Prisma Studio。你可以使用CLI来创建迁移,或者使用Studio来检查数据库,这与Prisma的Entity结构配合得很好。它还为数据库托管提供了一个Prisma数据平台。

Prisma的文档格式很好,并得到积极维护。

设置

使用Prisma,开发人员可以简单地定义他们的模式,而不必担心具体的ORM框架。下面是PrismaORM与NestJS的最基本用法:

// Firstly, we need a schema file to model the database
generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL") // the DATABASE_URL is stored in .env file
}

model Cat {
  id            Int         @default(autoincrement()) @id
  name          String
  age           Int
  breed         String   
}

// To generates SQL files and also runs them on the configured database, run following command
 npx prisma migrate dev --name init

// Create a Database service to interact with the Prisma Client API
@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit {
  async onModuleInit() {
    await this.$connect();
  }

  async enableShutdownHooks(app: INestApplication) {
    this.$on('beforeExit', async () => {
      await app.close();
    });
  }
}
// In service layer, we inject the DatabaseService
constructor(private prisma: PrismaService) {}

// We can perform database operation via Prisma client api as below
// Please note that we use Prisma Client's generated types, like the "Cat"
  async cat(
    catWhereUniqueInput: Prisma.CatWhereUniqueInput,
  ): Promise<Cat | null> {
    return this.prisma.cat.findUnique({
      where: catWhereUniqueInput,
    });
  }

社区和受欢迎程度

Prisma于2019年首次发布,是我们讨论的四个ORM中最新的一个。它需要时间来达到一个更成熟的状态。最近,第3版的发布引入了一些突破性的变化。在GitHub上也注意到了一些现有的问题,比如它不支持一些Postgres列类型

在GitHub上有23.3K颗星星和828个分叉,它的受欢迎程度在迅速增长。

使用案例

总的来说,Prisma是一个非常有前途的ORM。它旨在缓解传统ORM中存在的问题,并且可以与JavaScript或TypeScript一起使用。使用TypeScript的好处是,你可以利用Prisma客户端生成的类型来实现更好的类型安全。

Prisma的一些好的用例是:

  • 新应用程序的快速原型开发
  • 需要良好的数据库工具和强大的类型支持的应用程序

总结

有许多ORM可以和NestJS一起使用。因此,为你的NestJS应用程序选择一个合适的ORM可能是一个艰难的决定。在这篇文章中,我们浏览了四个流行的ORM,并讨论了它们的优势和劣势。

我还创建了一个 repo,其中有每个ORM的最基本的示例代码,所以如果你没有使用过它们,会更容易有一个直接的印象。你可以在这里找到源代码。