Relationships_overview
Chapter 9: Entity Relationships Overview
Section titled “Chapter 9: Entity Relationships Overview”Understanding TypeORM Relationships
Section titled “Understanding TypeORM Relationships”9.1 Relationship Types
Section titled “9.1 Relationship Types”TypeORM supports four main types of relationships between entities.
Entity Relationship Types ================================================================================
Relationships | +-----------+-----------+-----------+-----------+ | | | | | v v v v v +-------+ +-------+ +-----------+ +-------+ +-------+ | 1:1 | | 1:N | | N:1 | | N:N | | Self | |One-to-| |One-to-| | Many-to- | |Many-to| | Ref | | One | | Many | | One | | Many | | | +-------+ +-------+ +-----------+ +-------+ +-------+
================================================================================9.2 One-to-One Relationship
Section titled “9.2 One-to-One Relationship”Each entity instance is related to exactly one instance of another entity.
One-to-One Relationship +------------------------------------------------------------------+ | | | User Profile | | +------------------+ +------------------+ | | | id (PK) | | id (PK) | | | | email | 1:1 | bio | | | | password | <------> | avatar | | | | | | userId (FK, UQ) | | | +------------------+ +------------------+ | | | | Each user has exactly one profile | | Each profile belongs to exactly one user | | | +------------------------------------------------------------------+Implementation
Section titled “Implementation”import { Entity, PrimaryGeneratedColumn, Column, OneToOne, JoinColumn } from 'typeorm';import { Profile } from './profile.entity';
@Entity('users')export class User { @PrimaryGeneratedColumn() id: number;
@Column() email: string;
// One-to-One: User has one Profile @OneToOne(() => Profile, profile => profile.user) profile: Profile;}
@Entity('profiles')export class Profile { @PrimaryGeneratedColumn() id: number;
@Column() bio: string;
@Column() avatar: string;
// Foreign key column @Column({ unique: true }) userId: number;
// One-to-One: Profile belongs to User @OneToOne(() => User, user => user.profile) @JoinColumn({ name: 'userId' }) user: User;}9.3 One-to-Many Relationship
Section titled “9.3 One-to-Many Relationship”One entity instance can be related to multiple instances of another entity.
One-to-Many Relationship +------------------------------------------------------------------+ | | | User Post | | +------------------+ +------------------+ | | | id (PK) | | id (PK) | | | | email | 1:N | title | | | | name | -------> | content | | | | | | authorId (FK) | | | +------------------+ +------------------+ | | ^ | | | | N:1 | | | +----------------------------+ | | | | One user can have many posts | | Each post belongs to one user | | | +------------------------------------------------------------------+Implementation
Section titled “Implementation”import { Entity, PrimaryGeneratedColumn, Column, OneToMany, ManyToOne, JoinColumn } from 'typeorm';
@Entity('users')export class User { @PrimaryGeneratedColumn() id: number;
@Column() name: string;
// One-to-Many: User has many Posts @OneToMany(() => Post, post => post.author) posts: Post[];}
@Entity('posts')export class Post { @PrimaryGeneratedColumn() id: number;
@Column() title: string;
@Column({ type: 'text' }) content: string;
// Foreign key column @Column() authorId: number;
// Many-to-One: Post belongs to User @ManyToOne(() => User, user => user.posts) @JoinColumn({ name: 'authorId' }) author: User;}9.4 Many-to-One Relationship
Section titled “9.4 Many-to-One Relationship”The inverse of One-to-Many. Multiple instances of one entity relate to one instance of another.
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn } from 'typeorm';import { User } from './user.entity';
@Entity('comments')export class Comment { @PrimaryGeneratedColumn() id: number;
@Column({ type: 'text' }) content: string;
// Foreign key column @Column() authorId: number;
// Many-to-One: Comment belongs to User @ManyToOne(() => User, user => user.comments) @JoinColumn({ name: 'authorId' }) author: User;}9.5 Many-to-Many Relationship
Section titled “9.5 Many-to-Many Relationship”Multiple instances of one entity can be related to multiple instances of another entity.
Many-to-Many Relationship +------------------------------------------------------------------+ | | | Student Course | | +------------------+ +------------------+ | | | id (PK) | | id (PK) | | | | name | | title | | | | email | | credits | | | +------------------+ +------------------+ | | | ^ | | | | | | +-------------+--------------+ | | | | | v | | +------------------+ | | | student_courses | <-- Junction Table | | +------------------+ | | | studentId (PK,FK)| | | | courseId (PK,FK) | | | +------------------+ | | | | One student can enroll in many courses | | One course can have many students | | | +------------------------------------------------------------------+Implementation
Section titled “Implementation”import { Entity, PrimaryGeneratedColumn, Column, ManyToMany, JoinTable } from 'typeorm';import { Course } from './course.entity';
@Entity('students')export class Student { @PrimaryGeneratedColumn() id: number;
@Column() name: string;
// Many-to-Many: Student has many Courses @ManyToMany(() => Course, course => course.students) @JoinTable() // Creates junction table courses: Course[];}
@Entity('courses')export class Course { @PrimaryGeneratedColumn() id: number;
@Column() title: string;
// Many-to-Many: Course has many Students @ManyToMany(() => Student, student => student.courses) students: Student[];}9.6 Relationship Decorators
Section titled “9.6 Relationship Decorators” Relationship Decorators +------------------------------------------------------------------+ | | | Decorator | Purpose | | -------------------|------------------------------------------| | @OneToOne() | One-to-one relationship | | @OneToMany() | One-to-many relationship | | @ManyToOne() | Many-to-one relationship | | @ManyToMany() | Many-to-many relationship | | @JoinColumn() | Specify foreign key column | | @JoinTable() | Specify junction table (M:N) | | | +------------------------------------------------------------------+9.7 @JoinColumn() Options
Section titled “9.7 @JoinColumn() Options”import { Entity, PrimaryGeneratedColumn, Column, OneToOne, JoinColumn } from 'typeorm';import { Profile } from './profile.entity';
@Entity('users')export class User { @PrimaryGeneratedColumn() id: number;
// Basic JoinColumn @OneToOne(() => Profile) @JoinColumn() profile: Profile; // Creates column: profileId
// Custom column name @OneToOne(() => Profile) @JoinColumn({ name: 'profile_id' }) profile: Profile; // Creates column: profile_id
// Multiple join columns (composite key) @OneToOne(() => Profile) @JoinColumn([ { name: 'profile_id', referencedColumnName: 'id' }, { name: 'profile_type', referencedColumnName: 'type' }, ]) profile: Profile;
// With foreign key constraints @OneToOne(() => Profile) @JoinColumn({ name: 'profileId', referencedColumnName: 'id', foreignKeyConstraintName: 'fk_user_profile', onDelete: 'CASCADE', onUpdate: 'CASCADE', }) profile: Profile;}9.8 @JoinTable() Options
Section titled “9.8 @JoinTable() Options”import { Entity, PrimaryGeneratedColumn, Column, ManyToMany, JoinTable } from 'typeorm';import { Course } from './course.entity';
@Entity('students')export class Student { @PrimaryGeneratedColumn() id: number;
// Basic JoinTable @ManyToMany(() => Course, course => course.students) @JoinTable() courses: Course[]; // Creates table: student_courses
// Custom table name @ManyToMany(() => Course, course => course.students) @JoinTable({ name: 'enrollments' }) courses: Course[]; // Creates table: enrollments
// Custom column names @ManyToMany(() => Course, course => course.students) @JoinTable({ name: 'enrollments', joinColumn: { name: 'student_id', referencedColumnName: 'id', }, inverseJoinColumn: { name: 'course_id', referencedColumnName: 'id', }, }) courses: Course[]; // Creates table: enrollments // Columns: student_id, course_id
// With constraints @ManyToMany(() => Course, course => course.students) @JoinTable({ name: 'enrollments', joinColumn: { name: 'studentId', referencedColumnName: 'id', foreignKeyConstraintName: 'fk_enrollment_student', }, inverseJoinColumn: { name: 'courseId', referencedColumnName: 'id', foreignKeyConstraintName: 'fk_enrollment_course', }, }) courses: Course[];}9.9 Eager vs Lazy Loading
Section titled “9.9 Eager vs Lazy Loading”Eager Loading
Section titled “Eager Loading”Relations are loaded automatically with the entity.
import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from 'typeorm';
@Entity('users')export class User { @PrimaryGeneratedColumn() id: number;
@Column() name: string;
// Eager loading - always loaded @OneToMany(() => Post, post => post.author, { eager: true }) posts: Post[];}
// Usage - posts are automatically loadedconst user = await userRepository.findOne({ where: { id: 1 } });console.log(user.posts); // Already loadedLazy Loading
Section titled “Lazy Loading”Relations are loaded on demand (using Promises).
import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from 'typeorm';
@Entity('users')export class User { @PrimaryGeneratedColumn() id: number;
@Column() name: string;
// Lazy loading - loaded on demand @OneToMany(() => Post, post => post.author, { lazy: true }) posts: Promise<Post[]>;}
// Usage - posts loaded when accessedconst user = await userRepository.findOne({ where: { id: 1 } });console.log(user.posts); // Promiseconsole.log(await user.posts); // Loaded nowManual Loading
Section titled “Manual Loading”Load relations explicitly using find options.
// Using relations optionconst user = await userRepository.findOne({ where: { id: 1 }, relations: ['posts', 'posts.comments'],});
// Using join optionconst user = await userRepository.findOne({ where: { id: 1 }, join: { alias: 'user', leftJoinAndSelect: ['user.posts', 'posts.comments'], },});9.10 Cascading Operations
Section titled “9.10 Cascading Operations”Cascade options determine how operations on an entity affect related entities.
Cascade Options +------------------------------------------------------------------+ | | | Option | Effect | | ----------------|--------------------------------------------- | | CASCADE | Insert, Update, Delete propagate | | INSERT | Insert propagates to related entities | | UPDATE | Update propagates to related entities | | REMOVE | Delete propagates to related entities | | DETACH | Remove relation without deleting | | SOFT_REMOVE | Soft delete propagates | | RECOVER | Recover propagates | | | +------------------------------------------------------------------+Cascade Examples
Section titled “Cascade Examples”import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from 'typeorm';
@Entity('users')export class User { @PrimaryGeneratedColumn() id: number;
@Column() name: string;
// Cascade all operations @OneToMany(() => Post, post => post.author, { cascade: true, }) posts: Post[];
// Cascade specific operations @OneToMany(() => Comment, comment => comment.author, { cascade: ['insert', 'update'], }) comments: Comment[];}
// Usage with cascadeconst user = new User();user.name = 'John';user.posts = [ { title: 'Post 1', content: 'Content 1' }, { title: 'Post 2', content: 'Content 2' },];
// Saves user AND posts (because cascade: true)await userRepository.save(user);9.11 Orphan Removal
Section titled “9.11 Orphan Removal”When a related entity is removed from a collection, it gets deleted.
import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from 'typeorm';
@Entity('users')export class User { @PrimaryGeneratedColumn() id: number;
@Column() name: string;
@OneToMany(() => Post, post => post.author, { orphanRemoval: true, }) posts: Post[];}
// Usageconst user = await userRepository.findOne({ where: { id: 1 }, relations: ['posts'],});
// Remove post from collectionuser.posts = user.posts.filter(p => p.id !== 1);
// Save user - post with id=1 is DELETEDawait userRepository.save(user);9.12 Self-Referencing Relationships
Section titled “9.12 Self-Referencing Relationships”An entity can have relationships to itself.
Self-Referencing One-to-Many (Tree Structure)
Section titled “Self-Referencing One-to-Many (Tree Structure)”import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, OneToMany, JoinColumn } from 'typeorm';
@Entity('categories')export class Category { @PrimaryGeneratedColumn() id: number;
@Column() name: string;
// Parent category @ManyToOne(() => Category, category => category.children) @JoinColumn({ name: 'parentId' }) parent: Category;
@Column({ nullable: true }) parentId: number;
// Child categories @OneToMany(() => Category, category => category.parent) children: Category[];}
// Usageconst parent = new Category();parent.name = 'Electronics';await categoryRepository.save(parent);
const child = new Category();child.name = 'Phones';child.parent = parent;await categoryRepository.save(child);Self-Referencing Many-to-Many
Section titled “Self-Referencing Many-to-Many”import { Entity, PrimaryGeneratedColumn, Column, ManyToMany, JoinTable } from 'typeorm';
@Entity('users')export class User { @PrimaryGeneratedColumn() id: number;
@Column() name: string;
// Users this user follows @ManyToMany(() => User, user => user.followers) @JoinTable({ name: 'user_follows', joinColumn: { name: 'followerId' }, inverseJoinColumn: { name: 'followingId' }, }) following: User[];
// Users following this user @ManyToMany(() => User, user => user.following) followers: User[];}9.13 Default Relation Values
Section titled “9.13 Default Relation Values”import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from 'typeorm';
@Entity('posts')export class Post { @PrimaryGeneratedColumn() id: number;
@Column() title: string;
// Relation with default value @ManyToOne(() => User, { nullable: true }) @JoinColumn({ name: 'authorId' }) author: User | null;
@Column({ nullable: true }) authorId: number | null;}9.14 Relationship Best Practices
Section titled “9.14 Relationship Best Practices” Relationship Best Practices +------------------------------------------------------------------+ | | | 1. Always define both sides of a relationship | | @OneToMany + @ManyToOne | | @OneToOne + @OneToOne | | @ManyToMany + @ManyToMany | | | | 2. Use eager loading sparingly | | - Can cause N+1 problems | | - Use for small, always-needed relations | | | | 3. Be careful with cascade: true | | - Can accidentally delete data | | - Use specific cascade options | | | | 4. Use orphanRemoval for child entities | | - Ensures no orphaned records | | | | 5. Consider lazy loading for large relations | | - Reduces initial query size | | | +------------------------------------------------------------------+9.15 Summary
Section titled “9.15 Summary” Relationship Quick Reference +------------------------------------------------------------------+ | | | Relationship | Decorators | Table Structure | | ----------------|-------------------|---------------------------| | One-to-One | @OneToOne | FK on one side | | One-to-Many | @OneToMany | FK on "many" side | | Many-to-One | @ManyToOne | FK on this side | | Many-to-Many | @ManyToMany | Junction table | | Self-Reference | @ManyToOne | FK to same table | | | @OneToMany | | | | +------------------------------------------------------------------+Next Chapter
Section titled “Next Chapter”Chapter 10: Advanced Entity Patterns
Last Updated: February 2026