- 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
When we build an application, we create many entities. They often somehow relate to each other, and defining such relationships is an essential part of designing a database. In this article, we go through what is a relationship in the context of a Postgres database and how do we work with them using TypeORM and NestJS.
The relational databases have been around for quite some time and work great with structured data. They do so by organizing the data into tables and linking them to each other. When running various SQL queries, we can join the tables and extract meaningful information. There are a few different types of relationships, and today we go through them with the use of examples.
We’ve also gone through it in the TypeScript Express series. The below article acts as a recap of what we can get from there. This time we also look more into the SQL queries that TypeORM generates
You can find all of the code from this series in this repository.
One-to-one
With the one-to-one relationship, the first table has just one matching row in the second table, and vice versa.
The most straightforward example would be adding an address entity.
users/address.entity.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; @Entity() class Address { @PrimaryGeneratedColumn() public id: number; @Column() public street: string; @Column() public city: string; @Column() public country: string; } export default Address; |
Let’s assume that one address can be linked to just one user. Also, a user can’t have more than one address.
To implement the above, we need a one-to-one relationship. When using TypeORM, we can create it effortlessly with the use of decorators.
users/user.entity.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 |
import { Column, Entity, JoinColumn, OneToOne, PrimaryGeneratedColumn } from 'typeorm'; import { Exclude } from 'class-transformer'; import Address from './address.entity'; @Entity() class User { @PrimaryGeneratedColumn() public id: number; @Column({ unique: true }) public email: string; @Column() public name: string; @Column() @Exclude() public password: string; @OneToOne(() => Address) @JoinColumn() public address: Address; } export default User; |
Above, we use the @OneToOne() decorator. Its argument is a function that returns the class of the entity that we want to make a relationship with.
The second decorator, the @JoinColumn(), indicates that the User entity owns the relationship. It means that the rows of the User table contain the addressId column that can keep the id of an address. We use it only on one side of the relationship.
We can look into pgAdmin to inspect what TypeORM does to create the desired relationship.
Above, we can see that the addressId is a regular integer column. It has a constraint put onto it that indicates that any value we place into the addressId column needs to match some id in the address table.
The above can be simplified without the CONSTRAINT keyword.
1 2 3 4 |
CREATE TABLE user ( // ... addressId integer REFERENCES address (id) ) |
Both ON UPDATE NO ACTION and ON DELETE NO ACTION are a default behavior. They indicate that Postgres will raise an error if we attempt to delete or change the id of an address that is currently in use.
The MATCH SIMPLE refers to a situation when we use more than one column as the foreign key. It means that we allow some of them to be null.
Inverse relationship
Currently, our relationship is unidirectional. It means that only one side of the relationship has information about the other side. We could change that by creating an inverse relationship. By doing so, we make the relationship between the User and the Address bidirectional.
To create the inverse relationship, we need to use the @OneToOne and provide a property that holds the other side of the relationship.
users/address.entity.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
import { Column, Entity, OneToOne, PrimaryGeneratedColumn } from 'typeorm'; import User from './user.entity'; @Entity() class Address { @PrimaryGeneratedColumn() public id: number; @Column() public street: string; @Column() public city: string; @Column() public country: string; @OneToOne(() => User, (user: User) => user.address) public user: User; } export default Address; |
The crucial thing is that the inverse relationship is a bit of an abstract concept, and it does not create any additional columns in the database.
Storing the information about both sides of the relationship can come in handy. We can easily relate to both sides, for example, to fetch the addresses with users.
1 2 3 |
getAllAddressesWithUsers() { return this.addressRepository.find({ relations: ['user'] }); } |
If we want our related entities always to be included, we can make our relationship eager.
1 2 3 4 5 |
@OneToOne(() => Address, { eager: true }) @JoinColumn() public address: Address; |
Now, every time we fetch users, we also get their addresses. Only one side of the relationship can be eager.
Saving the related entities
Right now, we need to save users and addresses separately and this might not be the most convenient way. Instead, we can turn on the cascade option. Thanks to that, we can save an address while saving a user.
1 2 3 4 5 6 |
@OneToOne(() => Address, { eager: true, cascade: true }) @JoinColumn() public address: Address; |
One-to-many and many-to-one
The one-to-many and many-to-one is a relationship where a row from the first table can be linked to multiple rows of the second table. Rows from the second table can be linked to just one row of the first table.
The above is a very fitting relationship to implement to posts and users that we’ve defined in the previous parts of this series. Let’s assume that a user can create multiple posts, but a post has just one author.
users/user.entity.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 |
import { Column, Entity, JoinColumn, OneToMany, OneToOne, PrimaryGeneratedColumn } from 'typeorm'; import { Exclude } from 'class-transformer'; import Address from './address.entity'; import Post from '../posts/post.entity'; @Entity() class User { @PrimaryGeneratedColumn() public id: number; @Column({ unique: true }) public email: string; @Column() public name: string; @Column() @Exclude() public password: string; @OneToOne(() => Address, { eager: true, cascade: true }) @JoinColumn() public address: Address; @OneToMany(() => Post, (post: Post) => post.author) public posts: Post[]; } export default User; |
Thanks to using the @OneToMany() decorator, one user can be linked to many posts. We also need to define the other side of this relationship.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm'; import User from '../users/user.entity'; @Entity() class Post { @PrimaryGeneratedColumn() public id: number; @Column() public title: string; @Column() public content: string; @Column({ nullable: true }) public category?: string; @ManyToOne(() => User, (author: User) => author.posts) public author: User; } export default Post; |
Thanks to the @ManyToOne() decorator, many posts can be related to one user.
We implemented the authentication in the third part of this series. When a post is created in our API, we have access to the data about the authenticated user. We need to use it to determine the author of the post.
1 2 3 4 5 |
@Post() @UseGuards(JwtAuthenticationGuard) async createPost(@Body() post: CreatePostDto, @Req() req: RequestWithUser) { return this.postsService.createPost(post, req.user); } |
1 2 3 4 5 6 7 8 |
async createPost(post: CreatePostDto, user: User) { const newPost = await this.postsRepository.create({ ...post, author: user }); await this.postsRepository.save(newPost); return newPost; } |
If we want to return a list of the posts with the authors, we can now easily do so.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
getAllPosts() { return this.postsRepository.find({ relations: ['author'] }); } async getPostById(id: number) { const post = await this.postsRepository.findOne(id, { relations: ['author'] }); if (post) { return post; } throw new PostNotFoundException(id); } async updatePost(id: number, post: UpdatePostDto) { await this.postsRepository.update(id, post); const updatedPost = await this.postsRepository.findOne(id, { relations: ['author'] }); if (updatedPost) { return updatedPost } throw new PostNotFoundException(id); } |
If we look into the database, we can see that the side of the relationship that uses ManyToOne() decorator stores the foreign key.
This means that the post stores the id of the author and not the other way around.
Many-to-many
Previously, we added a property called category to our posts. Let’s elaborate on that more.
We would like to be able to define categories reusable across posts. We also want a single post to be able to belong to multiple categories.
The above is a many-to-many relationship. It happens when a row from the first table can link to multiple rows from the second table and the other way around.
categories/category.entity.ts
1 2 3 4 5 6 7 8 9 10 11 12 |
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; @Entity() class Category { @PrimaryGeneratedColumn() public id: number; @Column() public name: string; } export default Category; |
posts/post.entity.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 |
import { Column, Entity, JoinTable, ManyToMany, ManyToOne, PrimaryGeneratedColumn } from 'typeorm'; import User from '../users/user.entity'; import Category from '../categories/category.entity'; @Entity() class Post { @PrimaryGeneratedColumn() public id: number; @Column() public title: string; @Column() public content: string; @Column({ nullable: true }) public category?: string; @ManyToOne(() => User, (author: User) => author.posts) public author: User; @ManyToMany(() => Category) @JoinTable() public categories: Category[]; } export default Post; |
When we use the @ManyToMany() and @JoinTable() decorators, TypeORM set ups an additional table. This way, neither the Post nor Category table stores the data about the relationship.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
CREATE TABLE public.post_categories_category ( "postId" integer NOT NULL, "categoryId" integer NOT NULL, CONSTRAINT "PK_91306c0021c4901c1825ef097ce" PRIMARY KEY ("postId", "categoryId"), CONSTRAINT "FK_93b566d522b73cb8bc46f7405bd" FOREIGN KEY ("postId") REFERENCES public.post (id) MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE, CONSTRAINT "FK_a5e63f80ca58e7296d5864bd2d3" FOREIGN KEY ("categoryId") REFERENCES public.category (id) MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE ) |
Above, we can see that our new post_categories_category table uses a primary key that consists of the postId and categoryId combined.
We can also make the many-to-many relationship bidirectional. Remember to use the JoinTable decorator only on one side of the relationship, though.
1 2 3 |
@ManyToMany(() => Category, (category: Category) => category.posts) @JoinTable() public categories: Category[]; |
1 2 |
@ManyToMany(() => Post, (post: Post) => post.categories) public posts: Post[]; |
Thanks to doing the above, we can now easily fetch categories along with their posts.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
getAllCategories() { return this.categoriesRepository.find({ relations: ['posts'] }); } async getCategoryById(id: number) { const category = await this.categoriesRepository.findOne(id, { relations: ['posts'] }); if (category) { return category; } throw new CategoryNotFoundException(id); } async updateCategory(id: number, category: UpdateCategoryDto) { await this.categoriesRepository.update(id, category); const updatedCategory = await this.categoriesRepository.findOne(id, { relations: ['posts'] }); if (updatedCategory) { return updatedCategory } throw new CategoryNotFoundException(id); } |
Summary
This time we’ve covered creating relationships while using NestJS with Postgres and TypeORM. It included one-to-one, one-to-many, and many-to-many. We supplied them with various options, such as cascade and eager. We’ve also looked into SQL queries that TypeORM creates, to understand better how it works.
TYSM for this helpfull, Can you write article about Grapql, Typeorm, nestjs
When I create a user with address in the body, it works. (I enabled the cascade parameter)
But when trying to update a user (which has not address yet) (PUT) with address in the body, it didn’t work.
I can’t figure out why.
can i post an address with a user inside? like
{
“street”: “test”,
“user”: {
“name”: “John”
}
}
How to save many-to-many relationships
Here’s the way I did it:
Get a list of category ids from user. (from request body, or createPostDto)
Once you have that then, you need to find all categories with the list of ids
Then you link the post with categories
How does your getCategoriesByIds look ?
Mine looks like this
What if I want not to update the category, but add another one? Which method should I use?
Hi
Can you help me please?
I hav an entity:
Also a DTO:
And a service when i’m try to update the record if it exists or create if not:
It doesn’t work because of error:
I really don’t understand this error…
how to add column “created_by” in entity? please notice it is refer to “user_id” in the same entity?
I have a problem with updating Post error message below
Cannot query across many-to-many for property categories
How to delete categories record with many to many bidirectional?