NestJS Entities
An entity is a TypeScript class that maps to a database table. Each property of the class maps to a column in the table. Each instance of the class represents one row. TypeORM reads your entity definitions and creates or updates the corresponding database tables automatically when synchronize: true is enabled during development.
The Table-to-Class Mapping
Database Table: users
┌────┬────────┬───────────────────┬─────┐
│ id │ name │ email │ age │
├────┼────────┼───────────────────┼─────┤
│ 1 │ Alice │ alice@example.com │ 30 │
│ 2 │ Bob │ bob@example.com │ 25 │
└────┴────────┴───────────────────┴─────┘
TypeScript Entity: User class
{ id: 1, name: 'Alice', email: 'alice@example.com', age: 30 }
TypeORM keeps the class and the table in sync. Define the class once, and TypeORM handles the SQL schema.
Creating a Basic Entity
// user.entity.ts
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';
@Entity('users') // maps to the 'users' table
export class User {
@PrimaryGeneratedColumn()
id: number; // auto-incrementing primary key
@Column()
name: string;
@Column({ unique: true })
email: string;
@Column()
age: number;
@Column({ default: 'user' })
role: string;
@Column({ select: false })
password: string; // excluded from SELECT queries by default
@CreateDateColumn()
createdAt: Date; // set automatically on insert
@UpdateDateColumn()
updatedAt: Date; // updated automatically on every save
}
Entity Decorators Explained
Decorator | Purpose
--------------------------------|--------------------------------------------
@Entity('table_name') | Marks class as a DB table
@PrimaryGeneratedColumn() | Auto-increment integer primary key
@PrimaryGeneratedColumn('uuid') | UUID primary key
@Column() | Regular column
@Column({ unique: true }) | Column with unique constraint
@Column({ nullable: true }) | Column that allows NULL
@Column({ default: 'value' }) | Column with a default value
@Column({ select: false }) | Excluded from default SELECT queries
@CreateDateColumn() | Auto-set to INSERT timestamp
@UpdateDateColumn() | Auto-updated on every UPDATE
@DeleteDateColumn() | Used for soft deletes (sets timestamp)
Column Types
TypeORM infers the SQL column type from the TypeScript property type in most cases. You can specify the type explicitly when needed:
@Column({ type: 'varchar', length: 255 })
name: string;
@Column({ type: 'int' })
age: number;
@Column({ type: 'decimal', precision: 10, scale: 2 })
price: number;
@Column({ type: 'text' })
description: string;
@Column({ type: 'boolean', default: true })
isActive: boolean;
@Column({ type: 'json', nullable: true })
metadata: Record<string, any>;
UUID Primary Keys
Using auto-incrementing integers as primary keys is simple, but UUIDs are safer when IDs are exposed in public URLs — they prevent enumeration attacks where a client guesses sequential IDs:
@PrimaryGeneratedColumn('uuid')
id: string; // '3f6a1b2c-7d4e-8f9a-0b1c-2d3e4f5a6b7c'
Soft Deletes
Hard delete removes a record permanently. Soft delete marks a record as deleted without removing it from the database. Use @DeleteDateColumn() and enable soft-delete mode on the repository:
@DeleteDateColumn() deletedAt: Date | null; // null = active, timestamp = deleted // In service: await this.userRepository.softRemove(user); // sets deletedAt await this.userRepository.restore(user.id); // clears deletedAt
TypeORM automatically excludes soft-deleted records from all find queries. They stay in the database and can be restored if needed.
Embedded Entities
When multiple entities share a set of columns (like address fields), you can define an embedded class and reuse it:
export class Address {
@Column()
street: string;
@Column()
city: string;
@Column()
country: string;
}
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column(() => Address)
address: Address; // user table gets street, city, country columns
}
Entity Listeners
TypeORM entities support lifecycle hooks — methods that run automatically at specific points in the entity's lifecycle:
@Entity()
export class User {
@BeforeInsert()
async hashPassword() {
this.password = await bcrypt.hash(this.password, 10);
}
@AfterLoad()
setFullName() {
this.fullName = `${this.firstName} ${this.lastName}`;
}
}
Lifecycle Hooks: @BeforeInsert() ← runs before INSERT @AfterInsert() ← runs after INSERT @BeforeUpdate() ← runs before UPDATE @AfterUpdate() ← runs after UPDATE @BeforeRemove() ← runs before DELETE @AfterRemove() ← runs after DELETE @AfterLoad() ← runs after SELECT
Entities define the blueprint of your database. Every column, every constraint, every default value, and every relationship lives in the entity class. TypeORM reads these definitions and manages the SQL schema, so your database structure always matches your TypeScript code.
