Vấn Đề Khiến Tôi Tìm Đến Hasura
Sáu tháng trước, tôi đang xử lý song song ba dự án khác nhau — một dùng MySQL, một dùng PostgreSQL, một dùng MongoDB. Mỗi database đều có điểm mạnh riêng. Nhưng mỗi lần schema thay đổi, tôi phải cập nhật đồng thời các hàm resolver, REST controller và auth middleware. Một buổi chiều nọ, tôi mất bốn tiếng đồng hồ chỉ để tìm ra tại sao việc đổi tên một cột duy nhất lại làm hỏng ba API endpoint và một ứng dụng mobile. Buổi debug đó cuối cùng đã thuyết phục tôi rằng cần phải thay đổi cách làm.
Câu hỏi không phải là “database nào tốt nhất” — mà là “tại sao tôi lại ngồi viết tay CRUD wrapper lần thứ một trăm?”
Nguyên Nhân Gốc Rễ: Cái Giá Phải Trả Cho Tầng API
Phát triển backend theo kiểu truyền thống đi kèm với thứ tôi gọi là phí tầng API. Bạn có một relational schema hoàn toàn tốt trong PostgreSQL, nhưng để expose nó ra ngoài, bạn cần phải:
- Viết route handler hoặc resolver cho từng bảng và mối quan hệ
- Tự triển khai logic lọc, phân trang và sắp xếp
- Thêm middleware xác thực và kết nối với kiểm tra cấp hàng
- Xây dựng hạ tầng WebSocket nếu cần cập nhật realtime
- Giữ tất cả những thứ trên đồng bộ với mỗi lần migration schema
Với một schema 20 bảng, con số này dễ dàng phình thành hàng nghìn dòng boilerplate trước khi bạn viết được một dòng business logic thực sự. Hầu hết đều là công việc thuần cơ học — nó tuân theo schema một cách tất định. Vậy mà con người vẫn phải viết tay, và đó là lúc lỗi bắt đầu len lỏi vào.
Các Giải Pháp Tôi Đã Cân Nhắc
Lựa chọn 1: REST API thủ công (FastAPI / Express)
Toàn quyền kiểm soát, công cụ quen thuộc, dễ tùy chỉnh. Nhưng chi phí bảo trì là có thật. Mỗi lần ALTER TABLE lại kéo theo thay đổi trên nhiều file. Tôi vẫn dùng cách này cho các service có business logic phức tạp, nhưng không dùng cho CRUD nặng về dữ liệu.
Lựa chọn 2: PostgREST
PostgREST tự động tạo REST API trực tiếp từ schema PostgreSQL. Nhẹ và nhanh. Nhưng REST có nghĩa là client không có quyền chọn field — bạn không thể yêu cầu chính xác các field mình cần, và không có realtime tích hợp sẵn. Với các dashboard đọc nhiều dùng GraphQL client, nó không phù hợp.
Lựa chọn 3: Prisma + Apollo GraphQL Server
Bạn có được các truy vấn type-safe và một tầng GraphQL chính thống với sự kết hợp này. Nhưng bạn vẫn phải viết resolver, quản lý Prisma client, và cấu hình subscription riêng lẻ. Tốt hơn REST thuần túy — nhưng không loại bỏ được boilerplate.
Lựa chọn 4: Hasura GraphQL Engine
Hasura kết nối với database PostgreSQL hiện có của bạn và ngay lập tức tạo ra một GraphQL API hoàn chỉnh — query, mutation, subscription — mà không cần viết code. Permission được cấu hình qua UI hoặc file YAML metadata, không bị rải rác khắp middleware. Sau sáu tháng chạy production, đây là thứ tôi sẽ khuyến nghị cho các team đang xây dựng ứng dụng data-driven mà không có đội backend lớn.
Cài Đặt Hasura với Docker
Cách nhanh nhất để chạy Hasura trên máy local là dùng Docker Compose. Tạo file docker-compose.yml:
version: '3.6'
services:
postgres:
image: postgres:16
restart: always
environment:
POSTGRES_PASSWORD: mysecretpassword
volumes:
- db_data:/var/lib/postgresql/data
graphql-engine:
image: hasura/graphql-engine:v2.40.0
ports:
- "8080:8080"
restart: always
environment:
HASURA_GRAPHQL_DATABASE_URL: postgres://postgres:mysecretpassword@postgres:5432/postgres
HASURA_GRAPHQL_ENABLE_CONSOLE: "true"
HASURA_GRAPHQL_ADMIN_SECRET: myadminsecretkey
HASURA_GRAPHQL_JWT_SECRET: '{"type":"HS256","key":"your-256-bit-secret-here"}'
depends_on:
- postgres
volumes:
db_data:
Khởi động:
docker compose up -d
Mở http://localhost:8080/console và nhập admin secret của bạn. Xong phần cài đặt. Hasura giờ đây đã kết nối với instance PostgreSQL của bạn.
Track Bảng và Mối Quan Hệ
Hasura không tự động expose các bảng — bạn phải “track” chúng qua console hoặc API. Đây là thiết kế có chủ ý: bạn có thể có các bảng nội bộ (audit log, migration history) mà bạn không muốn đưa vào public API.
Giả sử bạn có các bảng sau:
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email TEXT UNIQUE NOT NULL,
created_at TIMESTAMPTZ DEFAULT now()
);
CREATE TABLE posts (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES users(id),
title TEXT NOT NULL,
body TEXT,
published BOOLEAN DEFAULT false,
created_at TIMESTAMPTZ DEFAULT now()
);
Trong Hasura console, vào Data → public → Track All. Hasura tự động phát hiện foreign key và gợi ý các mối quan hệ. Chấp nhận mối quan hệ array user → posts và mối quan hệ object post → user. Giờ bạn có thể truy vấn dữ liệu lồng nhau trong một request duy nhất:
query GetUserPosts {
users {
email
posts(where: { published: { _eq: true } }) {
title
created_at
}
}
}
Không cần code resolver. Việc duyệt quan hệ, join và lọc đều xảy ra tự động.
Bảo Mật Cấp Hàng Mà Không Cần Middleware Tùy Chỉnh
Mô hình permission khiến tôi bất ngờ — theo hướng tích cực. Trong các thiết lập truyền thống, bạn hoặc dùng PostgreSQL RLS policy trực tiếp (phức tạp khi quản lý ở quy mô lớn) hoặc viết kiểm tra phân quyền trong mỗi resolver (dễ sai và dễ bỏ sót). Hasura đi theo con đường thứ ba: định nghĩa rule permission theo role trong console, và nó tự dịch chúng thành mệnh đề WHERE trước khi chạm vào database.
Ví dụ: người dùng chỉ được đọc bài viết của chính họ.
Trong console, vào Data → posts → Permissions → user role → Select, sau đó đặt row permission:
{
"user_id": { "_eq": "X-Hasura-User-Id" }
}
X-Hasura-User-Id là một claim được trích xuất từ JWT token mà Hasura nhận được trong mỗi request. Khi người dùng đã đăng nhập truy vấn bài viết, Hasura tự động thêm WHERE user_id = '<id-của-họ>'. Họ không thể xem bài viết của người khác — dù họ đặt gì trong query.
Khả năng hiển thị ở cấp cột, giới hạn số hàng, và các rule riêng cho insert, update và delete đều có thể cấu hình theo role từ cùng một giao diện.
Realtime Subscription Mà Không Cần Dựng WebSocket
Triển khai realtime dựa trên WebSocket trong một backend tùy chỉnh tốn ít nhất một cuối tuần. Xử lý kết nối, heartbeat, logic reconnect — mọi thứ cộng lại rất nhanh. Với Hasura, bạn chỉ cần đổi query thành subscription:
subscription WatchNewPosts {
posts(
where: { published: { _eq: true } },
order_by: { created_at: desc },
limit: 10
) {
id
title
created_at
}
}
Client nhận được cập nhật mỗi khi tập kết quả thay đổi. Hasura polling PostgreSQL theo chu kỳ có thể cấu hình (mặc định 1 giây) và đẩy các thay đổi đến client đang kết nối qua WebSocket. Với một feed thông báo trực tiếp hoặc dashboard, bạn có realtime hoạt động mà không cần quản lý thêm hạ tầng nào.
Quản Lý Hasura Trên Môi Trường Production
Sáu tháng chạy production đã dạy tôi ba thực hành trông có vẻ đơn giản nhưng lại quan trọng hơn nhiều so với vẻ ngoài:
Dùng Hasura Metadata để Quản Lý Phiên Bản
Tất cả permission, mối quan hệ và các bảng đã track đều được lưu dưới dạng file YAML metadata. Xuất chúng ra và commit vào git:
# Cài đặt Hasura CLI
curl -L https://github.com/hasura/graphql-engine/raw/stable/cli/get.sh | bash
# Khởi tạo dự án
hasura init my-project --endpoint http://localhost:8080 --admin-secret myadminsecretkey
cd my-project
# Xuất metadata hiện tại
hasura metadata export
# Áp dụng metadata sang môi trường khác
hasura metadata apply --endpoint https://staging.example.com
Việc chuyển đổi môi trường — từ local lên staging rồi production — chỉ cần một lệnh duy nhất.
Không Bao Giờ Để Lộ Admin Secret Ra Client
Admin secret bỏ qua tất cả rule permission. Chỉ đặt HASURA_GRAPHQL_ADMIN_SECRET trong môi trường server và dùng JWT token cho mọi request từ phía client. Dịch vụ xác thực của bạn (Auth0, Supabase Auth, hoặc một JWT issuer tùy chỉnh) cấp token với các claim dành riêng cho Hasura:
{
"sub": "user-uuid-here",
"https://hasura.io/jwt/claims": {
"x-hasura-allowed-roles": ["user"],
"x-hasura-default-role": "user",
"x-hasura-user-id": "user-uuid-here"
}
}
Dùng Actions cho Business Logic
Hasura không thay thế hoàn toàn code backend — nó thay thế tầng dữ liệu. Với các thao tác có side effect (gửi email, xử lý thanh toán, gọi API bên thứ ba), hãy dùng Hasura Actions: định nghĩa một GraphQL mutation tùy chỉnh mà Hasura sẽ chuyển tiếp đến một HTTP handler do bạn viết. Data mutation ở trong tầng type-safe của Hasura; side effect nằm trong handler của bạn.
Hasura Phù Hợp Ở Đâu — và Không Phù Hợp Ở Đâu
Sau sáu tháng, đây là đánh giá thật lòng của tôi: Hasura tiết kiệm rất nhiều thời gian cho các ứng dụng nặng về dữ liệu, nơi schema chính là sản phẩm. Công cụ nội bộ, dashboard, admin panel và mobile backend với các pattern CRUD tiêu chuẩn là nơi nó phát huy tốt nhất.
Logic biến đổi nặng giữa database và client lại là câu chuyện khác. Các team đã đầu tư sâu vào REST — và chưa sẵn sàng chuyển frontend sang dùng GraphQL — sẽ thấy chi phí migration lớn hơn lợi ích thu được.
Nhưng nếu team bạn cứ phải ngồi viết thêm một “endpoint danh sách nữa với filter và phân trang”, hãy cho Hasura một ngày thử nghiệm đúng nghĩa. Các bảng PostgreSQL của tôi được track, cấu hình permission và phục vụ React frontend trong chưa đầy hai tiếng — thời gian đó tôi đã dành cho các tính năng sản phẩm thực sự thay vào đó.

