One-to-Many, Many-to-Many, Eager or Lazy
Relations & Eager Loading
Map relationships between entities and decide when to load them — without walking into an N+1 trap.
What you'll learn
- Define @OneToMany / @ManyToOne / @ManyToMany
- Use the relations option in find
- Understand N+1 and how to avoid it
Real schemas have relationships: a user has many posts, a post belongs to a user, a post has many tags. TypeORM expresses these with relation decorators that mirror the SQL.
One-to-Many / Many-to-One
The foreign key lives on the many side. That’s the side with
@ManyToOne.
@Entity()
export class User {
@PrimaryGeneratedColumn() id: number;
@OneToMany(() => Post, (p) => p.user)
posts: Post[];
}
@Entity()
export class Post {
@PrimaryGeneratedColumn() id: number;
@Column() title: string;
@ManyToOne(() => User, (u) => u.posts)
user: User;
} The arrow function (() => Post) avoids circular import problems at class
declaration time. The second argument tells TypeORM the inverse side.
Many-to-Many
When both sides can have multiple of the other, use @ManyToMany. One
side owns the join table — that’s the side with @JoinTable().
@Entity()
export class Post {
@ManyToMany(() => Tag, (t) => t.posts)
@JoinTable()
tags: Tag[];
}
@Entity()
export class Tag {
@ManyToMany(() => Post, (p) => p.tags)
posts: Post[];
} Loading Relations
By default, relations are not loaded. You opt in per query:
await this.repo.find({
relations: { posts: true },
});
// Nested relations work too
await this.repo.find({
relations: { posts: { tags: true } },
}); You can also set eager: true on the relation decorator to always load
it. Convenient for small, always-needed children — risky for large ones.
The N+1 Problem
The classic trap. You load 100 users, then loop and access user.posts —
TypeORM fires 100 separate queries. The fix is to ask for the relation in
one shot:
// Bad: N+1 queries
const users = await this.repo.find();
for (const u of users) console.log(u.posts.length); // each access = a query
// Good: one query
const users = await this.repo.find({ relations: { posts: true } }); For really wide trees, leftJoinAndSelect in a QueryBuilder gives you
more control over column selection and conditions.