Xây SaaS không dùng Supabase: kiến trúc backend linh hoạt cho tăng trưởng
Supabase giúp khởi đầu nhanh: database, auth, storage, realtime, API. Nhưng khi SaaS lớn dần, câu hỏi đổi: nhanh hôm nay có đủ bền cho ngày mai không?
Nhiều đội chọn Supabase vì muốn ship MVP nhanh. Đúng. Nhưng không phải SaaS nào cũng hợp với backend-as-a-service. Khi sản phẩm bắt đầu có nhiều tenant, logic phân quyền phức tạp, workload nặng, yêu cầu compliance, hoặc cần kiểm soát hạ tầng sâu hơn, kiến trúc tự chủ đáng xem xét.
Không dùng Supabase không có nghĩa quay về “tự build mọi thứ từ đầu”. Nghĩa đúng hơn: thiết kế backend bằng các khối chuẩn, thay thế được, mở rộng được, không bị khóa vào một nền tảng duy nhất.
Bài này đi vào kiến trúc thực tế cho SaaS tăng trưởng: database, API, auth, background jobs, file storage, observability, deployment, multi-tenancy và chiến lược mở rộng.
Vì sao không dùng Supabase?
Supabase mạnh ở giai đoạn đầu. Nhưng vài giới hạn thường xuất hiện khi SaaS lớn hơn.
Kiểm soát kiến trúc hạn chế
Supabase gom nhiều lớp vào cùng trải nghiệm: Postgres, Auth, Storage, Edge Functions, Realtime. Tiện, nhưng cũng làm kiến trúc dễ phụ thuộc vào cách Supabase tổ chức dịch vụ.
Khi cần:
– Custom auth flow sâu
– Multi-region phức tạp
– Tách service theo domain
– Kiểm soát connection pooling
– Tối ưu query lớn
– Tuân thủ quy định dữ liệu riêng
… đội dev thường phải đi vòng hoặc rời bớt khỏi Supabase.
Rủi ro vendor lock-in
Postgres vẫn là Postgres, nhưng Auth schema, Storage API, Realtime channel, Edge Functions, policy pattern có thể gắn chặt với hệ sinh thái Supabase.
Nếu sau này migrate, chi phí không chỉ là chuyển data. Còn phải đổi:
– Logic phân quyền
– SDK frontend
– API contract
– File URL pattern
– Webhook
– Background workflows
– Monitoring setup
SaaS tăng trưởng cần backend “thay ruột được”
Backend tốt không phải backend có nhiều tính năng nhất. Backend tốt là backend cho phép thay từng phần mà không phá cả hệ thống.
Ví dụ:
– Đổi Redis provider không ảnh hưởng business logic
– Đổi S3-compatible storage không đổi API client
– Đổi auth provider không rewrite app
– Tách billing service ra riêng khi doanh thu tăng
– Chuyển một module sang worker queue khi traffic tăng
Đó là mục tiêu chính.
Nguyên tắc kiến trúc: modular, explicit, boring
SaaS tăng trưởng không cần công nghệ lạ. Cần nền tảng rõ.
Modular: chia theo domain
Thay vì chia code theo kỹ thuật như controllers, services, models, nên chia theo domain:
– API routes
– Service logic
– Repository/database access
– Validation schema
– Events riêng
Lợi ích: khi domain lớn, dễ tách thành service riêng. Không cần microservices từ ngày đầu, nhưng code phải sẵn đường tách.
Explicit: logic rõ, không ẩn quá nhiều
Backend-as-a-service thường ẩn nhiều phần: auth state, row-level policy, generated API, storage permission. Khi lỗi xảy ra, debug khó hơn.
Kiến trúc tự chủ nên làm rõ:
– Ai gọi API nào
– Permission check ở đâu
– Transaction nằm đâu
– Event nào phát ra sau action
– Job nào chạy nền
– Data model nào là nguồn sự thật
Rõ hơn, dễ vận hành hơn.
Boring: chọn công nghệ bền
Stack gợi ý:
– API: Node.js + NestJS/Fastify, Go, hoặc Django
– Database: PostgreSQL
– Cache/queue: Redis
– Job queue: BullMQ, Faktory, Sidekiq, Celery
– Storage: S3 hoặc S3-compatible như Cloudflare R2, MinIO
– Search: Meilisearch, OpenSearch, Typesense
– Observability: OpenTelemetry, Prometheus, Grafana, Sentry
– Deployment: Docker, Kubernetes khi cần, hoặc ECS/Fly.io/Render/Railway giai đoạn đầu
Không cần mới. Cần ổn.
Database: Postgres làm lõi, nhưng đừng để thành “thần hộ mệnh”
PostgreSQL vẫn là lựa chọn tốt cho SaaS: ACID, indexing mạnh, JSONB, full-text search cơ bản, partitioning, extensions.
Thiết kế schema cho tăng trưởng
Các bảng SaaS thường có tenant/team/workspace:
CREATE TABLE projects (
id UUID PRIMARY KEY,
tenant_id UUID NOT NULL,
name TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX idx_projects_tenant_id ON projects (tenant_id);
Quy tắc:
– Mọi bảng thuộc tenant nên có tenant_id
– Index theo tenant_id + trường lọc thường dùng
– Không lạm dụng JSONB cho dữ liệu cần query nhiều
– Có migration version rõ
– Dùng transaction cho workflow quan trọng
Multi-tenancy: chọn đúng cấp cô lập
Có ba mô hình phổ biến:
1. Shared database, shared schema
– Mỗi row có tenant_id
– Rẻ, dễ vận hành
– Phù hợp hầu hết SaaS B2B giai đoạn đầu
2. Shared database, separate schema
– Mỗi tenant một schema
– Cô lập hơn
– Migration phức tạp hơn
3. Database riêng từng tenant
– Cô lập mạnh
– Phù hợp enterprise/compliance
– Chi phí vận hành cao
Khuyến nghị thực tế: bắt đầu với shared schema, thiết kế abstraction để có thể đưa enterprise tenant sang database riêng sau.
Row-Level Security hay application-level authorization?
Supabase dùng nhiều RLS. Nếu tự build, có thể dùng RLS hoặc kiểm tra quyền trong app.
– RLS tốt khi cần chặn lỗi query thiếu tenant filter
– App-level authorization tốt khi logic quyền phức tạp, cần test rõ, cần audit
Nhiều SaaS dùng kết hợp: app-level cho business rule, RLS cho lớp bảo vệ cuối.
API layer: hợp đồng ổn định quan trọng hơn framework
API là biên giới giữa frontend, mobile app, integration, webhook và worker.
REST, GraphQL hay tRPC?
– REST: rõ, bền, dễ cache, hợp public API
– GraphQL: hợp client phức tạp, cần query linh hoạt
– tRPC: nhanh cho TypeScript full-stack, ít hợp public API dài hạn
Nếu SaaS có kế hoạch mở API cho khách hàng, REST với OpenAPI là lựa chọn an toàn.
Validation và error contract
Đừng để frontend đoán lỗi. Chuẩn hóa response:
{
"error": {
"code": "PROJECT_LIMIT_REACHED",
"message": "Project limit reached for current plan."
}
}
Mỗi endpoint cần:
– Validate input
– Kiểm tra auth
– Kiểm tra permission
– Gọi service
– Log event quan trọng
– Trả lỗi có code ổn định
API tốt giúp sản phẩm dễ mở rộng integration.
Auth: tự build ít nhất có thể, kiểm soát nhiều nhất cần thiết
Auth là nơi không nên “tự chế” nếu không có lý do mạnh. Không dùng Supabase Auth không nghĩa là tự viết password hashing, session, OAuth từ đầu.
– Email/password hoặc magic link
– OAuth Google/Microsoft
– Organization/team membership
– Roles/permissions
– SSO/SAML cho enterprise
– SCIM nếu bán cho công ty lớn
– Audit log
Tách rõ hai khái niệm:
– Identity: user là ai
– Authorization: user được làm gì trong tenant nào
Đừng gắn role trực tiếp vào user toàn cục nếu SaaS có nhiều workspace. Một user có thể là admin ở workspace A, viewer ở workspace B.
Background jobs: tăng trưởng bắt đầu khi request không còn đủ
Nhiều việc không nên chạy trong HTTP request:
– Gửi email
– Tạo invoice
– Xử lý file
– Đồng bộ CRM
– Gọi webhook
– Tính analytics
– Import dữ liệu
– Retry payment
Dùng queue từ sớm giúp hệ thống chịu tải tốt hơn.
API request
-> ghi database
-> publish event/job
-> trả response nhanh
Worker
-> xử lý job
-> retry nếu lỗi
-> log kết quả
Cần có:
– Retry với backoff
– Dead-letter queue
– Idempotency key
– Job timeout
– Dashboard quan sát job
Không có idempotency, retry có thể tạo double charge, double email, double webhook. Đây là lỗi SaaS rất đau.
File storage: abstraction nhỏ, lợi ích lớn
Đừng để code gọi trực tiếp provider khắp nơi. Tạo interface:
Sau đó dùng S3, R2, GCS, Azure Blob hoặc MinIO phía sau. Khi cần đổi provider, chỉ đổi adapter.
Quy tắc:
– Không lưu file binary trong database
– Lưu metadata trong Postgres
– Dùng signed URL cho file private
– Quét virus nếu khách upload file nhạy cảm
– Tách bucket theo environment
Observability: không đo, không tăng trưởng
SaaS chết không chỉ vì bug. SaaS chết vì không biết bug nằm đâu.
Tối thiểu cần:
– Structured logs
– Request ID xuyên suốt API và worker
– Error tracking bằng Sentry
– Metrics: latency, error rate, queue depth, DB connection
– Tracing cho flow phức tạp
– Audit log cho hành động nhạy cảm
Khi khách báo lỗi, đội support cần tìm được sự kiện trong vài phút, không phải vài giờ.
Deployment: bắt đầu đơn giản, chuẩn bị đường lớn
Không cần Kubernetes từ ngày đầu. Nhiều SaaS chạy tốt với:
– Docker image
– Managed Postgres
– Managed Redis
– Object storage
– One API service
– One worker service
– CI/CD tự động
Khi traffic tăng, scale theo lớp:
1. Tăng instance API
2. Tăng worker riêng theo queue
3. Tối ưu index và query
4. Thêm read replica
5. Tách service nặng
6. Dùng cache có chiến lược
7. Multi-region nếu cần
Quan trọng: backend phải stateless ở API layer. Session lưu trong cookie/JWT hoặc Redis, file lưu object storage, job lưu queue. Như vậy horizontal scale dễ hơn.
Billing và entitlement: đừng để thành if-else khắp code
SaaS nào cũng sẽ có plan, quota, trial, add-on, overage. Nếu rải logic kiểu:
Supabase rất tốt cho nhiều dự án. Nhưng với SaaS cần tăng trưởng dài hạn, backend tự chủ cho nhiều quyền hơn: quyền tối ưu, quyền thay provider, quyền thiết kế domain sâu, quyền đáp ứng enterprise, quyền debug rõ.
Kiến trúc khuyến nghị:
– Postgres làm lõi dữ liệu
– API rõ contract
– Auth dùng provider chuyên biệt hoặc self-hosted chuẩn
– Queue cho việc nền
– S3-compatible storage qua abstraction
– Observability từ sớm
– Multi-tenancy thiết kế ngay ngày đầu
– Billing/entitlement tách riêng
– Deployment đơn giản nhưng stateless
Không cần build phức tạp ngay. Cần build đúng điểm nối. SaaS tăng trưởng không thắng nhờ backend “ngầu”. Thắng nhờ backend dễ hiểu, dễ sửa, dễ mở rộng, ít khóa chặt.