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.
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.
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.
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.
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.
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
.
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.
To migrate an existing application to Sequelize in NestJS, the basic steps are as follows:
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.
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.
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.
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.
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.
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:
Before migrating to TypeORM, it is worth considering the following:
To migrate to TypeORM in NestJS, the essential steps include:
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.
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.
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.
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);
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.
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:
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:
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.
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.
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.
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, }); }
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.
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:
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.
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.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowSOLID principles help us keep code flexible. In this article, we’ll examine all of those principles and their implementation using JavaScript.
JavaScript’s Date API has many limitations. Explore alternative libraries like Moment.js, date-fns, and the new Temporal API.
Explore use cases for using npm vs. npx such as long-term dependency management or temporary tasks and running packages on the fly.
Validating and auditing AI-generated code reduces code errors and ensures that code is compliant.
6 Replies to "Comparing 4 popular NestJS ORMs"
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
Thanks for reading and feedback.
Migrations are provided out-of-box via sequelize-cli, you can create model and migrations, then migrate to db. You can find more information about sequelize migration here https://sequelize.org/docs/v6/other-topics/migrations/.
I will update the example app to include migrations in next few days.
Thank you for that informative article,
I may ask about your personal choice between those 4 ORMs
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.
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
Thanks for sharing your experience, Olivier. These information are very useful for ppl considering prisma.