NestJS Repositories

A repository is the layer that communicates directly with the database. In TypeORM, every entity gets a repository automatically. The repository provides methods to find, save, update, and delete records without writing SQL. Your service injects the repository and calls its methods to perform all database operations.

Injecting a Repository

Use @InjectRepository(Entity) to inject a repository into a service:

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>,
  ) {}
}

TypeORM provides the Repository<User> instance automatically. You call its methods inside your service methods.

Common Repository Methods

Finding Records

// Find all users
const users = await this.userRepository.find();

// Find with a condition
const admins = await this.userRepository.find({
  where: { role: 'admin' }
});

// Find one by condition
const user = await this.userRepository.findOne({
  where: { id: 1 }
});

// Find one or throw NotFoundException automatically
const user = await this.userRepository.findOneOrFail({
  where: { id: 1 }
});

Creating Records

// Step 1: Create the entity instance
const newUser = this.userRepository.create({
  name: 'Alice',
  email: 'alice@example.com',
  age: 30,
});

// Step 2: Save it to the database
const savedUser = await this.userRepository.save(newUser);
// savedUser now has the auto-generated id

Updating Records

// Approach 1: Save a modified entity
const user = await this.userRepository.findOne({ where: { id: 1 } });
user.name = 'Alice Updated';
await this.userRepository.save(user);

// Approach 2: Update by ID directly
await this.userRepository.update(1, { name: 'Alice Updated' });

Deleting Records

// Hard delete by ID
await this.userRepository.delete(1);

// Soft delete (sets deletedAt timestamp)
await this.userRepository.softDelete(1);

// Remove an entity instance
const user = await this.userRepository.findOne({ where: { id: 1 } });
await this.userRepository.remove(user);

A Complete Service Example

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

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

  async findOne(id: number): Promise<User> {
    const user = await this.userRepository.findOne({ where: { id } });
    if (!user) throw new NotFoundException(`User #${id} not found`);
    return user;
  }

  create(dto: CreateUserDto): Promise<User> {
    const user = this.userRepository.create(dto);
    return this.userRepository.save(user);
  }

  async update(id: number, dto: UpdateUserDto): Promise<User> {
    const user = await this.findOne(id);
    Object.assign(user, dto);
    return this.userRepository.save(user);
  }

  async remove(id: number): Promise<void> {
    const user = await this.findOne(id);
    await this.userRepository.remove(user);
  }
}

Repository Flow Diagram

Controller          Service              Repository         Database
    |                  |                     |                  |
GET /users/1           |                     |                  |
    |──────────────→   |                     |                  |
    |            findOne(1)                  |                  |
    |                  |──────────────────→  |                  |
    |                  |    findOne({id:1})  |                  |
    |                  |                     |──────────────→   |
    |                  |                     | SELECT * FROM    |
    |                  |                     | users WHERE id=1 |
    |                  |                     |   ←──────────────|
    |                  |  ←──────────────────|  { id:1, ...}    |
    |   ←──────────────|                     |                  |
  response             |                     |                  |

Advanced Finding with Relations

When an entity has relations (covered in the next topic), you load related data using the relations option:

const user = await this.userRepository.findOne({
  where: { id: 1 },
  relations: ['orders', 'orders.items'],  // load user's orders and their items
});

The QueryBuilder

For complex queries that go beyond simple find options, TypeORM's QueryBuilder generates SQL programmatically:

const users = await this.userRepository
  .createQueryBuilder('user')
  .where('user.age > :minAge', { minAge: 18 })
  .andWhere('user.role = :role', { role: 'admin' })
  .orderBy('user.name', 'ASC')
  .limit(10)
  .getMany();

Custom Repositories

When your service needs database methods that the default repository does not provide, extend the repository in a custom class:

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

  findActiveAdmins(): Promise<User[]> {
    return this.repo.find({
      where: { role: 'admin', isActive: true },
      order: { createdAt: 'DESC' },
    });
  }
}

Custom repository methods keep complex query logic out of your service and give descriptive names to database operations that recur throughout your application.

Leave a Comment

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