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:
- Create a Repository File: Create a new file for your repository, usually named after the entity it manages (e.g.,
user.repository.ts
). - Define the Repository Class: Extend the TypeORM
Repository
class. - 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 returnsundefined
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 returnsundefined
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.