Lesson 22 of 5022 min read

NestJS TypeORM Entities and Repository Pattern Explained

Learn how to define TypeORM entities in NestJS and use the repository pattern to query and persist data through injectable, type-safe repositories.

Author: CodersNexus

NestJS TypeORM Entities and Repository Pattern Explained

With a database connection established, the next step is defining what your data actually looks like and how your application will interact with it. TypeORM uses two core concepts for this: entities, which describe the shape of your data as TypeScript classes, and repositories, which provide the actual methods for querying and persisting that data.

This lesson explains both concepts in depth, showing you how to define a properly decorated entity and how to inject and use its corresponding repository inside a NestJS service, following the repository pattern that underlies almost every TypeORM-based NestJS application.

NestJS TypeORM Entities Repository Pattern: Learning Objectives

  • Define a TypeORM entity using the @Entity() and @Column() decorators.
  • Understand the role of a primary key and how @PrimaryGeneratedColumn() works.
  • Explain the repository pattern and why it centralizes data access logic.
  • Inject a TypeORM repository into a NestJS service using @InjectRepository().
  • Perform basic create, find, and save operations using a repository.

NestJS TypeORM Entities Repository Pattern: Key Terms and Definitions

  • Entity: A TypeScript class decorated with @Entity() that maps directly to a database table, with each property representing a column.
  • @Column() decorator: Marks a class property as a database column, optionally specifying its type and constraints.
  • @PrimaryGeneratedColumn() decorator: Marks a class property as an auto-incrementing primary key column.
  • Repository pattern: A design pattern that centralizes all data access logic for a given entity behind a single, consistent interface.
  • @InjectRepository() decorator: A NestJS decorator that injects a TypeORM repository for a specific entity into a service's constructor.

How NestJS TypeORM Entities Repository Pattern Works: Detailed Explanation

An entity in TypeORM is simply a TypeScript class decorated with @Entity(), where each property represents a column in the corresponding database table. The @Column() decorator marks a regular property as a column, optionally accepting configuration like a specific SQL type, a default value, or whether the column allows null values. Every entity typically needs exactly one primary key column, most commonly defined using @PrimaryGeneratedColumn(), which creates an auto-incrementing integer ID that MySQL manages automatically for every new row.

Once an entity is defined, TypeORM needs to know about it, which happens either through the entities array in your TypeOrmModule configuration (as shown in the previous lesson) or, more commonly for feature-specific entities, by registering it with TypeOrmModule.forFeature([YourEntity]) inside the relevant feature module. This registration is what makes a repository for that specific entity available for dependency injection within that module.

The repository pattern is a design approach that centralizes all database interaction logic for a given entity behind a single, well-defined interface, rather than scattering raw queries throughout your application. TypeORM automatically provides a fully-featured generic Repository class for every registered entity, offering methods like find(), findOne(), save(), update(), and delete(), all fully typed against your entity's shape.

To actually use this repository inside a NestJS service, you inject it using the @InjectRepository(YourEntity) decorator on a constructor parameter typed as Repository<YourEntity>. NestJS's dependency injection system, combined with TypeORM's integration, handles supplying the correctly configured repository instance automatically, exactly like injecting any other provider. From that point on, your service can call repository methods like this.userRepository.find() to retrieve all users, this.userRepository.findOne({ where: { id } }) to find a specific user, or this.userRepository.save(newUser) to persist a new or updated record, all without writing a single line of raw SQL, while still benefiting from full TypeScript type checking on every field involved.

Interview-Friendly Explanation

A strong interview or viva answer for nestjs typeorm entities repository pattern must go beyond a one-line definition. Interviewers evaluating BCA, MCA, B.Tech, and self-taught backend developers reward answers that combine a precise definition, a clear working explanation, a real-world example, and a conclusion that highlights why it matters in production Node.js applications. Use the four-point framework below when answering under time pressure.

  • Definition point: Entity: A TypeScript class decorated with @Entity() that maps directly to a database table, with each property representing a column.
  • Working point: Understand the role of a primary key and how @PrimaryGeneratedColumn() works.
  • Example point: Every TypeORM-based NestJS application organizes its data access exactly this way: one entity file per table, registered via forFeature() in a feature module, and consumed through an injected repository in a corresponding service.
  • Conclusion point: Entities give you a type-safe, class-based way to describe your database schema directly in TypeScript.

How to Answer This in a Technical Interview

  1. Give a two-to-three sentence definition using correct database and ORM terminology.
  2. Add one specific example drawn from a real backend scenario such as an e-commerce, fintech, or SaaS database schema.
  3. Mention any trade-offs versus alternative approaches (e.g. TypeORM vs Prisma, SQL vs NoSQL) since interviewers often probe this contrast.
  4. Close with one benefit, limitation, or production use case.
  5. Avoid vague answers like "it just connects to the database" — interviewers filter these out immediately.

Practical Scenario

Imagine you are designing the data layer for a production SaaS product, similar to systems used at companies like Razorpay, Freshworks, or Zomato. Every lesson in this module maps directly to a decision you will make while building that backend: how data is structured, how it's queried efficiently, how relationships between entities are modeled, and how the schema evolves safely over time as the product grows.

NestJS TypeORM Entities Repository Pattern: Architecture and Flow Diagram

Visualize the relationship between entity, repository, and service:

[User Entity (@Entity, @Column, @PrimaryGeneratedColumn)] --> registered via TypeOrmModule.forFeature([User])
|
v
[Repository<User>] --injected via @InjectRepository(User)--> [UsersService]
|
v
[UsersService calls repository.find(), .findOne(), .save(), .delete()] --> [Generates and executes SQL against MySQL]

NestJS TypeORM Entities Repository Pattern: Concept Reference Table

ConceptDecorator/ClassPurpose
Entity@Entity()Marks a class as mapping directly to a database table
Column@Column()Marks a property as a table column, with optional type and constraints
Primary key@PrimaryGeneratedColumn()Defines an auto-incrementing primary key column
Repository access@InjectRepository(Entity)Injects a fully-typed repository for a specific entity into a service

NestJS TypeORM Entities Repository Pattern: NestJS Code Example

// src/users/user.entity.ts
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn } from 'typeorm';

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

  @Column({ length: 100 })
  name: string;

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

  @Column({ default: true })
  isActive: boolean;

  @CreateDateColumn()
  createdAt: Date;
}

// src/users/users.module.ts — registering the entity with the module
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './user.entity';
import { UsersService } from './users.service';

@Module({
  imports: [TypeOrmModule.forFeature([User])],
  providers: [UsersService],
  exports: [UsersService],
})
export class UsersModule {}

// src/users/users.service.ts — using the repository pattern
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';

@Injectable()
export class UsersService {
  constructor(
    @InjectRepository(User)
    private readonly userRepository: Repository<User>,
  ) {}

  findAll(): Promise<User[]> {
    return this.userRepository.find();
  }

  findOne(id: number): Promise<User | null> {
    return this.userRepository.findOne({ where: { id } });
  }

  create(name: string, email: string): Promise<User> {
    const newUser = this.userRepository.create({ name, email });
    return this.userRepository.save(newUser);
  }
}

The User entity defines four columns beyond the primary key: name, email (marked unique), isActive with a default value, and createdAt automatically populated by @CreateDateColumn(). UsersModule registers this entity using TypeOrmModule.forFeature([User]), which is what makes Repository<User> available for injection specifically within this module. UsersService then injects that repository using @InjectRepository(User) and uses it to implement findAll, findOne, and create, calling built-in repository methods like find(), findOne(), create() (which builds a new entity instance without saving it), and save() (which actually persists it to the database), all fully typed against the User entity's shape.

Real-World NestJS TypeORM Entities Repository Pattern Industry Examples

  • Every TypeORM-based NestJS application organizes its data access exactly this way: one entity file per table, registered via forFeature() in a feature module, and consumed through an injected repository in a corresponding service.
  • Audit and logging systems commonly use TypeORM's @CreateDateColumn() and @UpdateDateColumn() decorators, shown in this lesson, to automatically track when records were created or last modified without manual timestamp handling.
  • E-commerce platforms define separate entities for Product, Order, and Customer, each with its own dedicated repository, keeping data access logic for each domain cleanly separated.
  • Enterprise applications often wrap repository calls inside a service layer (as shown here) specifically so that business logic and validation can be layered on top of raw data access, rather than exposing repositories directly to controllers.

NestJS TypeORM Entities Repository Pattern Interview Questions and Answers

Q1. What is a TypeORM entity and how does it relate to a database table?

Short answer: A TypeORM entity is a TypeScript class decorated with @Entity() that directly maps to a database table, where each class property decorated with @Column() (or similar decorators) represents a column in that table, allowing developers to work with database rows as ordinary, type-safe JavaScript objects.

Detailed explanation: An entity in TypeORM is simply a TypeScript class decorated with @Entity(), where each property represents a column in the corresponding database table. The @Column() decorator marks a regular property as a column, optionally accepting configuration like a specific SQL type, a default value, or whether the column allows null values. Every entity typically needs exactly one primary key column, most commonly defined using @PrimaryGeneratedColumn(), which creates an auto-incrementing integer ID that MySQL manages automatically for every new row. Once an entity is defined, TypeORM needs to know about it, which happens either through the entities array in your TypeOrmModule configuration (as shown in the previous lesson) or, more commonly for feature-specific entities, by registering it with TypeOrmModule.forFeature([YourEntity]) inside the relevant feature module. This registration is what makes a repository for that specific entity available for dependency injection within that module. The repository pattern is a design approach that centralizes all database interaction logic for a given entity behind a single, well-defined interface, rather than scattering raw queries throughout your application. TypeORM automatically provides a fully-featured generic Repository class for every registered entity, offering methods like find(), findOne(), save(), update(), and delete(), all fully typed against your entity's shape. To actually use this repository inside a NestJS service, you inject it using the @InjectRepository(YourEntity) decorator on a constructor parameter typed as Repository<YourEntity>. NestJS's dependency injection system, combined with TypeORM's integration, handles supplying the correctly configured repository instance automatically, exactly like injecting any other provider. From that point on, your service can call repository methods like this.userRepository.find() to retrieve all users, this.userRepository.findOne({ where: { id } }) to find a specific user, or this.userRepository.save(newUser) to persist a new or updated record, all without writing a single line of raw SQL, while still benefiting from full TypeScript type checking on every field involved.

Practical example: Every TypeORM-based NestJS application organizes its data access exactly this way: one entity file per table, registered via forFeature() in a feature module, and consumed through an injected repository in a corresponding service.

Interview tip: @Entity() marks a class as mapping to a database table; @Column() marks individual properties as columns.

Revision hook: Entities give you a type-safe, class-based way to describe your database schema directly in TypeScript.

Q2. What is the repository pattern and why does TypeORM use it?

Short answer: The repository pattern centralizes all data access logic for a given entity behind a single, consistent interface, rather than scattering raw database queries throughout an application. TypeORM automatically generates a fully-typed Repository class for every registered entity, providing common methods like find(), findOne(), save(), and delete(), which promotes cleaner, more maintainable, and more testable data access code.

Detailed explanation: The User entity defines four columns beyond the primary key: name, email (marked unique), isActive with a default value, and createdAt automatically populated by @CreateDateColumn(). UsersModule registers this entity using TypeOrmModule.forFeature([User]), which is what makes Repository<User> available for injection specifically within this module. UsersService then injects that repository using @InjectRepository(User) and uses it to implement findAll, findOne, and create, calling built-in repository methods like find(), findOne(), create() (which builds a new entity instance without saving it), and save() (which actually persists it to the database), all fully typed against the User entity's shape.

Practical example: Audit and logging systems commonly use TypeORM's @CreateDateColumn() and @UpdateDateColumn() decorators, shown in this lesson, to automatically track when records were created or last modified without manual timestamp handling.

Interview tip: @PrimaryGeneratedColumn() is the standard way to define an auto-incrementing primary key.

Revision hook: The repository pattern keeps data access logic centralized, consistent, and easy to test across an entire application.

Q3. How do you inject a TypeORM repository into a NestJS service?

Short answer: You register the entity with the module using TypeOrmModule.forFeature([EntityName]), then in the service's constructor, declare a parameter decorated with @InjectRepository(EntityName), typed as Repository<EntityName>, which NestJS's dependency injection system automatically resolves and supplies at runtime.

Detailed explanation: TypeORM entities are TypeScript classes decorated with @Entity(), where properties marked with @Column() and a primary key defined via @PrimaryGeneratedColumn() together describe a database table's structure in a fully type-safe way. Once an entity is registered with a module using TypeOrmModule.forFeature([Entity]), NestJS can inject a fully-featured Repository instance for that entity into a service using the @InjectRepository() decorator. This repository pattern centralizes all data access logic behind consistent methods like find(), findOne(), create(), and save(), letting services interact with the database without writing raw SQL, while keeping data access logic cleanly separated from HTTP-handling controllers.

Practical example: E-commerce platforms define separate entities for Product, Order, and Customer, each with its own dedicated repository, keeping data access logic for each domain cleanly separated.

Interview tip: TypeOrmModule.forFeature([Entity]) registers an entity with a module, enabling repository injection.

Revision hook: forFeature() and @InjectRepository() work together to make TypeORM's dependency injection feel like a natural part of NestJS.

Q4. What is the difference between the repository's create() and save() methods?

Short answer: create() builds a new entity instance in memory, populating its properties from the provided data, but does not persist anything to the database. save() takes an entity instance, whether newly created or an existing one with changes, and actually writes it to the database, either as an insert for a new record or an update for an existing one.

Detailed explanation: An entity in TypeORM is simply a TypeScript class decorated with @Entity(), where each property represents a column in the corresponding database table. The @Column() decorator marks a regular property as a column, optionally accepting configuration like a specific SQL type, a default value, or whether the column allows null values. Every entity typically needs exactly one primary key column, most commonly defined using @PrimaryGeneratedColumn(), which creates an auto-incrementing integer ID that MySQL manages automatically for every new row. Once an entity is defined, TypeORM needs to know about it, which happens either through the entities array in your TypeOrmModule configuration (as shown in the previous lesson) or, more commonly for feature-specific entities, by registering it with TypeOrmModule.forFeature([YourEntity]) inside the relevant feature module. This registration is what makes a repository for that specific entity available for dependency injection within that module. The repository pattern is a design approach that centralizes all database interaction logic for a given entity behind a single, well-defined interface, rather than scattering raw queries throughout your application. TypeORM automatically provides a fully-featured generic Repository class for every registered entity, offering methods like find(), findOne(), save(), update(), and delete(), all fully typed against your entity's shape. To actually use this repository inside a NestJS service, you inject it using the @InjectRepository(YourEntity) decorator on a constructor parameter typed as Repository<YourEntity>. NestJS's dependency injection system, combined with TypeORM's integration, handles supplying the correctly configured repository instance automatically, exactly like injecting any other provider. From that point on, your service can call repository methods like this.userRepository.find() to retrieve all users, this.userRepository.findOne({ where: { id } }) to find a specific user, or this.userRepository.save(newUser) to persist a new or updated record, all without writing a single line of raw SQL, while still benefiting from full TypeScript type checking on every field involved.

Practical example: Enterprise applications often wrap repository calls inside a service layer (as shown here) specifically so that business logic and validation can be layered on top of raw data access, rather than exposing repositories directly to controllers.

Interview tip: @InjectRepository(Entity) injects a Repository<Entity> instance into a service's constructor.

Revision hook: Keeping repository usage inside services, rather than controllers, preserves NestJS's clean separation between HTTP handling and business/data logic.

NestJS TypeORM Entities Repository Pattern MCQs and Practice Questions

1. Which decorator marks a TypeScript class as a TypeORM entity mapping to a database table?

  1. @Injectable()
  2. @Entity()
  3. @Controller()
  4. @Repository()

Answer: B. @Entity()

Explanation: @Entity() is the decorator that marks a class as a TypeORM entity, establishing its mapping to a corresponding database table.

Concept link: @Entity() marks a class as mapping to a database table; @Column() marks individual properties as columns.

Why this matters: Entities give you a type-safe, class-based way to describe your database schema directly in TypeScript.

2. Which decorator is commonly used to define an auto-incrementing primary key column?

  1. @Column()
  2. @PrimaryGeneratedColumn()
  3. @CreateDateColumn()
  4. @Index()

Answer: B. @PrimaryGeneratedColumn()

Explanation: @PrimaryGeneratedColumn() defines a column as an auto-incrementing primary key, which the database automatically manages for every new row inserted.

Concept link: @PrimaryGeneratedColumn() is the standard way to define an auto-incrementing primary key.

Why this matters: The repository pattern keeps data access logic centralized, consistent, and easy to test across an entire application.

3. What must be done before a repository for an entity can be injected into a service?

  1. The entity must be registered using TypeOrmModule.forFeature([Entity]) in the relevant module
  2. The entity must be marked @Injectable()
  3. The entity must extend a Repository class directly
  4. Nothing, all repositories are available automatically

Answer: A. The entity must be registered using TypeOrmModule.forFeature([Entity]) in the relevant module

Explanation: TypeOrmModule.forFeature() registers an entity with a specific module, which is what makes a corresponding Repository instance available for dependency injection within that module's providers.

Concept link: TypeOrmModule.forFeature([Entity]) registers an entity with a module, enabling repository injection.

Why this matters: forFeature() and @InjectRepository() work together to make TypeORM's dependency injection feel like a natural part of NestJS.

4. What does the repository's create() method do, as opposed to save()?

  1. It immediately persists a new record to the database
  2. It builds a new entity instance in memory without saving it to the database
  3. It deletes an existing record
  4. It creates a new database table

Answer: B. It builds a new entity instance in memory without saving it to the database

Explanation: create() only constructs and populates a new entity instance in memory; the separate save() method is required to actually persist that instance (or any entity instance) to the database.

Concept link: @InjectRepository(Entity) injects a Repository<Entity> instance into a service's constructor.

Why this matters: Keeping repository usage inside services, rather than controllers, preserves NestJS's clean separation between HTTP handling and business/data logic.

Common NestJS TypeORM Entities Repository Pattern Mistakes to Avoid

  • Forgetting to register an entity with TypeOrmModule.forFeature() in its module, causing a 'repository not found' dependency injection error when trying to inject it into a service.
  • Calling repository.create() and expecting the record to already exist in the database, forgetting that save() is still required to actually persist it.
  • Defining multiple primary key columns on a single entity, or forgetting a primary key entirely, both of which cause TypeORM errors.
  • Bypassing the service layer and injecting repositories directly into controllers, mixing HTTP concerns with data access logic and losing a clean separation of responsibilities.

NestJS TypeORM Entities Repository Pattern: Interview Notes and Exam Tips

  • @Entity() marks a class as mapping to a database table; @Column() marks individual properties as columns.
  • @PrimaryGeneratedColumn() is the standard way to define an auto-incrementing primary key.
  • TypeOrmModule.forFeature([Entity]) registers an entity with a module, enabling repository injection.
  • @InjectRepository(Entity) injects a Repository<Entity> instance into a service's constructor.

Key NestJS TypeORM Entities Repository Pattern Takeaways

  • Entities give you a type-safe, class-based way to describe your database schema directly in TypeScript.
  • The repository pattern keeps data access logic centralized, consistent, and easy to test across an entire application.
  • forFeature() and @InjectRepository() work together to make TypeORM's dependency injection feel like a natural part of NestJS.
  • Keeping repository usage inside services, rather than controllers, preserves NestJS's clean separation between HTTP handling and business/data logic.

NestJS TypeORM Entities Repository Pattern: Summary

TypeORM entities are TypeScript classes decorated with @Entity(), where properties marked with @Column() and a primary key defined via @PrimaryGeneratedColumn() together describe a database table's structure in a fully type-safe way. Once an entity is registered with a module using TypeOrmModule.forFeature([Entity]), NestJS can inject a fully-featured Repository instance for that entity into a service using the @InjectRepository() decorator. This repository pattern centralizes all data access logic behind consistent methods like find(), findOne(), create(), and save(), letting services interact with the database without writing raw SQL, while keeping data access logic cleanly separated from HTTP-handling controllers.

Frequently Asked Questions

Not necessarily during development, if synchronize is enabled TypeORM will automatically create or update tables to match your entities. In production, where synchronize should be disabled, you would instead create a migration to generate the corresponding table structure. In interviews, tie this back to: @Entity() marks a class as mapping to a database table; @Column() marks individual properties as columns. In real applications, consider this example: Every TypeORM-based NestJS application organizes its data access exactly this way: one entity file per table, registered via forFeature() in a feature module, and consumed through an injected repository in a corresponding service. Key revision takeaway: Entities give you a type-safe, class-based way to describe your database schema directly in TypeScript.

Yes, TypeOrmModule.forFeature() accepts an array of entities, so a module can register and provide repositories for multiple related entities at once, such as forFeature([User, Profile]) if both entities are closely related to that module's feature area. In interviews, tie this back to: @PrimaryGeneratedColumn() is the standard way to define an auto-incrementing primary key. In real applications, consider this example: Audit and logging systems commonly use TypeORM's @CreateDateColumn() and @UpdateDateColumn() decorators, shown in this lesson, to automatically track when records were created or last modified without manual timestamp handling. Key revision takeaway: The repository pattern keeps data access logic centralized, consistent, and easy to test across an entire application.

find() returns an array of all matching records (or all records if no conditions are provided), while findOne() returns a single record matching the given conditions, or null if no match is found, making it appropriate for looking up one specific entity, such as by its ID. In interviews, tie this back to: TypeOrmModule.forFeature([Entity]) registers an entity with a module, enabling repository injection. In real applications, consider this example: E-commerce platforms define separate entities for Product, Order, and Customer, each with its own dedicated repository, keeping data access logic for each domain cleanly separated. Key revision takeaway: forFeature() and @InjectRepository() work together to make TypeORM's dependency injection feel like a natural part of NestJS.

It is strongly discouraged. Keeping repository injection and usage within services preserves NestJS's architectural convention of separating HTTP-handling concerns (controllers) from business and data-access logic (services), making the codebase easier to test and maintain. In interviews, tie this back to: @InjectRepository(Entity) injects a Repository<Entity> instance into a service's constructor. In real applications, consider this example: Enterprise applications often wrap repository calls inside a service layer (as shown here) specifically so that business logic and validation can be layered on top of raw data access, rather than exposing repositories directly to controllers. Key revision takeaway: Keeping repository usage inside services, rather than controllers, preserves NestJS's clean separation between HTTP handling and business/data logic.

TypeORM will throw an error originating from the underlying database driver, typically indicating a duplicate key or unique constraint violation, which your service or a global exception filter should catch and translate into an appropriate, user-friendly error response, such as a ConflictException. In interviews, tie this back to: @Entity() marks a class as mapping to a database table; @Column() marks individual properties as columns. In real applications, consider this example: Every TypeORM-based NestJS application organizes its data access exactly this way: one entity file per table, registered via forFeature() in a feature module, and consumed through an injected repository in a corresponding service. Key revision takeaway: Entities give you a type-safe, class-based way to describe your database schema directly in TypeScript.

Yes, a class can include regular TypeScript properties or methods that are not decorated with @Column(), and TypeORM will simply ignore them for database mapping purposes, treating them as ordinary class members useful for in-memory logic rather than persisted data. In interviews, tie this back to: @PrimaryGeneratedColumn() is the standard way to define an auto-incrementing primary key. In real applications, consider this example: Audit and logging systems commonly use TypeORM's @CreateDateColumn() and @UpdateDateColumn() decorators, shown in this lesson, to automatically track when records were created or last modified without manual timestamp handling. Key revision takeaway: The repository pattern keeps data access logic centralized, consistent, and easy to test across an entire application.