Thay Supabase bằng Appwrite: Auth, DB, Storage cho SaaS nhỏ

P P T P Chung

Thay thế Supabase bằng Appwrite: xây auth, database, storage cho SaaS nhỏ

Supabase rất mạnh: Postgres, Auth, Storage, Realtime, Edge Functions. Nhưng không phải SaaS nhỏ nào cũng cần full Postgres stack, SQL phức tạp, RLS sâu, trigger, extension. Nếu bạn muốn backend-as-a-service dễ tự host, API rõ, dashboard thân thiện, auth/storage/database đủ dùng, Appwrite là lựa chọn đáng cân nhắc.

Appwrite không phải “Supabase clone”. Nó đi hướng khác: document database, collection-based permission, SDK-first, self-host mạnh, built-in auth/storage/functions. Với SaaS nhỏ — ví dụ CRM mini, tool quản lý booking, dashboard nội bộ, ứng dụng subscription đơn giản — Appwrite có thể giảm đáng kể thời gian backend.

Bài này đi thẳng: khi nào nên thay Supabase bằng Appwrite, cách thiết kế auth, database, storage, điểm khác biệt quan trọng, lỗi cần tránh.


Appwrite hợp với SaaS nhỏ kiểu nào?

Appwrite hợp nếu app của bạn:

– Cần đăng ký/đăng nhập nhanh: email/password, OAuth, magic URL, anonymous user. – Dữ liệu dạng document: project, task, invoice, file metadata, customer record. – Quan hệ dữ liệu không quá phức tạp. – Muốn permission theo user/team dễ cấu hình. – Cần storage file: avatar, PDF, attachment, image. – Muốn tự host bằng Docker. – Muốn SDK frontend/backend rõ ràng.

Không hợp nếu:

– App phụ thuộc SQL phức tạp. – Cần join nhiều bảng thường xuyên. – Cần transaction sâu. – Cần analytics/reporting nặng. – Đang dùng Postgres-specific feature: function, trigger, materialized view, extension.

Tóm lại: CRUD SaaS nhỏ → Appwrite ổn. Data platform phức tạp → giữ Supabase/Postgres.


Supabase vs Appwrite: khác biệt cốt lõi

Database

Supabase dùng Postgres. Bạn có bảng, SQL, foreign key, join, RLS.

Appwrite dùng Database/Collection/Document. Mô hình gần NoSQL hơn:

– Database → nhóm logic. – Collection → tương đương bảng. – Document → tương đương record JSON. – Attribute → schema field. – Index → tối ưu query.

Ví dụ SaaS quản lý task:

workspacesprojectstaskscommentssubscriptions

Trong Supabase, bạn join tasks với projects, users, comments. Trong Appwrite, bạn thường lưu thêm field như:

{
  "title": "Thiết kế landing page",
  "projectId": "project_123",
  "workspaceId": "workspace_456",
  "assigneeId": "user_789",
  "status": "todo"
}

Query theo workspaceId, projectId, status.

Điểm chính: Appwrite khuyến khích denormalize vừa đủ.


Auth: từ Supabase Auth sang Appwrite Account

Supabase Auth thường xoay quanh user JWT, session, RLS. Appwrite có module Account.

Các flow phổ biến:

– Email/password. – OAuth2: Google, GitHub, Apple. – Magic URL. – Email verification. – Password recovery. – Anonymous session.

Frontend ví dụ:

import { Client, Account, ID } from "appwrite";

const client = new Client() .setEndpoint("https://cloud.appwrite.io/v1") .setProject("PROJECT_ID");

const account = new Account(client);

await account.create(ID.unique(), email, password, name); await account.createEmailPasswordSession(email, password);

Lấy user hiện tại:

const user = await account.get();

Logout:

await account.deleteSession("current");

Thiết kế user profile

Appwrite Account lưu thông tin cơ bản. Với SaaS, nên tạo thêm collection profiles:

{
  "userId": "abc",
  "name": "Linh",
  "avatarFileId": "file_123",
  "defaultWorkspaceId": "ws_456",
  "role": "owner"
}

Sau signup, tạo profile bằng server function hoặc client có permission phù hợp.

Team/workspace auth

SaaS thường không chỉ có user, mà có workspace/team. Appwrite có Teams built-in. Bạn có thể dùng:

– Team = workspace. – Membership = user trong workspace. – Role = owner/admin/member.

Hoặc tự quản lý collection workspace_members.

Cách thực tế cho SaaS nhỏ:

– Nếu cần invite, membership, role cơ bản → dùng Appwrite Teams. – Nếu logic billing/role phức tạp → tự tạo workspace_members.


Permission: thay RLS bằng document permission

Supabase mạnh nhờ RLS. Appwrite dùng permission ở collection/document level.

Ví dụ document chỉ owner đọc/sửa:

import { Permission, Role } from "appwrite";

await databases.createDocument( databaseId, collectionId, ID.unique(), data, [ Permission.read(Role.user(userId)), Permission.update(Role.user(userId)), Permission.delete(Role.user(userId)) ] );

Workspace document cho team đọc:

[
  Permission.read(Role.team(teamId)),
  Permission.update(Role.team(teamId, "admin"))
]

Điểm cần nhớ:

– Permission là phần thiết kế chính, không phải phụ. – Không nên mở collection public trừ khi thật sự cần. – Server SDK dùng API key có thể bypass permission → dùng cẩn thận. – Client SDK chỉ được quyền theo session user.

Mẫu an toàn:

– Client đọc/ghi dữ liệu user-owned đơn giản. – Server Function xử lý nghiệp vụ nhạy cảm: billing, invite, role change, admin update. – Mọi document quan trọng có workspaceId, createdBy, permission rõ.


Database design cho SaaS nhỏ

Ví dụ SaaS quản lý dự án.

Collection: workspaces

Fields:

name: string – slug: string – ownerId: string – plan: enum/free/pro/pro_plus – stripeCustomerId: string optional – createdAt: datetime

Index:

slugownerId

Permission:

– Read: team workspace. – Update: owner/admin. – Delete: owner.

Collection: projects

Fields:

workspaceIdnamedescriptionstatuscreatedBy

Index:

workspaceIdstatus

Permission:

– Read: team. – Create/update: team admin/member tùy app.

Collection: tasks

Fields:

workspaceIdprojectIdtitlestatusassigneeIddueDatepriority

Index:

workspaceIdprojectIdstatusassigneeId

Query:

const tasks = await databases.listDocuments(
  databaseId,
  tasksCollectionId,
  [
    Query.equal("workspaceId", workspaceId),
    Query.equal("status", "todo"),
    Query.limit(50)
  ]
);

Nguyên tắc schema

– Luôn có workspaceId nếu app multi-tenant. – Luôn index field dùng filter/sort. – Tránh query kiểu “quét toàn bộ”. – Denormalize field hay hiển thị: assigneeName, projectName nếu cần tốc độ. – Dùng Function để đồng bộ denormalized data nếu phức tạp.


Storage: file upload, avatar, attachment

Appwrite Storage dùng bucket. Mỗi bucket có rule, size limit, allowed extensions, encryption, antivirus tùy cấu hình.

Ví dụ bucket:

avatarsattachmentsinvoicespublic-assets

Upload file:

import { Storage, ID } from "appwrite";

const storage = new Storage(client);

const file = await storage.createFile( "avatars", ID.unique(), selectedFile, [ Permission.read(Role.user(userId)), Permission.update(Role.user(userId)), Permission.delete(Role.user(userId)) ] );

Preview image:

const url = storage.getFilePreview("avatars", file.$id);

Download:

const url = storage.getFileDownload("attachments", fileId);

File metadata

Không nên chỉ lưu file trong Storage. Nên có collection files:

{
  "fileId": "abc",
  "bucketId": "attachments",
  "workspaceId": "ws_123",
  "uploadedBy": "user_456",
  "entityType": "task",
  "entityId": "task_789",
  "name": "brief.pdf",
  "size": 230123
}

Lợi ích:

– Query file theo task/project. – Kiểm tra permission nghiệp vụ. – Dọn file rác. – Hiển thị lịch sử upload.


Functions: nơi đặt logic nhạy cảm

Appwrite Functions giống serverless backend. Dùng cho:

– Tạo workspace sau signup. – Invite member. – Đồng bộ Stripe webhook. – Resize image. – Kiểm tra quota. – Gửi email. – Cron cleanup.

Ví dụ signup flow tốt:

1. User tạo account. 2. Function createInitialWorkspace chạy. 3. Tạo team/workspace. 4. Tạo profile. 5. Set permission. 6. Trả về workspace mặc định.

Không nên để client tự làm tất cả, vì dễ bị bypass logic như plan limit, role, billing state.


Migration từ Supabase sang Appwrite

Auth

Khó nhất là password. Thường không thể export password raw. Cách thực tế:

– Cho user đăng nhập lại bằng magic link/password reset. – Migration dần: user cũ reset password. – OAuth user → map bằng email/provider.

Database

Supabase export CSV/JSON từ Postgres. Sau đó import vào Appwrite bằng Server SDK.

Checklist:

– Map table → collection. – Map row → document. – Tạo attribute trước. – Tạo index trước khi app production. – Chuyển foreign key thành xxxId. – Thêm workspaceId. – Tạo permissions cho từng document.

Storage

– Download từ Supabase bucket. – Upload sang Appwrite bucket. – Lưu mapping old path → new fileId. – Update metadata trong DB.

Realtime

Supabase realtime dựa Postgres changes. Appwrite có Realtime channel theo documents/files/account. Với SaaS nhỏ như task board, notification, live status → đủ dùng.


Lỗi thường gặp

Mở permission quá rộng

Role.any() tiện lúc dev, nguy hiểm lúc prod. Prod cần permission tối thiểu.

Không tạo index

Query Appwrite cần index phù hợp. Thiếu index → query lỗi/chậm. Mỗi filter/sort quan trọng → index.

Thiết kế quá giống SQL

Nếu cố mô phỏng relational phức tạp, Appwrite sẽ khó chịu. Hãy denormalize có kiểm soát.

Đưa business logic nhạy cảm lên client

Client không nên quyết định user được tạo bao nhiêu workspace, có quyền đổi plan, có thể invite ai. Đưa vào Function.


Khi nào nên chọn Appwrite thay Supabase?

Chọn Appwrite nếu bạn muốn:

– Self-host BaaS nhanh. – Auth/storage/database đủ dùng. – Dashboard dễ dùng. – SDK tốt cho web/mobile. – Permission document/team rõ. – SaaS nhỏ, CRUD nhiều, logic vừa phải.

Giữ Supabase nếu bạn cần:

– SQL mạnh. – Reporting phức tạp. – RLS tinh vi. – Transaction/foreign key nghiêm ngặt. – Postgres ecosystem.


Kết luận thực tế

Appwrite không “tốt hơn” Supabase trong mọi trường hợp. Nó tốt hơn khi bài toán của bạn là xây SaaS nhỏ nhanh, ít SQL phức tạp, cần auth + database + storage + permission rõ ràng.

Cách triển khai khuyến nghị:

– Auth bằng Account. – Multi-tenant bằng Teams hoặc workspace_members. – Database theo collection/document. – Mỗi document có workspaceId. – Permission theo user/team. – Storage tách bucket, metadata lưu trong DB. – Logic nhạy cảm đặt trong Functions. – Index từ đầu.

Nếu SaaS của bạn là project tracker, booking tool, client portal, mini CRM, internal dashboard — Appwrite có thể thay Supabase rất ổn. Nếu bạn đang xây data-heavy SaaS với SQL/report phức tạp, Supabase vẫn là nền tảng hợp lý hơn.

Tác giả

P T P

Chia sẻ

Bài viết liên quan

Bình luận (0)

Email của bạn sẽ không được hiển thị công khai.

Chưa có bình luận. Hãy là người đầu tiên!