Mastering TypeORM Relationships in NestJS: A Complete Guide

One of the superpowers of building with NestJS is how seamlessly it integrates with TypeORM, giving developers a clean, structured way to model database relationships using decorators and entities. But while getting started with basic models is easy, understanding relationships—how tables/entities connect—is where things get more interesting, and sometimes confusing. In this post, we’ll break down how to model and use relationships in TypeORM with NestJS, covering the following: One-to-One One-to-Many / Many-to-One Many-to-Many How to use relations in services How to load related data Tips and pitfalls Let’s dive in. What is a Relationship in TypeORM? In database design, a relationship is how tables (or entities in ORM) connect. Think: A user has one profile A post has many comments A student can be enrolled in many courses, and vice versa These connections are defined using relation decorators like @OneToOne, @ManyToOne, @OneToMany, and @ManyToMany in TypeORM. Setting Up: Example Models We’ll use the example of a blogging system, with User, Post, and Comment entities. npm install --save @nestjs/typeorm typeorm 1. One-to-One: User & Profile Scenario: Each user has one profile. Each profile belongs to one user. User Entity @Entity() export class User { @PrimaryGeneratedColumn() id: number; @Column() username: string; @OneToOne(() => Profile, (profile) => profile.user, { cascade: true }) @JoinColumn() profile: Profile; } Profile Entity @Entity() export class Profile { @PrimaryGeneratedColumn() id: number; @Column() bio: string; @OneToOne(() => User, (user) => user.profile) user: User; } Note: @JoinColumn() is used on the side that owns the relationship. 2. One-to-Many / Many-to-One: Post & Comment Scenario: One post can have many comments. Each comment belongs to one post. Post Entity @Entity() export class Post { @PrimaryGeneratedColumn() id: number; @Column() title: string; @OneToMany(() => Comment, (comment) => comment.post, { cascade: true }) comments: Comment[]; } Comment Entity @Entity() export class Comment { @PrimaryGeneratedColumn() id: number; @Column() content: string; @ManyToOne(() => Post, (post) => post.comments) post: Post; } This is probably the most commonly used relationship in apps: a user has many posts, an order has many items, etc. 3. Many-to-Many: Students & Courses Scenario: A student can be enrolled in many courses. A course can have many students. Student Entity @Entity() export class Student { @PrimaryGeneratedColumn() id: number; @Column() name: string; @ManyToMany(() => Course, (course) => course.students) @JoinTable() courses: Course[]; } Course Entity @Entity() export class Course { @PrimaryGeneratedColumn() id: number; @Column() title: string; @ManyToMany(() => Student, (student) => student.courses) students: Student[]; } Use @JoinTable() on one side only to define the owning side of the relationship. Loading Relationships in Services In your services, you can load related entities using the relations option in find or findOne. Example: // post.service.ts findAll() { return this.postRepository.find({ relations: ['comments'], }); } Nested Relations: this.userRepository.find({ relations: ['profile', 'posts', 'posts.comments'], }); This is powerful for loading deeply nested related data in one go. Creating and Saving Relationships You can also save entities with their relations using cascade options: const user = this.userRepository.create({ username: 'john_doe', profile: { bio: 'Fullstack dev', }, }); await this.userRepository.save(user); Make sure you set cascade: true on the relation if you're doing this. Pitfalls & Best Practices Don’t overfetch: Only include relations you need. Too many joins = slow queries. Use DTOs: Don’t expose raw entities in your controllers. Transform them into DTOs. Avoid circular relations in JSON: Be careful when returning nested entities—you can easily create circular JSON references. Use transactions: When saving deeply nested relationships that depend on each other, wrap in a transaction. Always index foreign keys: Speeds up joins significantly. In Summary TypeORM relationships + NestJS provide a declarative and powerful way to work with relational databases. To recap: Use @OneToOne, @OneToMany, @ManyToOne, and @ManyToMany to model relationships. Use relations in .find() queries to pull in related data. Use cascade when saving nested entities. Always structure your data flow using services and DTOs, not raw entities. Thanks for reading!.

Apr 25, 2025 - 10:07
 0
Mastering TypeORM Relationships in NestJS: A Complete Guide

One of the superpowers of building with NestJS is how seamlessly it integrates with TypeORM, giving developers a clean, structured way to model database relationships using decorators and entities.

But while getting started with basic models is easy, understanding relationships—how tables/entities connect—is where things get more interesting, and sometimes confusing.

In this post, we’ll break down how to model and use relationships in TypeORM with NestJS, covering the following:

  • One-to-One
  • One-to-Many / Many-to-One
  • Many-to-Many
  • How to use relations in services
  • How to load related data
  • Tips and pitfalls

Let’s dive in.

What is a Relationship in TypeORM?

In database design, a relationship is how tables (or entities in ORM) connect. Think:

  • A user has one profile
  • A post has many comments
  • A student can be enrolled in many courses, and vice versa

These connections are defined using relation decorators like @OneToOne, @ManyToOne, @OneToMany, and @ManyToMany in TypeORM.

Setting Up: Example Models

We’ll use the example of a blogging system, with User, Post, and Comment entities.

npm install --save @nestjs/typeorm typeorm

1. One-to-One: User & Profile

Scenario:

Each user has one profile. Each profile belongs to one user.

User Entity

@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  username: string;

  @OneToOne(() => Profile, (profile) => profile.user, { cascade: true })
  @JoinColumn()
  profile: Profile;
}

Profile Entity

@Entity()
export class Profile {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  bio: string;

  @OneToOne(() => User, (user) => user.profile)
  user: User;
}

Note: @JoinColumn() is used on the side that owns the relationship.

2. One-to-Many / Many-to-One: Post & Comment

Scenario:

One post can have many comments. Each comment belongs to one post.

Post Entity

@Entity()
export class Post {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  title: string;

  @OneToMany(() => Comment, (comment) => comment.post, { cascade: true })
  comments: Comment[];
}

Comment Entity

@Entity()
export class Comment {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  content: string;

  @ManyToOne(() => Post, (post) => post.comments)
  post: Post;
}

This is probably the most commonly used relationship in apps: a user has many posts, an order has many items, etc.

3. Many-to-Many: Students & Courses

Scenario:

A student can be enrolled in many courses. A course can have many students.

Student Entity

@Entity()
export class Student {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;

  @ManyToMany(() => Course, (course) => course.students)
  @JoinTable()
  courses: Course[];
}

Course Entity

@Entity()
export class Course {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  title: string;

  @ManyToMany(() => Student, (student) => student.courses)
  students: Student[];
}

Use @JoinTable() on one side only to define the owning side of the relationship.

Loading Relationships in Services

In your services, you can load related entities using the relations option in find or findOne.

Example:

// post.service.ts
findAll() {
  return this.postRepository.find({
    relations: ['comments'],
  });
}

Nested Relations:

this.userRepository.find({
  relations: ['profile', 'posts', 'posts.comments'],
});

This is powerful for loading deeply nested related data in one go.

Creating and Saving Relationships

You can also save entities with their relations using cascade options:

const user = this.userRepository.create({
  username: 'john_doe',
  profile: {
    bio: 'Fullstack dev',
  },
});

await this.userRepository.save(user);

Make sure you set cascade: true on the relation if you're doing this.

Pitfalls & Best Practices

  • Don’t overfetch: Only include relations you need. Too many joins = slow queries.
  • Use DTOs: Don’t expose raw entities in your controllers. Transform them into DTOs.
  • Avoid circular relations in JSON: Be careful when returning nested entities—you can easily create circular JSON references.
  • Use transactions: When saving deeply nested relationships that depend on each other, wrap in a transaction.
  • Always index foreign keys: Speeds up joins significantly.

In Summary

TypeORM relationships + NestJS provide a declarative and powerful way to work with relational databases.

To recap:

  • Use @OneToOne, @OneToMany, @ManyToOne, and @ManyToMany to model relationships.
  • Use relations in .find() queries to pull in related data.
  • Use cascade when saving nested entities.
  • Always structure your data flow using services and DTOs, not raw entities.

Thanks for reading!.