DEV Community

Cover image for Mastering TypeORM Relationships in NestJS: A Complete Guide
Henry
Henry

Posted on

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

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

Profile Entity

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

  @Column()
  bio: string;

  @OneToOne(() => User, (user) => user.profile)
  user: User;
}
Enter fullscreen mode Exit fullscreen mode

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[];
}
Enter fullscreen mode Exit fullscreen mode

Comment Entity

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

  @Column()
  content: string;

  @ManyToOne(() => Post, (post) => post.comments)
  post: Post;
}
Enter fullscreen mode Exit fullscreen mode

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[];
}
Enter fullscreen mode Exit fullscreen mode

Course Entity

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

  @Column()
  title: string;

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

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

Nested Relations:

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

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

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!.

Top comments (0)