Lesson 1: Mở đầu & Data Source
📋 Agenda
Thời gian đọc ước tính: ~20 phút
Sau bài này, bạn sẽ:
- ✅ Hiểu TypeORM là gì và tại sao nó ra đời
- ✅ Giải thích đ ược lợi ích của ORM so với raw SQL
- ✅ Cấu hình được DataSource với Multiple connections và Replication
- ✅ Phân biệt cách xử lý
nullvàundefinedtrong điều kiện WHERE
Yêu cầu đầu vào (Prerequisites):
- 🔹 Biết cơ bản TypeScript (interface, decorator, generic)
- 🔹 Đã từng sử dụng một cơ sở dữ liệu quan hệ (PostgreSQL, MySQL)
- 🔹 Đã cài đặt Node.js và npm
❓ Vấn đề & Giải pháp
Vấn đề (Problem Statement):
- Viết raw SQL thuần túy dễ gây lỗi typo, không có type-check từ TypeScript
- Khi đổi database (MySQL → PostgreSQL), phải viết lại nhiều câu query
- Không có cơ chế quản lý schema tự động khi model thay đổi (thiếu Migrations)
- Code SQL lẫn lộn với business logic, khó đọc và khó test
Giải pháp (Solution): TypeORM là một ORM (Object-Relational Mapper) dành cho TypeScript/JavaScript. Nó giúp map các class TypeScript thành các bảng SQL, cung cấp type-safety đầy đủ và một lớp trừu tượng nhất quán bất kể backend database là gì.
📖 TypeORM là gì?
Định nghĩa: TypeORM là một thư viện ORM chạy trên Node.js, hỗ trợ TypeScript first-class, cho phép tương tác với database thông qua các class và decorator thay vì raw SQL.
Hỗ trợ: PostgreSQL, MySQL, MariaDB, SQLite, Oracle, MS SQL Server, MongoDB (NoSQL), CockroachDB,...
So sánh: Trước và Sau TypeORM
❌ Trước (Raw SQL + node-postgres):
// filename: user.repository.ts — Trước TypeORM
import { Pool } from 'pg';
const pool = new Pool({ connectionString: '...' });
// Không có type-safety — gõ sai tên cột thì chỉ biết lúc runtime
async function findUserByEmail(email: string) {
const result = await pool.query(
'SELECT id, name, email FROM users WHERE email = $1 AND deleted_at IS NULL',
[email]
);
// result.rows có type là any[] — không biết shape của object
return result.rows[0];
}
// Khi thêm cột mới vào DB, phải tìm và sửa thủ công TẤT CẢ câu SQL liên quan
async function createUser(name: string, email: string) {
const result = await pool.query(
'INSERT INTO users (name, email, created_at) VALUES ($1, $2, NOW()) RETURNING *',
[name, email]
);
return result.rows[0];
}
✅ Sau (TypeORM):
// filename: user.entity.ts — Sau TypeORM
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, DeleteDateColumn } from 'typeorm';
@Entity('users')
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@Column({ unique: true })
email: string;
@CreateDateColumn()
createdAt: Date;
// Soft-delete: TypeORM tự động thêm WHERE deleted_at IS NULL vào mọi query
@DeleteDateColumn()
deletedAt: Date;
}
// filename: user.service.ts
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
class UserService {
constructor(
@InjectRepository(User)
private userRepository: Repository<User>
) {}
// TypeScript biết rõ return type là Promise<User | null>
findByEmail(email: string): Promise<User | null> {
return this.userRepository.findOne({ where: { email } });
}
createUser(name: string, email: string): Promise<User> {
const user = this.userRepository.create({ name, email });
return this.userRepository.save(user);
}
}
| Tiêu chí | Raw SQL | TypeORM |
|---|---|---|
| Type-safety | ❌ any[] | ✅ TypeScript Entity |
| Đổi Database | ❌ Viết lại query | ✅ Chỉ đổi type config |
| Quản lý Schema | ❌ Thủ công | ✅ Migrations tự động |
| Code maintainability | ❌ SQL lẫn logic | ✅ Tách biệt rõ ràng |
🔨 Data Source — Nguồn dữ liệu
DataSource là gì?
DataSource là entry point trung tâm của TypeORM — nó quản lý kết nối đến database, chứa config, và cung cấp các API để tương tác với database.
// filename: data-source.ts
import { DataSource } from 'typeorm';
import { User } from './user.entity';
export const AppDataSource = new DataSource({
type: 'postgres', // Loại database
host: 'localhost',
port: 5432,
username: 'postgres',
password: 'secret',
database: 'mydb',
entities: [User], // Danh sách entities
migrations: ['src/migrations/**/*.ts'],
synchronize: false, // ⚠️ Chỉ dùng true trong DEV, KHÔNG BAOGIỜ dùng trên PROD
logging: true,
});
Data Source Options — Các tùy chọn quan trọng
const dataSource = new DataSource({
// --- Connectivity ---
type: 'postgres',
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT, 10),
username: process.env.DB_USER,
password: process.env.DB_PASS,
database: process.env.DB_NAME,
// --- TLS/SSL (Production bắt buộc) ---
ssl: { rejectUnauthorized: false },
// --- Connection Pool ---
// Tại sao pool? Vì mở kết nối DB tốn kém, pool giúp tái sử dụng kết nối cũ
extra: {
max: 10, // Tối đa 10 kết nối đồng thời
min: 2, // Giữ ít nhất 2 kết nối luôn sẵn sàng
idleTimeoutMillis: 30000,
},
// --- Entities & Migrations ---
entities: ['src/**/*.entity.ts'],
migrations: ['src/migrations/*.ts'],
// --- Behavior ---
synchronize: false, // ⚠️ Tắt trên PROD — dùng migrations thay thế
logging: ['error', 'warn'], // Chỉ log lỗi và cảnh báo trên PROD
});