DEV Community

Cover image for Comparing 4 popular NestJS ORMs
Matt Angelosanto for LogRocket

Posted on • Originally published at blog.logrocket.com

Comparing 4 popular NestJS ORMs

Written by Yan Sun✏️

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, and maturity, and 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 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.

// 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>();
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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.

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.

// 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);
Enter fullscreen mode Exit fullscreen mode

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];');
Enter fullscreen mode Exit fullscreen mode

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.

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

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 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);
Enter fullscreen mode Exit fullscreen mode

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

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 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,
    });
  }
Enter fullscreen mode Exit fullscreen mode

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

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.


LogRocket: Full visibility into your web apps

LogRocket signup

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.

Top comments (0)