On this page
TypeORM integration and entity definitions
TypeORM in NestJS
TypeORM is the most popular ORM for TypeScript and Node.js applications. NestJS provides first-class integration through the @nestjs/typeorm package, which wires TypeORM's connection management into the module system and exposes repositories through dependency injection.
Install the required packages:
npm install @nestjs/typeorm typeorm pgFor SQLite (development/testing) or MySQL, replace pg with better-sqlite3 or mysql2.
Configuring the database connection
Register TypeOrmModule.forRoot() in AppModule once. This creates the database connection and makes it available across the application:
TypeOrmModule.forRoot({
type: 'postgres',
host: 'localhost',
port: 5432,
username: 'postgres',
password: 'postgres',
database: 'bookstore',
entities: [Book, Author, Category],
synchronize: true, // development only!
})The entities array lists all entity classes that TypeORM should manage. Alternatively, use autoLoadEntities: true combined with TypeOrmModule.forFeature([Book]) in each feature module — TypeORM then collects entities automatically.
Defining entities
An entity is a TypeScript class decorated with @Entity() that maps to a database table. Each property decorated with @Column() corresponds to a table column.
Primary keys
// Auto-incrementing integer (most common)
@PrimaryGeneratedColumn()
id: number;
// UUID primary key
@PrimaryGeneratedColumn('uuid')
id: string;
// Composite primary key
@PrimaryColumn()
userId: number;
@PrimaryColumn()
bookId: number;Column types
@Column({ length: 200 })
title: string; // VARCHAR(200)
@Column('text')
description: string; // TEXT
@Column('decimal', { precision: 10, scale: 2 })
price: number; // DECIMAL(10, 2)
@Column('int')
stock: number; // INTEGER
@Column('boolean', { default: true })
isActive: boolean;
@Column('jsonb', { nullable: true })
metadata: Record<string, unknown> | null; // JSONB (PostgreSQL)
@Column('simple-array')
tags: string[]; // Stored as comma-separated stringTimestamps
@CreateDateColumn()
createdAt: Date; // Auto-set on INSERT
@UpdateDateColumn()
updatedAt: Date; // Auto-updated on UPDATE
@DeleteDateColumn()
deletedAt: Date | null; // For soft deletesColumn options
Common column options:
@Column({
name: 'cover_url', // Custom DB column name
nullable: true, // Allow NULL
unique: true, // UNIQUE constraint
default: 'default.jpg',
select: false, // Exclude from default SELECT
insert: false, // Cannot be set on insert
update: false, // Cannot be changed after insert
})
coverUrl: string | null;Indexes
Define database indexes on entities for query performance:
import { Entity, Index } from 'typeorm';
@Entity('books')
@Index(['title', 'authorId']) // Composite index
@Index(['isbn'], { unique: true }) // Unique index
export class Book {
@Column()
@Index() // Single column index
title: string;
}Embedding entities
For reusable groups of columns, use embedded entities:
export class Address {
@Column()
street: string;
@Column()
city: string;
@Column()
country: string;
}
@Entity()
export class Author {
@Column(() => Address)
address: Address;
}TypeORM flattens the embedded columns into the parent table as addressStreet, addressCity, addressCountry.
Registering entities per module
Each feature module registers the entities it needs using TypeOrmModule.forFeature():
// books.module.ts
@Module({
imports: [TypeOrmModule.forFeature([Book])],
controllers: [BooksController],
providers: [BooksService],
exports: [TypeOrmModule], // Export so other modules can inject BookRepository
})
export class BooksModule {}This makes the Repository<Book> injectable in BooksService:
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
@Injectable()
export class BooksService {
constructor(
@InjectRepository(Book)
private readonly booksRepository: Repository<Book>,
) {}
}The DataSource
For advanced queries, transactions, and migrations, inject the DataSource directly:
import { DataSource } from 'typeorm';
@Injectable()
export class BooksService {
constructor(
@InjectRepository(Book)
private readonly booksRepository: Repository<Book>,
private readonly dataSource: DataSource,
) {}
async transferBooks(fromId: number, toId: number): Promise<void> {
await this.dataSource.transaction(async (manager) => {
await manager.update(Book, { authorId: fromId }, { authorId: toId });
});
}
}Entity listeners
TypeORM entities can define lifecycle hooks:
import { BeforeInsert, BeforeUpdate, AfterLoad } from 'typeorm';
import * as bcrypt from 'bcrypt';
@Entity()
export class User {
@Column()
password: string;
@BeforeInsert()
@BeforeUpdate()
async hashPassword(): Promise<void> {
if (this.password) {
this.password = await bcrypt.hash(this.password, 10);
}
}
@AfterLoad()
setFullName(): void {
this.fullName = `${this.firstName} ${this.lastName}`;
}
}Available hooks: @AfterLoad, @BeforeInsert, @AfterInsert, @BeforeUpdate, @AfterUpdate, @BeforeRemove, @AfterRemove, @BeforeSoftRemove, @AfterSoftRemove, @BeforeRecover, @AfterRecover.
Soft deletes
Instead of deleting rows permanently, mark them as deleted with a timestamp:
@Entity()
export class Book {
@DeleteDateColumn()
deletedAt: Date | null;
}Then use softDelete() and restore():
// Soft delete
await this.booksRepository.softDelete(id);
// Restore
await this.booksRepository.restore(id);
// Find including soft-deleted
await this.booksRepository.find({ withDeleted: true });This is a common requirement for audit trails and compliance where data cannot be permanently deleted.
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
ManyToOne,
JoinColumn,
} from 'typeorm';
import { Author } from '../authors/author.entity';
@Entity('books')
export class Book {
@PrimaryGeneratedColumn()
id: number;
@Column({ length: 200 })
title: string;
@Column({ unique: true, length: 13 })
isbn: string;
@Column('decimal', { precision: 8, scale: 2 })
price: number;
@Column({ default: true })
isAvailable: boolean;
@Column({ nullable: true })
coverUrl: string | null;
@ManyToOne(() => Author, (author) => author.books, {
nullable: false,
onDelete: 'RESTRICT',
})
@JoinColumn({ name: 'author_id' })
author: Author;
@Column({ name: 'author_id' })
authorId: number;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
}
Sign in to track your progress