Khi phát triển Backend bằng NestJS và làm việc với các hệ quản trị cơ sở dữ liệu quan hệ (RDBMS) như PostgreSQL, MariaDB hay MS SQL, khái niệm Entity (Thực thể) đóng vai trò là cầu nối cốt lõi giữa mã nguồn ứng dụng và cơ sở dữ liệu vật lý. Tài liệu này giúp bạn thấu hiểu bản chất của Entity, cách thiết lập cấu trúc bảng, các loại mối quan hệ và cách tương tác hiệu quả với Claude Code để sinh thực thể chuẩn xác nhất.

Entity là gì?

Trong các thư viện ORM (Object-Relational Mapping) như TypeORM hay Prisma, một Entity là một lớp (Class) trong mã nguồn được ánh xạ (map) trực tiếp với một bảng (Table) trong cơ sở dữ liệu. Mỗi thực thể đại diện cho cấu trúc của bảng, các thuộc tính của lớp đại diện cho các cột (Columns), và mỗi đối tượng (instance) của lớp đại diện cho một bản ghi (Row).

Các thành phần cốt lõi của một Entity

Khi định nghĩa một Entity cho CSDL quan hệ như PostgreSQL, bạn cần xác định rõ các yếu tố sau:

1. Primary Key (Khóa chính)

Mỗi bảng trong CSDL quan hệ bắt buộc phải có một khóa chính để định danh duy nhất cho từng bản ghi.
  • ID tự tăng (Auto-increment Integer): Thường sử dụng số nguyên (1, 2, 3...). Ưu điểm là hiệu năng truy vấn cao, dung lượng lưu trữ nhỏ.
  • UUID (Universally Unique Identifier): Chuỗi ký tự ngẫu nhiên duy nhất trên toàn cầu (ví dụ: f81d4fae-7dec-11d0-a765-00a0c91e6bf6). Rất an toàn và phù hợp cho các hệ thống phân tán, tránh lộ ID thật của bản ghi ra URL.

2. Columns & Data Types (Cột & Kiểu dữ liệu)

Việc chọn kiểu dữ liệu phù hợp trong PostgreSQL giúp tối ưu hóa dung lượng đĩa và tốc độ truy vấn:
  • varchar(N) hoặc text: Dành cho chuỗi ký tự.
  • integer hoặc bigint: Dành cho số nguyên.
  • decimal(precision, scale) hoặc numeric: Dành cho tiền tệ, số có phần thập phân cần độ chính xác tuyệt đối.
  • boolean: Trạng thái Đúng/Sai.
  • timestamp hoặc timestamptz: Thời gian (có hoặc không có múi giờ - khuyến nghị sử dụng timestamptz cho PostgreSQL).

3. Constraints (Ràng buộc dữ liệu)

  • Nullable: Cột có được phép để trống (null) hay không.
  • Unique: Đảm bảo giá trị trong cột không được trùng lặp (ví dụ: email, username).
  • Default: Giá trị mặc định khi tạo mới bản ghi.

Định nghĩa Các Mối Quan hệ (Relationships)

Điểm mạnh của CSDL quan hệ là khả năng liên kết các bảng thông qua khóa ngoại (Foreign Key). TypeORM cung cấp các Decorator để mô tả điều này một cách trực quan:
Đây là mối quan hệ phổ biến nhất. Ví dụ: Một User có thể có nhiều Order (Một-Nhiều), nhưng mỗi Order chỉ thuộc về một User duy nhất (Nhiều-Một).
order.entity.ts
import { Entity, PrimaryGeneratedColumn, ManyToOne, JoinColumn } from 'typeorm';
import { User } from './user.entity';

@Entity('orders')
export class Order {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @ManyToOne(() => User, (user) => user.orders, { onDelete: 'CASCADE' })
  @JoinColumn({ name: 'user_id' }) // Xác định tên cột khóa ngoại vật lý
  user: User;
}

Hướng dẫn viết Prompt cho Claude Code thiết kế Entity chuẩn xác

Khi sử dụng Claude Code để phát triển Backend, việc viết prompt rõ ràng về cấu trúc cơ sở dữ liệu sẽ giúp AI sinh mã nguồn chính xác ngay từ lần đầu tiên.

Cấu trúc một Prompt chuẩn cho Entity:

  1. Tên thực thểTên bảng vật lý.
  2. Kiểu khóa chính (UUID hay Auto-increment).
  3. Danh sách các cột kèm kiểu dữ liệu, ràng buộc (unique, nullable, default).
  4. Mối quan hệ cụ thể với các thực thể khác (nếu có) và hành vi xóa khóa ngoại (onDelete: 'CASCADE').
  5. Thư viện sử dụng (ví dụ: TypeORM).
Hãy thiết kế cho tôi một Entity TypeORM có tên 'Product' ánh xạ vào bảng 'products' trong PostgreSQL với các yêu cầu:
1. Khóa chính là UUID tự sinh.
2. Các cột:
   - name: string, không được null, giới hạn 150 ký tự.
   - sku: string, unique, không được null.
   - price: decimal, độ chính xác 12 chữ số và 2 chữ số thập phân, không được null.
   - isAvailable: boolean, mặc định true.
   - createdAt: timestamp có múi giờ (timestamptz), tự sinh.
3. Mối quan hệ:
   - Nhiều sản phẩm thuộc về một 'Category' (Many-to-One), khóa ngoại đặt tên là 'category_id', hành vi onDelete là 'SET NULL'.
Luôn luôn chỉ định rõ hành vi onDelete (ví dụ: 'CASCADE', 'SET NULL') khi thiết kế các mối quan hệ để đảm bảo tính toàn vẹn dữ liệu (Referential Integrity) ở tầng cơ sở dữ liệu vật lý.

Phân biệt Entity và DTO (Request & Response)

Đối với những bạn mới bắt đầu học Backend, một lỗi rất phổ biến là sử dụng trực tiếp Entity để nhận dữ liệu gửi lên hoặc trả về cho Client. Đây là thói quen nguy hiểm, dễ gây lộ dữ liệu nhạy cảm (như mật khẩu đã mã hóa, khóa bí mật) hoặc cho phép người dùng tự ý ghi đè các cột hệ thống (như id, role, createdAt). Hãy phân biệt rõ ràng trách nhiệm của 3 thành phần này:

1. Request DTO (Dữ liệu yêu cầu đầu vào)

  • Nhiệm vụ: Định nghĩa cấu trúc và luật kiểm tra dữ liệu mà Client gửi lên (ví dụ: bắt buộc nhập email đúng định dạng, mật khẩu phải dài từ 6 ký tự).
  • Đặc điểm: Không chứa các trường hệ thống tự sinh như id, createdAt, updatedAt.
  • Ví dụ: Client gửi thông tin đăng ký chỉ gồm email, password, fullName.

2. Entity (Thực thể CSDL)

  • Nhiệm vụ: Định nghĩa cấu trúc bảng lưu trữ vật lý trong Database.
  • Đặc điểm: Chứa đầy đủ các trường (bao gồm cả mật khẩu đã băm, quyền hạn role, trạng thái isActive, thời gian tạo).
  • Ví dụ: Thực thể User chứa id (UUID), email, password (hashed), role, createdAt.

3. Response DTO / Serialization (Dữ liệu phản hồi đầu ra)

  • Nhiệm vụ: Định nghĩa cấu trúc dữ liệu an toàn trả về cho Client.
  • Đặc điểm: Tuyệt đối không chứa các trường nhạy cảm.
  • Ví dụ: Dữ liệu trả về chỉ gồm id, email, fullNamecreatedAt (đã giấu cột password đã mã hóa).
import { IsEmail, IsString, MinLength } from 'class-validator';

export class CreateUserDto {
  @IsEmail()
  email: string;

  @MinLength(6)
  password: string;

  @IsString()
  fullName: string;
}

Cách yêu cầu Claude Code triển khai bảo mật DTO:

Khi ra lệnh cho Claude Code viết API, hãy nhắc AI lọc bỏ thông tin nhạy cảm ở đầu ra:
Hãy viết hàm 'register' trong 'UsersService'.
Yêu cầu: Sử dụng thực thể 'User' để lưu trữ đầy đủ thông tin (gồm cả mật khẩu đã băm). 
Tuy nhiên, khi trả về kết quả cho Client, hãy chuyển đổi kết quả sang 'UserResponseDto' 
hoặc sử dụng class-transformer để ẩn trường 'password' đi để bảo mật.