- 1. API with NestJS #1. Controllers, routing and the module structure
- 2. API with NestJS #2. Setting up a PostgreSQL database with TypeORM
- 3. API with NestJS #3. Authenticating users with bcrypt, Passport, JWT, and cookies
- 4. API with NestJS #4. Error handling and data validation
- 5. API with NestJS #5. Serializing the response with interceptors
- 6. API with NestJS #6. Looking into dependency injection and modules
- 7. API with NestJS #7. Creating relationships with Postgres and TypeORM
- 8. API with NestJS #8. Writing unit tests
- 9. API with NestJS #9. Testing services and controllers with integration tests
- 10. API with NestJS #10. Uploading public files to Amazon S3
- 11. API with NestJS #11. Managing private files with Amazon S3
- 12. API with NestJS #12. Introduction to Elasticsearch
- 13. API with NestJS #13. Implementing refresh tokens using JWT
- 14. API with NestJS #14. Improving performance of our Postgres database with indexes
- 15. API with NestJS #15. Defining transactions with PostgreSQL and TypeORM
- 16. API with NestJS #16. Using the array data type with PostgreSQL and TypeORM
- 17. API with NestJS #17. Offset and keyset pagination with PostgreSQL and TypeORM
- 18. API with NestJS #18. Exploring the idea of microservices
- 19. API with NestJS #19. Using RabbitMQ to communicate with microservices
- 20. API with NestJS #20. Communicating with microservices using the gRPC framework
- 21. API with NestJS #21. An introduction to CQRS
- 22. API with NestJS #22. Storing JSON with PostgreSQL and TypeORM
- 23. API with NestJS #23. Implementing in-memory cache to increase the performance
- 24. API with NestJS #24. Cache with Redis. Running the app in a Node.js cluster
- 25. API with NestJS #25. Sending scheduled emails with cron and Nodemailer
- 26. API with NestJS #26. Real-time chat with WebSockets
- 27. API with NestJS #27. Introduction to GraphQL. Queries, mutations, and authentication
- 28. API with NestJS #28. Dealing in the N + 1 problem in GraphQL
- 29. API with NestJS #29. Real-time updates with GraphQL subscriptions
- 30. API with NestJS #30. Scalar types in GraphQL
- 31. API with NestJS #31. Two-factor authentication
- 32. API with NestJS #32. Introduction to Prisma with PostgreSQL
- 33. API with NestJS #33. Managing PostgreSQL relationships with Prisma
- 34. API with NestJS #34. Handling CPU-intensive tasks with queues
- 35. API with NestJS #35. Using server-side sessions instead of JSON Web Tokens
- 36. API with NestJS #36. Introduction to Stripe with React
- 37. API with NestJS #37. Using Stripe to save credit cards for future use
- 38. API with NestJS #38. Setting up recurring payments via subscriptions with Stripe
- 39. API with NestJS #39. Reacting to Stripe events with webhooks
- 40. API with NestJS #40. Confirming the email address
- 41. API with NestJS #41. Verifying phone numbers and sending SMS messages with Twilio
- 42. API with NestJS #42. Authenticating users with Google
- 43. API with NestJS #43. Introduction to MongoDB
- 44. API with NestJS #44. Implementing relationships with MongoDB
- 45. API with NestJS #45. Virtual properties with MongoDB and Mongoose
- 46. API with NestJS #46. Managing transactions with MongoDB and Mongoose
- 47. API with NestJS #47. Implementing pagination with MongoDB and Mongoose
- 48. API with NestJS #48. Definining indexes with MongoDB and Mongoose
- 49. API with NestJS #49. Updating with PUT and PATCH with MongoDB and Mongoose
- 50. API with NestJS #50. Introduction to logging with the built-in logger and TypeORM
- 51. API with NestJS #51. Health checks with Terminus and Datadog
- 52. API with NestJS #52. Generating documentation with Compodoc and JSDoc
- 53. API with NestJS #53. Implementing soft deletes with PostgreSQL and TypeORM
- 54. API with NestJS #54. Storing files inside a PostgreSQL database
- 55. API with NestJS #55. Uploading files to the server
- 56. API with NestJS #56. Authorization with roles and claims
- 57. API with NestJS #57. Composing classes with the mixin pattern
- 58. API with NestJS #58. Using ETag to implement cache and save bandwidth
- 59. API with NestJS #59. Introduction to a monorepo with Lerna and Yarn workspaces
- 60. API with NestJS #60. The OpenAPI specification and Swagger
- 61. API with NestJS #61. Dealing with circular dependencies
- 62. API with NestJS #62. Introduction to MikroORM with PostgreSQL
- 63. API with NestJS #63. Relationships with PostgreSQL and MikroORM
- 64. API with NestJS #64. Transactions with PostgreSQL and MikroORM
- 65. API with NestJS #65. Implementing soft deletes using MikroORM and filters
- 66. API with NestJS #66. Improving PostgreSQL performance with indexes using MikroORM
- 67. API with NestJS #67. Migrating to TypeORM 0.3
- 68. API with NestJS #68. Interacting with the application through REPL
- 69. API with NestJS #69. Database migrations with TypeORM
- 70. API with NestJS #70. Defining dynamic modules
- 71. API with NestJS #71. Introduction to feature flags
- 72. API with NestJS #72. Working with PostgreSQL using raw SQL queries
- 73. API with NestJS #73. One-to-one relationships with raw SQL queries
- 74. API with NestJS #74. Designing many-to-one relationships using raw SQL queries
- 75. API with NestJS #75. Many-to-many relationships using raw SQL queries
- 76. API with NestJS #76. Working with transactions using raw SQL queries
- 77. API with NestJS #77. Offset and keyset pagination with raw SQL queries
- 78. API with NestJS #78. Generating statistics using aggregate functions in raw SQL
- 79. API with NestJS #79. Implementing searching with pattern matching and raw SQL
- 80. API with NestJS #80. Updating entities with PUT and PATCH using raw SQL queries
- 81. API with NestJS #81. Soft deletes with raw SQL queries
- 82. API with NestJS #82. Introduction to indexes with raw SQL queries
- 83. API with NestJS #83. Text search with tsvector and raw SQL
- 84. API with NestJS #84. Implementing filtering using subqueries with raw SQL
- 85. API with NestJS #85. Defining constraints with raw SQL
- 86. API with NestJS #86. Logging with the built-in logger when using raw SQL
- 87. API with NestJS #87. Writing unit tests in a project with raw SQL
- 88. API with NestJS #88. Testing a project with raw SQL using integration tests
- 89. API with NestJS #89. Replacing Express with Fastify
- 90. API with NestJS #90. Using various types of SQL joins
- 91. API with NestJS #91. Dockerizing a NestJS API with Docker Compose
- 92. API with NestJS #92. Increasing the developer experience with Docker Compose
- 93. API with NestJS #93. Deploying a NestJS app with Amazon ECS and RDS
- 94. API with NestJS #94. Deploying multiple instances on AWS with a load balancer
- 95. API with NestJS #95. CI/CD with Amazon ECS and GitHub Actions
- 96. API with NestJS #96. Running unit tests with CI/CD and GitHub Actions
- 97. API with NestJS #97. Introduction to managing logs with Amazon CloudWatch
- 98. API with NestJS #98. Health checks with Terminus and Amazon ECS
- 99. API with NestJS #99. Scaling the number of application instances with Amazon ECS
- 100. API with NestJS #100. The HTTPS protocol with Route 53 and AWS Certificate Manager
- 101. API with NestJS #101. Managing sensitive data using the AWS Secrets Manager
- 102. API with NestJS #102. Writing unit tests with Prisma
- 103. API with NestJS #103. Integration tests with Prisma
- 104. API with NestJS #104. Writing transactions with Prisma
- 105. API with NestJS #105. Implementing soft deletes with Prisma and middleware
- 106. API with NestJS #106. Improving performance through indexes with Prisma
- 107. API with NestJS #107. Offset and keyset pagination with Prisma
- 108. API with NestJS #108. Date and time with Prisma and PostgreSQL
- 109. API with NestJS #109. Arrays with PostgreSQL and Prisma
- 110. API with NestJS #110. Managing JSON data with PostgreSQL and Prisma
- 111. API with NestJS #111. Constraints with PostgreSQL and Prisma
- 112. API with NestJS #112. Serializing the response with Prisma
- 113. API with NestJS #113. Logging with Prisma
- 114. API with NestJS #114. Modifying data using PUT and PATCH methods with Prisma
- 115. API with NestJS #115. Database migrations with Prisma
- 116. API with NestJS #116. REST API versioning
- 117. API with NestJS #117. CORS – Cross-Origin Resource Sharing
- 118. API with NestJS #118. Uploading and streaming videos
- 119. API with NestJS #119. Type-safe SQL queries with Kysely and PostgreSQL
- 120. API with NestJS #120. One-to-one relationships with the Kysely query builder
- 121. API with NestJS #121. Many-to-one relationships with PostgreSQL and Kysely
For a long time, TypeORM seemed to have a reputation of being somewhat stagnant. Lately, they stepped up their game, though, and started releasing many new versions. When doing that, they introduced a large number of breaking changes. In this article, we go through the most significant differences between 0.2.x and 0.3.x versions so far and see how they affect NestJS projects.
When writing this article, I was using TypeORM 0.3.7 and @nestjs/typeorm 8.1.4
Changes made to the repository API
TypeORM made a few changes to the functions we use to query the data.
Changes to the findOne method
First, they got rid of the findOne() function returning the first row from the table when not provided with an argument. It was caused by findOne(undefined) being confusing.
What’s more, TypeORM dropped the support for using findOne(id) to make the syntax more consistent. So now, we can only use findOne() by providing it with an object.
Because of all of the above, the following code no longer works:
1 2 3 4 5 6 7 |
this.categoriesRepository.findOne( id, { relations: ['posts'], withDeleted: true } ); |
error TS2554: Expected 1 arguments, but got 2.
Instead, we must pass a single object to the findOne() method.
1 2 3 4 5 6 7 8 9 |
this.categoriesRepository.findOne( { where: { id }, relations: ['posts'], withDeleted: true } ); |
To see all of the available options, check out the FindOneOptions interface.
Although the above code with relations: ['posts'] still works, it is now deprecated and will soon be removed. Instead, we should use a new object-literal notation.
1 2 3 4 5 6 7 8 9 10 11 |
this.categoriesRepository.findOne( { where: { id }, relations: { posts: true }, withDeleted: true } ); |
If we want to load a nested relation, we can create an object like this one:
1 2 3 4 5 |
relations: { posts: { author: true } } |
Changes to the find method
TypeORM made a similar set of changes to the find() method. Because of that, the following code is no longer valid:
1 2 3 4 5 |
this.commentsRepository.find({ post: { id: query.postId } }); |
error TS2345: Argument of type { post: { id: number; }; } is not assignable to parameter of type FindManyOptions<Comment>.
Instead, we need to provide the where object explicitly.
1 2 3 4 5 6 7 |
this.commentsRepository.find({ where: { post: { id: query.postId } } }); |
Besides the findOne and find methods, similar changes happened to findOneOrFail, count, and findAndCount.
Using the new findOneBy and findBy functions
When querying data, we don’t always need additional options, such as relations. In that case, we can use the new findOneBy method.
1 2 3 |
this.databaseFilesRepository.findOneBy({ id: fileId }); |
Similarly, we can use the new findBy method if we want to find multiple entities and don’t need to provide additional options such as relations.
1 2 3 4 5 |
this.commentsRepository.findBy({ post: { id: query.postId } }); |
Besides the above, we also have new findOneByOrFail, countBy, and findAndCountBy functions.
Deprecating the findByIds method
The findByIds is now deprecated and will soon be removed. Instead, we can use the findBy method with the In operator.
1 2 3 4 5 |
import { In } from 'typeorm'; postsRepository.findBy({ id: In([1, 2, 3]) }) |
Renaming Connection to DataSource
Before TypeORM 0.3.0, the configuration with our database used to be called a Connection. Recently, TypeORM renamed it to DataSource.
Throughout this series, we didn’t interact with the Connection much except for working with transactions.
users.service.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
import { Injectable, InternalServerErrorException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository, Connection } from 'typeorm'; import User from './user.entity'; import { FilesService } from '../files/files.service'; @Injectable() export class UsersService { constructor( @InjectRepository(User) private usersRepository: Repository<User>, private readonly filesService: FilesService, private connection: Connection ) {} async deleteAvatar(userId: number) { const queryRunner = this.connection.createQueryRunner(); const user = await this.getById(userId); const fileId = user.avatar?.id; if (fileId) { await queryRunner.connect(); await queryRunner.startTransaction(); try { await queryRunner.manager.update(User, userId, { ...user, avatar: null }); await this.filesService.deletePublicFileWithQueryRunner(fileId, queryRunner); await queryRunner.commitTransaction(); } catch (error) { await queryRunner.rollbackTransaction(); throw new InternalServerErrorException(); } finally { await queryRunner.release(); } } } // ... } |
Unfortunately, the above code is no longer valid. Instead of Connection, we should use DataSource instead.
users.service.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
import { Injectable, InternalServerErrorException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository, DataSource } from 'typeorm'; import User from './user.entity'; import { FilesService } from '../files/files.service'; @Injectable() export class UsersService { constructor( @InjectRepository(User) private usersRepository: Repository<User>, private readonly filesService: FilesService, private dataSource: DataSource ) {} async deleteAvatar(userId: number) { const queryRunner = this.dataSource.createQueryRunner(); const user = await this.getById(userId); const fileId = user.avatar?.id; if (fileId) { await queryRunner.connect(); await queryRunner.startTransaction(); try { await queryRunner.manager.update(User, userId, { ...user, avatar: null }); await this.filesService.deletePublicFileWithQueryRunner(fileId, queryRunner); await queryRunner.commitTransaction(); } catch (error) { await queryRunner.rollbackTransaction(); throw new InternalServerErrorException(); } finally { await queryRunner.release(); } } } } |
Changes to the configuration
TypeORM made a few significant changes to how we configure our database connection. First, let’s take a look at our current configuration.
database.module.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { ConfigModule, ConfigService } from '@nestjs/config'; @Module({ imports: [ TypeOrmModule.forRootAsync({ imports: [ConfigModule], inject: [ConfigService], useFactory: (configService: ConfigService) => ({ type: 'postgres', username: configService.get('POSTGRES_USER'), password: configService.get('POSTGRES_PASSWORD'), // ... entities: [ __dirname + '/../**/*.entity{.ts,.js}', ], }) }), ], }) export class DatabaseModule {} |
Unfortunately, strings with entities, migrations, and subscribers is now deprecated. Therefore, in future TypeORM versions, we will be able only to use entity references.
database.module.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { ConfigModule, ConfigService } from '@nestjs/config'; import Post from '../posts/post.entity'; import User from '../users/user.entity'; @Module({ imports: [ TypeOrmModule.forRootAsync({ imports: [ConfigModule], inject: [ConfigService], useFactory: (configService: ConfigService) => ({ type: 'postgres', username: configService.get('POSTGRES_USER'), password: configService.get('POSTGRES_PASSWORD'), // ... entities: [ Post, User, // ... ], }) }), ], }) export class DatabaseModule {} |
Having to list all of our entities might be a bit troublesome. Fortunately, @nestjs/typeorm implements the autoLoadEntities option that we can use to auto-load entities.
database.module.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { ConfigModule, ConfigService } from '@nestjs/config'; import Address from '../users/address.entity'; @Module({ imports: [ TypeOrmModule.forRootAsync({ imports: [ConfigModule], inject: [ConfigService], useFactory: (configService: ConfigService) => ({ type: 'postgres', username: configService.get('POSTGRES_USER'), password: configService.get('POSTGRES_PASSWORD'), entities: [ Address ], autoLoadEntities: true, // ... }) }), ], }) export class DatabaseModule {} |
Please notice that we still include the Address entity in the entities array above. The above is because we need to manually add every entity we don’t use through TypeOrmModule.forFeature(). In our case, a good example is the Address entity we use only through a one-to-one relationship.
New array operators
Previously, we had to use raw SQL to run more advanced queries with PostgreSQL arrays.
1 2 3 4 |
async getPostsWithParagraph(paragraph: string) { return this.postsRepository .query(`SELECT * from post WHERE ${paragraph} = ANY(paragraphs)`); } |
Now, TypeORM 0.3.1 introduced new array operators that can let us handle such cases.
1 2 3 4 5 6 7 8 |
import { ArrayContains } from 'typeorm'; async getPostsWithParagraph(paragraph: string) { return this.postsRepository .findBy({ paragraphs: ArrayContains([paragraph]) }) } |
Besides the above, we also have the ArrayContainedBy and ArrayOverlap operators.
Other changes
Besides all the changes mentioned so far, TypeORM fixed a ton of minor and significant issues and added performance improvements.
It is worth tracking the changelog in the GitHub releases page.
Some of the important changes are:
- requiring NodeJS 14+
- supporting TypeScript 4.8
- upgrading the ioredis library to v5
- supporting the Google Cloud Spanner database,
- implementing the FOR KEY SHARE lock mode for PostgreSQL,
- possibility of naming primary keys, foreign keys, and indices explicitly,
- supporting Common Table Expressions.
Summary
In this article, we’ve gone through the most significant changes made to TypeORM in the last changes. Some of them forced us to refactor our code to be able to bump the TypeORM version we use. All the changes we’ve handled in this article seem like steps in the right direction. A crucial thing to remember is that TypeORM does not follow semantic versioning. Because of that, going from 0.3.0 to 0.3.1 can include breaking changes, for example. Because of that, we need to be super careful when updating TypeORM and read the changelog very carefully to see if it affects our project.
What about custom repositories with Nestjs?
What a coincidence. I am having issues with custom repositories too. I am trying this structure. But i got error like below;
When i try to console.log it i am getting below;
You can see the code below:
We probably say to nestjs “hey this is a repository initialize it for us” 😀
Same issue for me here. I would love to get a solution.
Nice post, recently I was suffering with configurations, What about migrations config? I guess this was a breaking change also, how do you manage it?
I know only NestJS + Typeorm + Many Crutches = Migrations 🙁 Please show good way.
Hi. I’ve just published a separate article about migrations. I hope it helps you!
https://dev.wanago.io/2022/07/25/api-nestjs-database-migrations-typeorm/
Thank you. This is so insightful!
An example setup is here.
https://github.com/crazyoptimist/nest-starter
Any feedbacks are welcome!
Thank you very much. But can you help for another topic relating to this one, after upgrading to version 0.3 typeorm destroyed a lot of old approaches. Can you share how can we can create custom repository for entities for typeorm 0.3