Schema Relationships and Migrations in NestJS

In a relational database, schema relationships define how different tables (entities) are connected to each other. Understanding these relationships is crucial when designing a database for a NestJS application. This module will guide you through the process of defining schema relationships using TypeORM and running migrations to update your database schema over time.

We’ll cover the following concepts in this module:

  1. One-to-One, One-to-Many, and Many-to-Many Relationships.
  2. Using TypeORM to define these relationships in your NestJS entities.
  3. Running migrations to keep your database schema in sync with your codebase.

Table of Contents

  1. Introduction to Schema Relationships
  2. Defining One-to-One Relationships
  3. Defining One-to-Many Relationships
  4. Defining Many-to-Many Relationships
  5. Using TypeORM Migrations
  6. Running Migrations in NestJS
  7. Conclusion

Introduction to Schema Relationships

Schema relationships in relational databases are essential for modeling the real-world connections between different entities. Some of the common types of relationships are:

  • One-to-One: A relationship where each record in one table is related to only one record in another table.
  • One-to-Many: A relationship where a record in one table can be related to multiple records in another table.
  • Many-to-Many: A relationship where multiple records in one table can be related to multiple records in another table.

In NestJS, TypeORM makes it easy to define and manage these relationships through decorators in your entity classes.

Defining One-to-One Relationships

A One-to-One relationship occurs when a single record in one table is associated with only one record in another table. For example, a User might have one Profile.

Example: Defining One-to-One Relationship

typescriptCopyEdit// src/user/user.entity.ts
import { Entity, PrimaryGeneratedColumn, Column, OneToOne, JoinColumn } from 'typeorm';
import { Profile } from '../profile/profile.entity';

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

  @Column()
  name: string;

  @OneToOne(() => Profile)
  @JoinColumn()
  profile: Profile;
}
typescriptCopyEdit// src/profile/profile.entity.ts
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';

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

  @Column()
  bio: string;
}

In this example:

  • @OneToOne() establishes the one-to-one relationship between User and Profile.
  • @JoinColumn() specifies that the User entity will hold the foreign key to the Profile entity.

Defining One-to-Many Relationships

A One-to-Many relationship happens when one record in a table is related to multiple records in another table. For instance, a Category can have multiple Products.

Example: Defining One-to-Many Relationship

typescriptCopyEdit// src/category/category.entity.ts
import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from 'typeorm';
import { Product } from '../product/product.entity';

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

  @Column()
  name: string;

  @OneToMany(() => Product, (product) => product.category)
  products: Product[];
}
typescriptCopyEdit// src/product/product.entity.ts
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from 'typeorm';
import { Category } from '../category/category.entity';

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

  @Column()
  name: string;

  @ManyToOne(() => Category, (category) => category.products)
  category: Category;
}

In this example:

  • @OneToMany() defines the one-to-many relationship from Category to Product.
  • @ManyToOne() establishes the inverse relationship in the Product entity.

Defining Many-to-Many Relationships

A Many-to-Many relationship occurs when multiple records in one table are related to multiple records in another table. For example, an Author can write multiple Books, and a Book can be written by multiple Authors.

Example: Defining Many-to-Many Relationship

typescriptCopyEdit// src/author/author.entity.ts
import { Entity, PrimaryGeneratedColumn, Column, ManyToMany, JoinTable } from 'typeorm';
import { Book } from '../book/book.entity';

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

  @Column()
  name: string;

  @ManyToMany(() => Book)
  @JoinTable()
  books: Book[];
}
typescriptCopyEdit// src/book/book.entity.ts
import { Entity, PrimaryGeneratedColumn, Column, ManyToMany } from 'typeorm';
import { Author } from '../author/author.entity';

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

  @Column()
  title: string;

  @ManyToMany(() => Author, (author) => author.books)
  authors: Author[];
}

In this example:

  • @ManyToMany() defines the many-to-many relationship between Author and Book.
  • @JoinTable() is used on one side of the relationship to specify the junction table that will manage the many-to-many relationship.

Using TypeORM Migrations

A migration in TypeORM allows you to manage and apply schema changes to your database. Migrations are helpful when you need to track changes to your database schema over time and keep your production and development environments in sync.

Creating a Migration

To create a migration, you can use the following command:

bashCopyEditnpx typeorm migration:generate -n MigrationName

This will create a migration file in the src/migration folder with the necessary SQL to update the database schema based on the changes in your entities.

Example: Migration for Schema Changes

Suppose you add a new column to an existing entity. You would create a migration as shown above, which might look something like this:

typescriptCopyEdit// src/migration/1618560247852-AddNewColumnToUser.ts
import {MigrationInterface, QueryRunner} from "typeorm";

export class AddNewColumnToUser1618560247852 implements MigrationInterface {
    public async up(queryRunner: QueryRunner): Promise<void> {
        await queryRunner.addColumn("user", new TableColumn({
            name: "newColumn",
            type: "varchar",
            isNullable: true,
        }));
    }

    public async down(queryRunner: QueryRunner): Promise<void> {
        await queryRunner.dropColumn("user", "newColumn");
    }
}

In the up() method, you define the changes to apply to the schema, and in the down() method, you define how to revert those changes.

Running Migrations in NestJS

After creating your migration, you need to run it to apply the changes to the database. Use the following command:

bashCopyEditnpx typeorm migration:run

This will execute the migrations and update your database schema.

To revert a migration, you can use:

bashCopyEditnpx typeorm migration:revert

Conclusion

In this module, we learned how to define schema relationships in NestJS using TypeORM. We explored One-to-One, One-to-Many, and Many-to-Many relationships, which allow you to model complex data structures in your database. Additionally, we covered how to create and run migrations to keep your database schema in sync with the application code.

By using schema relationships and migrations, you can maintain a well-structured and consistent database while ensuring that your application’s data model evolves in a controlled and predictable manner.