Fullstack CourseLearn by building
Back to week 2

Topic

Entities

Definition

A TypeORM entity is a TypeScript class decorated with @Entity whose properties, marked with column decorators such as @Column, @PrimaryGeneratedColumn, @CreateDateColumn, and @UpdateDateColumn, map to the columns of a single database table.

In simpler words

An entity is the SQL table from earlier, written as a typed class so the rest of the app can read and write rows as objects instead of raw SQL strings.

User and Ticket in this repo are the entities behind every users/tickets row you have already seen in raw SQL.

After this you can

  • Map every column decorator in User/Ticket back to its SQL column
  • Explain what @PrimaryGeneratedColumn('uuid') and the date columns generate
  • Recognize column options (unique, nullable, default, length) as the TypeScript form of SQL constraints

@Entity and @Column map class to table

Definition

@Entity({ name: ... }) associates a class with a specific table name, and @Column (with options) associates a property with a specific column, its type, and its constraints.

In simpler words

Read an entity top-to-bottom and you are reading the same information as the CREATE TABLE statement, just in TypeScript.

User maps to the users table; every @Column here corresponds 1:1 to a column you already saw in InitSchema.

Column options mirror SQL constraints: unique, nullable, default, length, and an explicit type when it differs from the TypeScript type.

User entity

@Entity({ name: 'users' })
export class User {
  @PrimaryGeneratedColumn('uuid')
  id!: string;

  @Column({ unique: true })
  email!: string;

  @Column({ name: 'password_hash' })
  passwordHash!: string;

  @Column({ type: 'varchar', length: 20, default: 'member' })
  role!: UserRole;
}

@Column({ name: 'password_hash' }) maps a camelCase property to a snake_case column — property and column names do not have to match.

Generated and timestamp columns

Definition

@PrimaryGeneratedColumn generates a primary key value automatically on insert, and @CreateDateColumn / @UpdateDateColumn automatically populate creation and last-modified timestamps without application code setting them.

In simpler words

These decorators exist so nobody has to remember to set id, created_at, or updated_at by hand on every insert or update.

@PrimaryGeneratedColumn('uuid') tells TypeORM (and the migration it generates) to default the column to a generated UUID, matching gen_random_uuid() in SQL.

@UpdateDateColumn refreshes its value on every save() — this is how Ticket.updatedAt stays accurate without manual bookkeeping.

Ticket entity timestamps

@Entity({ name: 'tickets' })
export class Ticket {
  @PrimaryGeneratedColumn('uuid')
  id!: string;

  @Column({ length: 200 })
  title!: string;

  @Column({ type: 'text', nullable: true })
  description!: string | null;

  @CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
  createdAt!: Date;

  @UpdateDateColumn({ name: 'updated_at', type: 'timestamptz' })
  updatedAt!: Date;
}

description is explicitly nullable — its TypeScript type says so too (string | null).

Column options are SQL constraints, spelled in TypeScript

Definition

Column decorator options (unique, nullable, default, length, type) directly control the constraints and type that TypeORM will generate in a migration for that column.

In simpler words

Every option you set here should have a reason traceable to a real business rule, the same discipline as choosing raw SQL types and constraints.

unique: true on email is exactly UNIQUE ("email") in SQL — TypeORM will name the constraint and generate it in a migration.

nullable: true is the TypeScript-side way of omitting NOT NULL for a column, as seen on Ticket.description and Ticket.assigneeId.

Nullable foreign key column

@Column({ name: 'assignee_id', type: 'uuid', nullable: true })
assigneeId!: string | null;

nullable: true here matches the SQL column having no NOT NULL — an unassigned ticket is valid.

Keep in mind

  • When in doubt about a column’s real SQL shape, compare the entity decorator to the migration SQL — they must agree.
  • Never hand-set a value that a generated or timestamp column owns.
  • A mismatched nullable/type between entity and actual column is a common source of confusing runtime errors.

Test

Check your understanding

At least 10 questions — mix of concept, syntax, practical, and logic. Score ≥ 80% (enforced by the API) to save progress.

Checking your session…

10 questions · concept 3 · syntax 2 · practical 3 · logic 2

Concept1. Which statement best defines Entities?
Syntax2. Which code-level choice matches Entities?
Practical3. A reviewer spots a bug related to Entities. What is the right fix?
Logic4. Which reasoning correctly explains Entities?
Concept5. Which boundary does correct use of Entities preserve?
Practical6. What is the safest next step when applying Entities?
Syntax7. Which implementation direction matches the rule for Entities?
Logic8. Which consequence follows from applying Entities correctly?
Concept9. Which claim about Entities is true in this Nest + Postgres monorepo?
Practical10. Which team practice best demonstrates Entities?