Repositories
⭐ Interview Importance: MEDIUM
⏱️ Revision Time: 8 min
In the context of NestJS and TypeORM, Repositories are the auto-generated classes that provide standard CRUD (Create, Read, Update, Delete) operations for your Entities.
Overview
When you register an entity in a feature module using TypeOrmModule.forFeature([User]), NestJS automatically creates a Repository<User> provider.
You inject this repository into your services. It comes pre-loaded with dozens of useful methods to interact with the database, eliminating the need to write basic SQL queries.
Key Concepts
@InjectRepository(): The decorator used to inject the auto-generated repository into a Service constructor.findvsfindOne: Methods for querying the database.createvssavevsinsert: Different approaches to adding data to the database, each with specific side effects.
Code Examples
1. Querying Data (Read)
The find methods accept complex “Find Options” objects.
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, Like, MoreThan } from 'typeorm';
import { User } from './user.entity';
@Injectable()
export class UsersService {
constructor(
@InjectRepository(User) private userRepository: Repository<User>,
) {}
// Get a single record
async getUser(id: number): Promise<User> {
// findOneBy is a helper for querying by simple conditions
const user = await this.userRepository.findOneBy({ id });
if (!user) throw new NotFoundException();
return user;
}
// Complex querying
async searchUsers(): Promise<User[]> {
return this.userRepository.find({
where: {
isActive: true,
age: MoreThan(18), // Age > 18
name: Like('%John%'), // Name LIKE '%John%'
},
order: { createdAt: 'DESC' }, // ORDER BY createdAt DESC
take: 10, // LIMIT 10
skip: 0, // OFFSET 0
relations: ['profile', 'posts'] // Join these tables!
});
}
}
2. Mutating Data (Create, Update, Delete)
@Injectable()
export class UsersService {
constructor(@InjectRepository(User) private userRepository: Repository<User>) {}
// CREATE
async createUser(dto: any) {
// .create() makes an object in memory. It DOES NOT hit the database.
const newUser = this.userRepository.create(dto);
// .save() executes the INSERT query and triggers lifecycle hooks.
return await this.userRepository.save(newUser);
}
// UPDATE
async updateUser(id: number, dto: any) {
// .update() executes a raw UPDATE query. It is very fast, but does
// NOT trigger lifecycle hooks (like @BeforeUpdate).
await this.userRepository.update(id, dto);
return this.getUser(id); // Fetch the updated record
}
// DELETE
async deleteUser(id: number) {
// Executes a raw DELETE query.
await this.userRepository.delete(id);
// Alternatively, soft delete (sets deletedAt timestamp instead of removing row)
// await this.userRepository.softDelete(id);
}
}
Best Practices
- Understand
savevsupdate/insert:- Use
repository.save(entity)when you need to trigger TypeORM listeners (like hashing a password on save) or when you need to save cascading relationships. It is slower because it often does aSELECTto see if the record exists before doing anINSERTorUPDATE. - Use
repository.update()orrepository.insert()when you want raw performance. They execute direct SQL commands without checking if the entity exists in memory and without triggering hooks.
- Use