Database Integration with TypeORM

Connecting to a database (e.g., PostgreSQL, MySQL) using TypeORM. Defining entities, repositories, and performing CRUD operations.


Repositories in NestJS with TypeORM

Introduction

This document explains how to create and implement repositories in a NestJS application using TypeORM to interact with entities. We'll cover creating repositories, using TypeORM's Repository API, and performing queries and data manipulation operations.

Creating Repositories

In NestJS, repositories are typically used as an abstraction layer between your services and the database. They encapsulate the logic for data access and manipulation, making your services cleaner and easier to test. Here's how to create a repository:

  1. Create a Repository File: Create a new file for your repository, usually named after the entity it manages (e.g., user.repository.ts).
  2. Define the Repository Class: Extend the TypeORM Repository class.
  3. Inject the Repository: Inject the repository into your service using NestJS's dependency injection.

Example: Creating a User Repository

Assume we have a User entity. Let's create a repository for it.

// src/user/user.repository.ts

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';

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

  // Custom repository methods will go here
}

Implementing Repositories for Interacting with Entities

Now that we have a basic repository, let's implement methods to interact with the User entity. This involves using TypeORM's Repository API.

Example: Repository Methods

// src/user/user.repository.ts (continued)

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';

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

  async createUser(userData: Partial<User>): Promise<User> {
    const user = this.userRepository.create(userData);
    return this.userRepository.save(user);
  }

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

  async findByEmail(email: string): Promise<User | undefined> {
    return this.userRepository.findOne({ where: { email } });
  }

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

  async update(id: number, userData: Partial<User>): Promise<User | undefined> {
    await this.userRepository.update(id, userData);
    return this.findOne(id); // Return the updated user
}

async delete(id: number): Promise<void> {
  await this.userRepository.delete(id);
}
}

Using TypeORM's Repository API to Perform Queries and Data Manipulation Operations

TypeORM provides a rich API for interacting with the database through its Repository class. Here's a breakdown of some common operations:

  • create(entityLike: Partial<T>): T: Creates a new entity instance but does not save it to the database. entityLike is an object containing properties to initialize the entity with.
  • save(entity: T): Promise<T>: Saves a single entity or an array of entities to the database. It performs either an insert or an update depending on whether the entity already exists.
  • find(options?: FindManyOptions<T>): Promise<T[]>: Retrieves multiple entities. options allow for filtering, pagination, and sorting.
  • findOne(options?: FindOneOptions<T>): Promise<T | undefined>: Retrieves a single entity. options allow for filtering based on specific criteria, including relations. It returns undefined if the entity is not found.
  • findOneBy(where: FindOptionsWhere<T>): Promise<T | undefined>: Retrieves a single entity. where allow for filtering based on specific criteria, including relations. It returns undefined if the entity is not found. This is a simplified version of `findOne`.
  • update(id: number | string | Date | ObjectId, partialEntity: QueryDeepPartialEntity<T>): Promise<UpdateResult>: Updates entities that match a given criteria. id specifies the ID of the entity to update. partialEntity is an object containing the fields to update.
  • delete(id: number | string | Date | ObjectId): Promise<DeleteResult>: Deletes entities that match a given criteria. id specifies the ID of the entity to delete.
  • createQueryBuilder(alias?: string, queryRunner?: QueryRunner): SelectQueryBuilder<T>: Provides a more flexible way to build complex queries using a fluent API. Useful for joins, subqueries, and other advanced operations.

Example: Querying with Options

// src/user/user.repository.ts (continued)

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';

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

  async findActiveUsers(limit: number): Promise<User[]> {
    return this.userRepository.find({
    where: {
      isActive: true,
    },
    take: limit, // Limit the number of results
    order: {
      createdAt: 'DESC', // Order by creation date in descending order
    },
  });
}

async findUsersWithProfile(profileId:number): Promise<User[]> {
   return this.userRepository.find({
     relations: ['profile'], // Eagerly load the profile relation
     where: {
       profile: {
          id: profileId
       }
     }
   })
}

async countAllUsers(): Promise<number> {
  return this.userRepository.count()
}
}

Using the Repository in a Service

To use the repository in your service, inject it using NestJS's dependency injection system.

// src/user/user.service.ts

import { Injectable } from '@nestjs/common';
import { UserRepository } from './user.repository';
import { User } from './user.entity';

@Injectable()
export class UserService {
  constructor(private readonly userRepository: UserRepository) {}

  async createUser(userData: Partial<User>): Promise<User> {
    return this.userRepository.createUser(userData);
  }

  async getUserById(id: number): Promise<User | undefined> {
    return this.userRepository.findOne(id);
  }

  // Other service methods using the repository
}

Benefits of Using Repositories

  • Abstraction: Hides the data access logic from the service layer.
  • Testability: Makes it easier to mock and test your services.
  • Maintainability: Centralizes data access logic, making it easier to update and maintain.
  • Reusability: Repository methods can be reused across multiple services.