Đừng viết CRUD nữa: Xây dựng REST API trực tiếp từ PostgreSQL với PostgREST

Database tutorial - IT technology blog
Database tutorial - IT technology blog

Cái bẫy Boilerplate

Tôi đã dành vô số ngày cuối tuần bị kẹt trong một vòng lặp. Tôi thiết kế một schema PostgreSQL sạch sẽ, rồi lãng phí ba ngày tiếp theo để viết logic CRUD (Create, Read, Update, Delete) lặp đi lặp lại trong Node.js hoặc Python. Tôi xây dựng các controller, định nghĩa DTO và ánh xạ thủ công từng cột cơ sở dữ liệu vào một API endpoint. Việc này thật tẻ nhạt, dễ sai sót và làm mất đi động lực của dự án.

Sự cản trở này tồn tại vì chúng ta đã chấp nhận một khoảng cách kiến trúc khổng lồ như một tiêu chuẩn. Chúng ta coi cơ sở dữ liệu như một thùng chứa dữ liệu thụ động và coi backend là nguồn trí tuệ duy nhất. Tuy nhiên, PostgreSQL vốn dĩ đã cực kỳ tinh vi. Nó sở hữu hệ thống kiểu dữ liệu mạnh mẽ, các ràng buộc (constraints) phức tạp và mô hình phân quyền đã được kiểm chứng qua thời gian. Việc viết lại logic đó ở tầng trung gian thường là dư thừa.

PostgREST loại bỏ lớp trung gian này bằng cách cung cấp một RESTful API trực tiếp từ schema của bạn. Nó kiểm tra cấu trúc cơ sở dữ liệu và tạo các endpoint ngay lập tức. Bạn có được một API sẵn sàng cho môi trường production mà không cần viết một dòng mã backend nào.

Bắt đầu nhanh: Từ con số 0 đến API trong 5 phút

Tôi thích những công cụ không gây phiền hà. PostgREST là một file thực thi duy nhất được biên dịch bằng Haskell. Nó cực kỳ nhẹ, thường tiêu tốn ít hơn 30MB RAM, nhưng có thể xử lý hơn 2.000 yêu cầu mỗi giây trên một VPS cơ bản giá 5 USD/tháng. Đây là cách tôi thiết lập một API thực tế trên một máy chủ mới.

1. Chuẩn bị cơ sở dữ liệu

Bảo mật bắt đầu từ việc cô lập. Tôi không bao giờ để lộ schema public. Thay vào đó, tôi tạo một schema api riêng biệt để kiểm soát chính xác những gì web có thể thấy. Hãy cùng xây dựng một ứng dụng quản lý công việc đơn giản.

-- Kết nối tới instance Postgres của bạn
CREATE SCHEMA api;

CREATE TABLE api.todos (
  id SERIAL PRIMARY KEY,
  done BOOLEAN DEFAULT false,
  task TEXT NOT NULL,
  due TIMESTAMPTZ
);

-- Tạo một role cho các yêu cầu web ẩn danh
CREATE ROLE web_anon NOLOGIN;
GRANT USAGE ON SCHEMA api TO web_anon;
GRANT SELECT ON api.todos TO web_anon;

2. Cài đặt và Chạy PostgREST

Việc cài đặt rất đơn giản. Bạn có thể sử dụng Docker, nhưng đối với phát triển cục bộ, file thực thi (binary) sẽ nhanh hơn. Hãy tải phiên bản mới nhất cho hệ điều hành của bạn và giải nén nó.

# Ví dụ cho người dùng Linux
wget https://github.com/PostgREST/postgrest/releases/download/v12.0.2/postgrest-v12.0.2-linux-static-x64.tar.xz
tar xf postgrest-v12.0.2-linux-static-x64.tar.xz

Tiếp theo, hãy tạo một tệp tutorial.conf để chỉ dẫn PostgREST cách kết nối với cơ sở dữ liệu của bạn:

db-uri = "postgres://authenticator:mysecretpassword@localhost:5432/postgres"
db-schema = "api"
db-anon-role = "web_anon"

Khởi chạy nó bằng lệnh ./postgrest tutorial.conf. API của bạn hiện đã hoạt động tại http://localhost:3000/todos. Một yêu cầu GET đơn giản sẽ trả về dữ liệu của bạn dưới dạng mảng JSON sạch sẽ ngay lập tức.

Tìm hiểu sâu: Bảo mật với JWT và RLS

Mối quan tâm phổ biến nhất mà tôi thường nghe là: “Làm thế nào để xử lý quyền của người dùng?” PostgREST không bận tâm đến logic đăng nhập tùy chỉnh. Nó giao phó việc bảo mật cho JSON Web Tokens (JWT) và Row Level Security (RLS) gốc của PostgreSQL. Cách tiếp cận này an toàn hơn đáng kể so với việc kiểm tra ở cấp độ ứng dụng truyền thống.

Mô hình Authenticator

Hãy coi role authenticator như một proxy đáng tin cậy. Nó kết nối với cơ sở dữ liệu nhưng không có quyền hạn riêng. Nhiệm vụ duy nhất của nó là chuyển sang một role người dùng cụ thể dựa trên các claim tìm thấy trong JWT.

-- Tạo một role cho người dùng đã đăng nhập
CREATE ROLE todo_user NOLOGIN;
GRANT USAGE ON SCHEMA api TO todo_user;
GRANT ALL ON api.todos TO todo_user;
GRANT USAGE, SELECT ON SEQUENCE api.todos_id_seq TO todo_user;

-- Cho phép authenticator chuyển sang role todo_user
GRANT todo_user TO authenticator;

Cấu hình JWT Secret

Thêm một jwt-secret vào tệp tutorial.conf của bạn. Khóa này phải có ít nhất 32 ký tự để ngăn chặn các cuộc tấn công brute-force.

jwt-secret = "a-very-secure-32-character-secret-key"

Khi một client gửi header Authorization: Bearer <token>, PostgREST sẽ xác thực nó. Nếu token chứa "role": "todo_user", cơ sở dữ liệu sẽ thực thi truy vấn dưới danh nghĩa người dùng đó. Bạn không còn phải kiểm tra thủ công if (user.id === record.owner_id) trong mã Node.js của mình nữa.

Sử dụng nâng cao: Vượt xa CRUD đơn thuần

PostgREST không chỉ là một lớp vỏ bọc cơ bản. Nó xử lý các yêu cầu dữ liệu phức tạp mà thông thường đòi hỏi hàng trăm dòng mã.

Bộ lọc mạnh mẽ

Bạn không cần phải xây dựng một công cụ tìm kiếm. PostgREST ánh xạ các tham số URL vào các điều kiện SQL. Để tìm các công việc chưa hoàn thành có nhắc đến ‘coffee’ và sắp đến hạn, chỉ cần sử dụng:

GET /todos?done=is.false&task=like.*coffee*&limit=10

Nhúng tài nguyên (Joins)

Việc truy xuất dữ liệu liên quan thường dẫn đến bẫy hiệu năng “truy vấn N+1”. PostgREST giải quyết vấn đề này bằng Resource Embedding. Nếu các bảng userstodos của bạn được liên kết bằng khóa ngoại, bạn có thể lấy chúng cùng lúc trong một yêu cầu:

GET /users?select=name,todos(*)

Nhập dữ liệu hiệu quả

Xử lý dữ liệu lớn là một điểm khó khăn phổ biến. Khi tôi cần chuyển đổi các bảng tính cũ, tôi sử dụng toolcraft.app/vi/tools/data/csv-to-json để chuyển đổi các hàng thành một mảng JSON cục bộ. Sau đó, tôi POST mảng đó trực tiếp đến endpoint của PostgREST. Cơ sở dữ liệu sẽ xử lý việc chèn hàng loạt trong một transaction duy nhất, đảm bảo tính toàn vẹn của dữ liệu.

Tối ưu hóa cho môi trường Production

Sau khi triển khai PostgREST cho nhiều dự án có lưu lượng truy cập cao, tôi nhận thấy ba thực hành sau đây là thiết yếu:

  • Sử dụng SQL Views: Hãy coi các bảng của bạn là riêng tư. Thay vào đó, hãy cung cấp View cho API. Điều này cho phép bạn thay đổi schema cơ sở dữ liệu mà không làm hỏng hợp đồng API với frontend.
  • Bắt buộc sử dụng RLS: Luôn bật Row Level Security. Nó đóng vai trò như một lưới an toàn, đảm bảo người dùng không bao giờ có thể truy cập vào hàng của người khác ngay cả khi một API endpoint bị cấu hình sai.
  • Stored Procedures cho logic: Đối với các hành động như gửi Email chào mừng, hãy sử dụng trigger hoặc function của PostgreSQL. Bạn có thể kích hoạt chúng thông qua POST /rpc/send_welcome_email.

Bằng cách chuyển logic vào cơ sở dữ liệu, bạn tạo ra một “nguồn sự thật duy nhất” (single source of truth). Dù người dùng của bạn kết nối qua ứng dụng web, ứng dụng di động hay công cụ CLI, các quy tắc kinh doanh của bạn vẫn nhất quán. Nó buộc bạn phải thành thạo SQL — một kỹ năng sẽ vẫn còn giá trị rất lâu sau khi các framework backend hợp thời hiện nay biến mất.

Share: