Truy vấn SQL Type-safe với Kysely: Giải pháp thay thế nhẹ nhàng cho các ORM cồng kềnh

Programming tutorial - IT technology blog
Programming tutorial - IT technology blog

Điểm trung hòa giữa SQL thuần và các ORM cồng kềnh

Khi bắt đầu xây dựng một backend Node.js mới, bạn thường phải đánh đổi giữa SQL thuần (raw SQL) và các ORM cồng kềnh. Các chuỗi SQL thuần mang lại hiệu năng và khả năng kiểm soát tối đa, nhưng chúng lại rất dễ lỗi.

Chỉ một lỗi đánh máy nhỏ trong tên cột cũng có thể không bị phát hiện cho đến khi nó làm sập môi trường production của bạn. Ở phía ngược lại, các ORM như Prisma hay TypeORM mang lại sự an toàn nhưng đi kèm với những gánh nặng đáng kể. Query engine của Prisma có thể thêm hơn 100MB vào Docker image của bạn và gây ra độ trễ 5-10ms cho mỗi request chỉ vì lớp chuyển đổi nội bộ của nó.

Kysely chiếm lĩnh vị trí trung hòa hoàn hảo. Đây là một query builder ưu tiên TypeScript, tận dụng SQL thay vì che giấu nó. Thay vì các lớp trừu tượng phức tạp, nó cung cấp một lớp type-safe giúp việc viết các truy vấn lỗi gần như là không thể. Đội ngũ của tôi đã sử dụng thư viện này trong môi trường production nhiều năm để đạt được sự ổn định cao. Bạn có được sự tự tin từ việc kiểm tra lỗi lúc biên dịch (compile-time checks) mà không gặp phải sự “kỳ bí” khiến các ORM truyền thống trở nên khó debug.

Kysely tận dụng hệ thống kiểu (type system) nâng cao của TypeScript để ánh xạ schema cơ sở dữ liệu trực tiếp vào các truy vấn của bạn. Nếu bạn chọn một cột không tồn tại hoặc join bảng sai, trình biên dịch sẽ báo lỗi ngay lập tức. Bạn sẽ bắt được bug ngay trong quá trình phát triển, thay vì lúc 3 giờ sáng khi đang trong ca trực on-call.

Cài đặt: Thiết lập nền tảng

Để bắt đầu, bạn cần thư viện lõi và một driver cho cơ sở dữ liệu cụ thể của mình. Mặc dù tôi sử dụng PostgreSQL cho hầu hết các dự án, logic vẫn tương tự đối với MySQL hoặc SQLite. Trước tiên, bạn cần cấu hình TypeScript trong môi trường của mình.

Khởi tạo dự án và cài đặt các gói cần thiết:

npm init -y
npm install kysely pg
npm install -D typescript @types/node @types/pg ts-node

Kysely đảm nhận việc xây dựng truy vấn nhưng để việc kết nối thực tế cho các driver chuyên dụng. Gói pg quản lý connection pool của PostgreSQL. Nếu bạn thích MySQL, chỉ cần thay pg bằng mysql2. Đối với phát triển local hoặc các edge function nhỏ, better-sqlite3 là một lựa chọn thay thế tuyệt vời.

Việc tạo interface thủ công rất tẻ nhạt. Để tiết kiệm thời gian, hãy sử dụng kysely-codegen. Công cụ này sẽ kiểm tra schema thực tế và tự động tạo các định nghĩa TypeScript, đảm bảo mã nguồn của bạn luôn khớp với trạng thái của cơ sở dữ liệu.

npm install -D kysely-codegen

Cấu hình: Ánh xạ Schema

Interface Database đóng vai trò là nguồn sự thật duy nhất (single source of truth) cho ứng dụng của bạn. Nó định nghĩa mọi bảng, cột và kiểu dữ liệu trong hệ thống. Khi interface này được thiết lập, Kysely cung cấp khả năng tự động hoàn thành (autocomplete) đầy đủ trong IDE cho mọi truy vấn bạn viết.

Tạo file db.ts để khởi tạo kết nối. Hãy xem xét một thiết lập blog tiêu chuẩn với các bảng usersposts.

import { Pool } from 'pg';
import { Kysely, PostgresDialect } from 'kysely';

interface UserTable {
  id: number;
  email: string;
  first_name: string;
  created_at: Date;
}

interface PostTable {
  id: number;
  title: string;
  content: string;
  author_id: number;
}

interface Database {
  users: UserTable;
  posts: PostTable;
}

export const db = new Kysely<Database>({
  dialect: new PostgresDialect({
    pool: new Pool({
      database: 'my_blog_db',
      host: 'localhost',
      user: 'admin',
      port: 5432,
      max: 10, // Duy trì tối đa 10 kết nối đồng thời
    }),
  }),
});

Việc truyền interface Database vào constructor của Kysely sẽ liên kết các kiểu dữ liệu của bạn với engine thực thi. Từ thời điểm này, đối tượng db sẽ hiểu rõ toàn bộ schema của bạn. Nó sẽ không cho phép bạn tham chiếu đến một bảng không tồn tại.

Viết truy vấn và đảm bảo tính ổn định

Lợi ích của việc thiết lập này trở nên rõ ràng ngay khi bạn bắt đầu gõ phím. Khi bạn gọi db.selectFrom('...'), IDE sẽ gợi ý users hoặc posts. Điều này loại bỏ việc đoán mò thường thấy khi làm việc với các chuỗi truy vấn thuần.

Truy xuất dữ liệu với Type Safety

Join các bảng thường là nơi các ORM trở nên khó hiểu. Kysely giữ cú pháp gần gũi với SQL tiêu chuẩn trong khi vẫn duy trì các kiểu dữ liệu nghiêm ngặt:

async function getAuthorWithPosts(userId: number) {
  const result = await db
    .selectFrom('users')
    .innerJoin('posts', 'posts.author_id', 'users.id')
    .select([
      'users.email',
      'posts.title',
      'posts.content'
    ])
    .where('users.id', '=', userId)
    .execute();

  return result;
}

Hãy tưởng tượng bạn đổi tên email thành contact_email trong cơ sở dữ liệu. Nếu không có Kysely, bạn có thể bỏ sót một tham chiếu ở một file xa xôi nào đó. Với Kysely, trình biên dịch sẽ đánh dấu truy vấn này là lỗi ngay lập tức, ngăn chặn lỗi runtime xảy ra.

Thêm và cập nhật dữ liệu

Việc thay đổi dữ liệu cũng an sau không kém. Kysely xác thực rằng các đối tượng bạn chèn vào khớp với kiểu dữ liệu của cột mong muốn. Nó ngăn bạn vô tình gửi một chuỗi vào một cột kiểu số nguyên.

async function createPost(title: string, content: string, authorId: number) {
  return await db
    .insertInto('posts')
    .values({
      title,
      content,
      author_id: authorId,
    })
    .returning('id')
    .executeTakeFirst();
}

Giám sát và Debug

Sự trừu tượng thường che giấu những gì thực sự xảy ra bên dưới. Kysely tránh điều này bằng cách cung cấp một hook logging đơn giản. Tôi khuyên bạn nên bật tính năng này trong quá trình phát triển để kiểm tra tính hiệu quả của truy vấn.

const db = new Kysely<Database>({
  dialect: new PostgresDialect({ ... }),
  log(event) {
    if (event.level === 'query') {
      console.log(`SQL: ${event.query.sql}`);
      console.log(`Tham số: ${event.query.parameters}`);
    }
  },
});

Điều này cho phép bạn thấy chính xác câu lệnh SQL được gửi đến máy chủ. Nếu một truy vấn chậm, bạn có thể dán trực tiếp kết quả đầu ra vào pgAdmin hoặc DBeaver. Bạn có thể chạy EXPLAIN ANALYZE mà không cần phải dịch từ cú pháp ORM riêng biệt ngược lại SQL.

Tái cấu trúc một cách tự tin

Thay đổi schema là điều không thể tránh khỏi khi ứng dụng phát triển. Khi sử dụng Kysely, việc tái cấu trúc không còn là một hoạt động rủi ro cao. Nếu bạn cập nhật một kiểu dữ liệu của cột trong interface, mọi tham chiếu bị lỗi trong toàn bộ dự án sẽ hiển thị màu đỏ trong trình soạn thảo. Bạn có thể sửa mọi truy vấn trước khi mã nguồn đến được môi trường kiểm thử. Quy trình làm việc này đã giúp đội ngũ của tôi tiết kiệm hàng trăm giờ debug thủ công và giảm đáng kể tỷ lệ lỗi trên production.

Share: