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.

Leave a Comment

Your email address will not be published. Required fields are marked *