Hướng dẫn sử dụng TypeORM với NestJS và PostgreSQL
Mục lục
- Giới thiệu
- Cài đặt và thiết lập
- Định nghĩa Entities
- Repositories và Services
- Ví dụ về Controller
- Ví dụ truy vấn
- Quan hệ trong TypeORM
- Giao dịch
- Di chuyển và nâng cấp
- Chỉ số và ràng buộc
- Mẹo nâng cao
- Thực tiễn tốt nhất
- Lỗi thường gặp
- Câu hỏi thường gặp
Giới thiệu
TypeORM là một ORM (Object-Relational Mapping) mạnh mẽ cho TypeScript và JavaScript, giúp bạn dễ dàng tương tác với cơ sở dữ liệu PostgreSQL trong các ứng dụng NestJS. Bài viết này sẽ hướng dẫn bạn từ cài đặt đến việc sử dụng TypeORM một cách hiệu quả.
Cài đặt và thiết lập
Để bắt đầu, trước tiên bạn cần cài đặt các gói cần thiết. Bạn có thể thực hiện lệnh sau:
bash
npm install --save @nestjs/typeorm typeorm pg
Sau khi cài đặt xong, bạn cần cấu hình TypeORM trong module chính của ứng dụng.
typescript
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './users/user.entity';
import { UsersModule } from './users/users.module';
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'postgres',
host: 'localhost',
port: 5432,
username: 'postgres',
password: 'yourpassword',
database: 'testdb',
entities: [User],
synchronize: true,
}),
UsersModule,
],
})
export class AppModule {}
Định nghĩa Entities
Entities là các lớp đại diện cho bảng trong cơ sở dữ liệu. Dưới đây là ví dụ về cách định nghĩa một entity cho người dùng và bài viết:
User Entity
typescript
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, OneToMany } from 'typeorm';
import { Post } from './post.entity';
@Entity()
export class User {
@PrimaryGeneratedColumn() id: number;
@Column({ unique: true }) email: string;
@Column() name: string;
@Column({ nullable: true }) age: number;
@CreateDateColumn() createdAt: Date;
@UpdateDateColumn() updatedAt: Date;
@OneToMany(() => Post, post => post.user)
posts: Post[];
}
Post Entity
typescript
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from 'typeorm';
import { User } from './user.entity';
@Entity()
export class Post {
@PrimaryGeneratedColumn() id: number;
@Column() title: string;
@Column({ type: 'text' }) content: string;
@ManyToOne(() => User, user => user.posts)
user: User;
}
Repositories và Services
Repositories cung cấp các phương thức để tương tác với cơ sở dữ liệu. Dưới đây là ví dụ về một service cho người dùng:
typescript
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 usersRepo: Repository<User>) {}
findAll() { return this.usersRepo.find({ relations: ['posts'] }); }
findOne(id: number) { return this.usersRepo.findOne({ where: { id }, relations: ['posts'] }); }
create(userData: Partial<User>) { const user = this.usersRepo.create(userData); return this.usersRepo.save(user); }
update(id: number, userData: Partial<User>) { return this.usersRepo.update(id, userData); }
remove(id: number) { return this.usersRepo.delete(id); }
}
Ví dụ về Controller
Controller nhận yêu cầu từ client và trả về phản hồi. Dưới đây là ví dụ về controller cho người dùng:
typescript
import { Controller, Get, Post, Body, Param, Put, Delete } from '@nestjs/common';
import { UsersService } from './users.service';
import { User } from './user.entity';
@Controller('users')
export class UsersController {
constructor(private usersService: UsersService) {}
@Get() getAll() { return this.usersService.findAll(); }
@Get(':id') getOne(@Param('id') id: number) { return this.usersService.findOne(id); }
@Post() create(@Body() userData: Partial<User>) { return this.usersService.create(userData); }
@Put(':id') update(@Param('id') id: number, @Body() userData: Partial<User>) { return this.usersService.update(id, userData); }
@Delete(':id') remove(@Param('id') id: number) { return this.usersService.remove(id); }
}
Ví dụ truy vấn
TypeORM hỗ trợ nhiều cách để thực hiện truy vấn. Dưới đây là một số ví dụ:
Sử dụng QueryBuilder
typescript
const users = await this.usersRepo.createQueryBuilder('user')
.leftJoinAndSelect('user.posts', 'post')
.where('user.age > :age', { age: 25 })
.orderBy('user.name', 'ASC')
.getMany();
Sử dụng SQL thuần
typescript
const result = await this.usersRepo.query('SELECT * FROM "user" WHERE age > $1', [25]);
Quan hệ trong TypeORM
TypeORM hỗ trợ nhiều loại quan hệ giữa các bảng:
- OneToOne: User → Profile
- OneToMany / ManyToOne: User → Posts
- ManyToMany: Posts ↔ Tags
Ví dụ về quan hệ ManyToMany:
typescript
@ManyToMany(() => Tag, tag => tag.posts)
@JoinTable()
tags: Tag[];
Giao dịch
Giao dịch giúp đảm bảo tính toàn vẹn dữ liệu khi thực hiện nhiều thao tác trong cơ sở dữ liệu. Dưới đây là ví dụ về cách sử dụng giao dịch trong TypeORM:
typescript
await this.dataSource.transaction(async (manager) => {
await manager.update(User, { id: 1 }, { age: 30 });
await manager.update(Post, { id: 5 }, { title: 'Updated' });
});
Di chuyển và nâng cấp
Để quản lý các thay đổi trong cấu trúc cơ sở dữ liệu, bạn có thể sử dụng migrations. Dưới đây là các lệnh để tạo và chạy migrations:
bash
npx typeorm migration:generate -n CreateUsers
npx typeorm migration:run
npx typeorm migration:revert
Chỉ số và ràng buộc
Chỉ số và ràng buộc giúp tối ưu hóa hiệu suất truy vấn. Ví dụ:
typescript
@Column({ unique: true }) email: string;
@Column({ nullable: true }) username: string;
@Index('idx_user_email')
@Column() email: string;
Mẹo nâng cao
- Sử dụng QueryBuilder cho các truy vấn phức tạp với joins, phân trang và lọc.
- Tránh vấn đề N+1 bằng cách sử dụng
leftJoinAndSelect. - Sử dụng giao dịch cho các thao tác nhiều bảng.
- Sử dụng migrations trong môi trường sản xuất, không sử dụng
synchronize: true. - Kết hợp với Redis caching cho các truy vấn nặng.
- Sử dụng DTOs + ValidationPipe để xác thực đầu vào.
Thực tiễn tốt nhất
- Luôn xác định rõ ràng các entity và quan hệ giữa chúng.
- Sử dụng các phương thức của repository một cách hiệu quả.
- Chú ý tới hiệu suất khi thực hiện các truy vấn lớn.
Lỗi thường gặp
- Lỗi kết nối tới cơ sở dữ liệu: Kiểm tra thông tin kết nối trong cấu hình.
- Lỗi không tìm thấy entity: Đảm bảo entity đã được khai báo trong module.
Câu hỏi thường gặp
TypeORM là gì?
TypeORM là một ORM cho TypeScript và JavaScript, giúp tương tác với cơ sở dữ liệu dễ dàng hơn.
Làm thế nào để sử dụng TypeORM với NestJS?
Cài đặt gói TypeORM, thiết lập cấu hình trong module và định nghĩa entities.
Có cần sử dụng migrations không?
Có, migrations giúp quản lý các thay đổi trong cơ sở dữ liệu một cách an toàn và hiệu quả.