Yan Sun I am a full-stack developer. Love coding, learning, and writing.

Comparing 4 popular NestJS ORMs

10 min read 2806

Comparing four NestJS ORMs

NestJS is a popular Node.js server-side framework. It’s highly customizable, comes with a rich ecosystem, and is compatible with most Node.js libraries, including the ORM libraries.

ORMs in NestJS

Object-relational mapping (ORM) is a technique that abstracts your database tables to data objects in memory. It allows you to query and write data to databases using data objects. ORMs are used to make database access easier, as developers won’t need to write raw queries.

ORMs have their limitations, such as performance issues with complex queries, but they can still make your life easier when used in the right places.

NestJS is database-agnostic. For convenience, NestJS provides tight integrations with TypeORM and Sequelize out of the box with the @nestjs/typeorm and @nestjs/sequelize packages. You can also directly use any general purpose Node.js database integration library or ORM, but the ecosystem of NestJS ORMs is so massive, it can be daunting to choose the right one for your project.

In this article, we are going to walk through using four common ORMs with NestJS. They are:

Our intention is to summarize their common characteristics, like popularity, features, documentation, maturity, and information about migration. For each ORM, a code snippet will be provided to give you the simplest example of applying the framework.

NestJS and Sequelize

Introduced around 2014, Sequelize is an easy-to-use and Promise-based ORM for Node.js.

It supports many databases, including PostgreSQL, MySQL, MariaDB, SQLite, DB2, and MSSQL. Its limitations include a lack of NoSQL support and only supporting the Active Record pattern.

Features

Sequelize provides a rich set of features: transaction and migration support, model validations, eager and lazy loading, and read replication, among others. Sequelize has reasonable documentation with rich information and good examples, but sometimes I find it’s not easy to search for a particular topic.

Sequelize comes with a CLI that can create a database, initialize configuration and seeders, or manage migration. It also uses the Active Record pattern. In the Active Record pattern, a database row is mapped into an object in the application, and a database table is represented by a class. Thus, when we create an entity object and call the save method, a new row is added to the database table.

The main benefit of the Active Record pattern is its simplicity: you can directly use the entity classes to represent and interact with the database tables.

Setup

Sequelize is easy to set up and use. Below is an example of the basic database configuration and operation.

// First, 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 represents 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>();

In some cases, where it’s just easier to execute raw SQL queries, you can use the function 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 is hard at work making official TypeScript support possible, but for now, it is still recommended that you use the sequelize-typescript package to work with TypeScript in your project.

Community and popularity

Sequelize is a very mature and stable ORM. It has an active community and a wide array of necessary tooling. As one of the most popular ORM frameworks, Sequelize has 26K stars and 4K forks on GitHub. It’s also directly supported by NestJS with @nestjs/sequelize.

Use cases

Sequelize is a general purpose ORM for Node.js apps. If you are looking for a stable, easy-to-use ORM, it is worth your consideration.

Migrating to Sequelize

To migrate an existing application to Sequelize in NestJS, the basic steps are as follows:

  • Set up the Sequelize database provider
  • Create models from the existing database schema
  • Create a repository provider for each model and inject it into the corresponding service

Sequelize does not offer inbuilt support to generate models from existing databases. When migrating an existing application, you’ll have a choice of creating models manually or using sequelize-typescript-generator to generate models from the existing database.

Switching ORMs is a major change, but it is also an opportunity to refactor existing code to make it more maintainable. If the existing application doesn’t have a clean structure, it is time to implement a clean separation between the service and database access layer.



NestJS and TypeORM

TypeORM is another mature ORM for Node.js. It has a rich feature set, including an entity manager, connection pooling, replication, and query caching. It’s also directly supported by NestJS with its own package, @nestjs/typeorm.

Released in 2016, TypeORM supports the dialects PostgreSQL, MySQL, MariaDB, SQLite, MSSQL, and MongoDB. That means that you can use both NoSQL and SQL databases at the same time with TypeORM.

Features

TypeORM provides a CLI that can create entities, projects, and subscribers or manage migration. It supports both Active Record and Data Mapper patterns.

The Data Mapper pattern adds a data access layer (DAL) between the business domain of your application and the database. Using the Data Mapper pattern provides more flexibility and better performance, as it makes more efficient use of the database than a naive Active Record implementation. Providing both approaches gives you a choice of pattern that suits your application.

Much like some open source projects, its documentation has room for improvement. One of the common issues is a lack of necessary API details.

Setup

Below is the basic database configuration and operation for TypeORM using the Active Record pattern.

// First, we set up the 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);

To run a raw SQL query, you can use the @InjectConnection decorator and use the query method to run a custom query script.

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

You can use either JavaScript or TypeScript with TypeORM. Using TypeORM with TypeScript is more natural as compared to other ORMs, as it’s written by TypeScript.

Community and popularity

It’s one of the most popular ORM libraries, with 28.2K stars and 5.1K forks on GitHub.

With its rich feature set and flexibility, TypeORM is one of the best ORMs for NestJS.


More great articles from LogRocket:


Use cases

TypeORM can run on Node.js and a number of other platforms. It is a great choice if your application requires one or more of the following features:

  • Scalable to a large, enterprise-grade app
  • Support for multiple databases
  • Support for both SQL and NoSQL databases
  • Support for multiple platforms, including Node.js, Ionic, Cordova, React Native, NativeScript, Expo, or Electron

Migrating to TypeORM

Before migrating to TypeORM, it is worth considering the following:

  • Choose the right pattern for your app. TypeORM supports both the Active Record and Data Mapper pattern. Active Record is simple and intuitive but suffers from tight coupling to the database. It is suitable for small CRUD applications. For complex applications, Data Mapper will be a better choice as it allows for more flexible design and cleaner separation between the business domain and data access layers
  • Take advantage TypeORM’s strong typing support. For example, implementing nominal typing for entity IDs will prevent IDs from being mistakenly assigned across different entities (types)

To migrate to TypeORM in NestJS, the essential steps include:

  • Configuring the database connection
  • Creating the entity model class and the corresponding service. The entity model can be created manually, or you can use the typeorm-model-generator to generate them from the database. The typeorm-model-generator package supports MSSQL, Postgres, and SQLite

If you are migrating from Sequelize, this documentation discusses the similarity and differences between these two ORMs. Moving from Sequelize is relatively easy, as both ORMs support the Active Record pattern.

NestJS and MikroORM

MikroORM is another TypeScript ORM for Node.js based on the Data Mapper, Unit of Work, and Identity Map patterns.

Launched in 2018, it’s a relatively young ORM. MikroORM supports both SQL and NoSQL databases, including MongoDB, MySQL, PostgreSQL, and SQLite databases. MikroORM also has its own NestJS support,@mikro-orm/nestjs package, which is a third-party package and not managed by the NestJS team.

Features

MikroORM provides an impressive list of features, including transactions, support for the Identity Map pattern, cascading persist and remove, a query builder, and more. It’s a fully-featured ORM with all the major database options, and is easily migrated to from TypeORM, if you’re looking to switch.

It comes with a CLI tool that can create/update/drop database schema, manage database migration, and generate entities.

MikroORM supports the Data Mapper pattern. It’s also built based on Unit of Work and Identity Map patterns. Implementing Unit of Work allows us to handle transactions automatically. It’s also optimized for transactions via Identity Map patterns, which makes it possible to prevent unnecessary round-trips to the database. Those patterns also help MikroORM achieve good performance: a recent benchmark shows it only takes around 70ms for inserting 10K entities with SQLite.

Setup

The syntax of MikroORM is very simple and straightforward. Below is an example of using MikroORM for the simplest database operations in NestJS.

// Firstly, import MikroOrmModule in App.module
@Module({
  imports: [MikroOrmModule.forRoot(), CatsModule],
})
// The Database configuration is stored 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);

Community and popularity

The documentation of MikroORM is actively maintained and easy to navigate. Although it’s one of the youngest ORMs, it doesn’t have a long list of open issues in GitHub. The repo is actively maintained, and issues are normally resolved quickly.

MikroORM is built to overcome some existing issues of other Node.js ORMs, like lack of transaction support. It’s fast growing and offers strong type safety in addition to a list of great features.

Use cases

MikroORM stands out because of its unique features, strong typing, and good support. It’s worth considering if you are looking for an ORM for:

  • A greenfield app that needs good transaction support
  • An app that needs regular batch data updates and fast performance

Migrating to MikroORM

Moving to a new ORM is an expensive decision. Before switching to MikroORM, ensure your application can take advantage of its many features. If your application falls into any of the following categories, you’ll probably want to choose another ORM:

  • Small application with only a handful of tables
  • Doesn’t require nested tables or complex joined queries
  • Not many users, thus no performance concerns

Using inbuilt tools can help save you time and improve code quality in the migration. MikroORM ships with several command line tools that are very helpful during development, such as Schema Generator, Entity Generator, and Migration.

  • Entity Generator: Can be used to generate entities from existing database schema
  • Schema Generator: Can be used to generate, update, or drop schema from your entity metadata; this tool is meant to be used in development only
  • Migration: Allows you to generate migrations with current schema differences

NestJS and Prisma

Prisma is an open source ORM for Node.js. It currently supports PostgreSQL, MySQL, SQL Server, SQLite, MongoDB, and CockroachDB (which is still available for preview only).

Prisma integrates smoothly with NestJS. Unlike the other ORMs, there is no need to have a separate package for the NestJS-ORM integration. Instead, the Prisma CLI package is the only thing we need to use Prisma in NestJS.

Features

Prisma is a unique library compared with other ORMs discussed in this article. Instead of using entity models as other ORMs do, it uses a schema definition language. Developers can define the data models with Prisma schema, and the models are used to generate the migration files for the chosen database. It also generates strongly typed code to interact with the database.

Schema files are used as a single source of truth for both database and application, and the code generation based on it helps to build type-safe queries to catch errors during compile time.

Prisma provides good tooling, including Prisma CLI and Prisma Studio. You can use the CLI to create migrations or use the Studio to inspect databases, which works well with the Entity structure of Prisma. It also provides a Prisma data platform for database hosting.

The documentation of Prisma is nicely formatted and actively maintained.

Setup

Using Prisma, developers can simply define their schemas and not worry about specific ORM frameworks. Below is the most basic usage of PrismaORM with 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 generate SQL files and also runs them on the configured database, run the 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 the 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,
    });
  }

Community and popularity

First released in 2019, Prisma is the newest ORM of the four we discussed. It will need time to get to a more mature state. Recently, the release of version 3 introduced a few breaking changes. There are also some existing issues noted in GitHub, such as that it does not support some Postgres column types.

With 23.3K GitHub stars and 828 forks, it’s grown rapidly in popularity.

Use cases

Overall, Prisma is a very promising ORM. It’s designed to mitigate existing problems in traditional ORMs and can be used with either JavaScript or TypeScript. The advantage of using TypeScript is that you can leverage the Prisma Client’s generated types for better type-safety.

Some good use cases for Prisma are:

  • Rapid prototype development for new apps
  • Apps that require good database tooling and strong typing support

Migration to Prisma

Prisma supports incremental adoption. This means you can move your database queries to Prisma gradually, instead of migrating the whole application in one go.

The migration to Prisma includes the below general steps, but details of the migration process can be found found in the Prisma documentation.

  • Install the Prisma CLI
  • Introspect the current database: Populate your Prisma schema with a data model that reflects the current database schema
  • Install the Prisma client
  • Gradually replace existing database queries with the Prisma client

Summary

There are a number of ORMs that can be used together with NestJS. Thus, choosing a suitable ORM for your NestJS application can be a hard decision. In this article, we have walked through four popular ORMs, and discussed their strengths and weaknesses.

I also created a repo with the most basic sample code for each ORM, so it will be easier for you to have a direct impression if you have not used them. You can find the source code here.

: Full visibility into your web and mobile apps

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page and mobile apps.

.
Yan Sun I am a full-stack developer. Love coding, learning, and writing.

6 Replies to “Comparing 4 popular NestJS ORMs”

  1. Is it possible to use sequelize-cli to create migrations and models, and migrate to db?
    In your example app you have not defined migrations

  2. Thank you for that informative article,
    I may ask about your personal choice between those 4 ORMs

    1. I am glad that you found the article useful.
      I used Prisma/NestJS for a recent personal App, and it works very well. It is just person preference (mainly because I like the full type safe queries). There are of course much more factors to be considered if the decision is for an Enterprise App.

  3. After using Prisma for quite some time now, I feel like overall the DSL and querying is not bad, although it has limitations that can be overcome but lead to worse performance. (https://github.com/prisma/prisma/discussions/4185)
    In comparison with knex, having a schema where we can see how the DB is supposed to look like is definitely nice. That said, the way we migrate the DB is not awesome. Generating migrations directly modifying the schema, falls in my mind into the “demo wowww” effect, but it has strong limitations. For example, when renaming a column, prisma cannot tell what you want to do. It will end up deleting the old column and create a new one. There are some work-arounds (https://www.basedash.com/blog/how-to-rename-a-table-or-column-using-prisma-migrations) but it shows the limitations of this abstraction: I think an imperative approach to migrate the DB (with a DSL) is superior than a declarative one.
    Also the fact, that we cannot use JS inside migrations means that this tool can only be used to change data structure but not so much migrate / move the existing data. When you want to do this (we had to and we will have to), the response (https://github.com/prisma/prisma/discussions/10854#discussioncomment-1865526) is to launch a Node Script against the DB. Problem with this is that you need to develop your own tooling like recording that a script was run not to run it multiple times (something prisma SQL migrations already cater for).
    All in all, I am still happy we went with prisma as opposed to knex. But I think some of the abstractions that were used fundamentally prevents this tool from being on par with other tools like Rails’ ActiveRecord for example

    1. Thanks for sharing your experience, Olivier. These information are very useful for ppl considering prisma.

Leave a Reply