Thay Supabase bằng Appwrite: Auth, Database, Storage từ A-Z

16/05/2026 · P T P · Chung

Thay thế Supabase bằng Appwrite: hướng dẫn triển khai auth, database, file storage

Supabase nổi tiếng vì trải nghiệm “Firebase nhưng dùng PostgreSQL”: auth nhanh, database mạnh, storage tiện. Nhưng không phải dự án nào cũng hợp Supabase. Nếu bạn muốn self-host dễ hơn, API backend dạng service rõ ràng, console trực quan, phân quyền tài nguyên chi tiết, Appwrite là lựa chọn đáng cân nhắc.

Appwrite không phải bản sao Supabase. Khác biệt lớn nhất: Supabase xoay quanh PostgreSQL + SQL + Row Level Security, còn Appwrite dùng mô hình Collections/Documents, permissions theo tài nguyên, SDK đa nền tảng. Vì vậy, thay thế Supabase bằng Appwrite không chỉ là đổi SDK. Bạn cần đổi tư duy thiết kế auth, database, file storage.

Bài viết này hướng dẫn triển khai thực tế 3 phần cốt lõi: Auth, Database, File Storage.


1. Khi nào nên chuyển từ Supabase sang Appwrite?

Appwrite phù hợp nếu bạn cần:

Self-host đơn giản qua Docker.
Backend-as-a-service đầy đủ: Auth, DB, Storage, Functions, Realtime.
Permission model trực tiếp theo user/team/document/file.
SDK đồng nhất cho Web, Mobile, Server.
Console dễ dùng cho team nhỏ, startup, sản phẩm MVP.

Nên cân nhắc kỹ nếu dự án phụ thuộc nặng vào:

– SQL phức tạp.
– PostgreSQL extensions.
– Stored procedures.
– Row Level Security tinh vi.
– Query join nhiều bảng.

Appwrite không thay thế PostgreSQL theo kiểu 1:1. Nó hợp hơn với mô hình document, API-first, client SDK-first.


2. Cài đặt Appwrite

Chạy Appwrite bằng Docker

Cách nhanh nhất: dùng lệnh cài chính thức.

docker run -it --rm 
  --volume /var/run/docker.sock:/var/run/docker.sock 
  --volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw 
  --entrypoint="install" 
  appwrite/appwrite:latest

Sau khi cài xong, mở console:

http://localhost

Tạo:

– Project.
– Platform Web.
– API endpoint.
– Project ID.

Trong frontend, cài SDK:

npm install appwrite

Tạo client:

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

export const client = new Client() .setEndpoint("http://localhost/v1") .setProject("YOUR_PROJECT_ID");

export const account = new Account(client); export const databases = new Databases(client); export const storage = new Storage(client); export { ID };

Với production, dùng HTTPS + domain thật:

.setEndpoint("https://api.example.com/v1")

3. Auth: thay Supabase Auth bằng Appwrite Account

Supabase thường dùng:

supabase.auth.signUp()
supabase.auth.signInWithPassword()
supabase.auth.getUser()
supabase.auth.signOut()

Trong Appwrite, phần tương đương nằm ở Account.

Đăng ký tài khoản

import { account, ID } from "./appwrite";

export async function register(email, password, name) { return await account.create(ID.unique(), email, password, name); }

Sau khi tạo user, Appwrite chưa tự login. Bạn cần tạo session:

export async function login(email, password) {
  return await account.createEmailPasswordSession(email, password);
}

Flow thường dùng:

await register(email, password, name);
await login(email, password);

Đăng nhập

export async function login(email, password) {
  return await account.createEmailPasswordSession(email, password);
}

Lấy user hiện tại

export async function getCurrentUser() {
  try {
    return await account.get();
  } catch {
    return null;
  }
}

Đăng xuất

export async function logout() {
  return await account.deleteSession("current");
}

OAuth login

Appwrite hỗ trợ OAuth2 với Google, GitHub, Facebook…

account.createOAuth2Session(
  "google",
  "http://localhost:3000/dashboard",
  "http://localhost:3000/login"
);

Trong production, đổi URL callback đúng domain.

Email verification

await account.createVerification("https://example.com/verify");

Xác nhận:

await account.updateVerification(userId, secret);

Khác biệt quan trọng với Supabase Auth

Supabase gắn auth chặt với PostgreSQL RLS. Appwrite dùng permissions. Nghĩa là: user đăng nhập chỉ là một phần. Bạn vẫn phải cấp quyền đọc/ghi cho document/file.

Ví dụ permission theo user:

import { Permission, Role } from "appwrite";

[ Permission.read(Role.user(userId)), Permission.update(Role.user(userId)), Permission.delete(Role.user(userId)) ]


4. Database: từ PostgreSQL sang Appwrite Database

Trong Supabase, bạn tạo bảng SQL:

create table notes (
  id uuid primary key,
  user_id uuid references auth.users,
  title text,
  content text
);

Trong Appwrite, bạn tạo:

– Database.
– Collection.
– Attributes.
– Indexes.
– Permissions.

Ví dụ cấu trúc:

Database: app
Collection: notes
Attributes:
- title: string
- content: string
- userId: string
- createdAt: datetime

Tạo document

import { databases, ID } from "./appwrite";
import { Permission, Role } from "appwrite";

const DATABASE_ID = "app"; const NOTES_COLLECTION_ID = "notes";

export async function createNote(userId, title, content) { return await databases.createDocument( DATABASE_ID, NOTES_COLLECTION_ID, ID.unique(), { userId, title, content, createdAt: new Date().toISOString() }, [ Permission.read(Role.user(userId)), Permission.update(Role.user(userId)), Permission.delete(Role.user(userId)) ] ); }

Điểm mạnh: mỗi document có permission riêng. Điểm yếu: bạn phải thiết kế permissions cẩn thận.

Đọc danh sách document

import { Query } from "appwrite";

export async function listNotes(userId) { return await databases.listDocuments( DATABASE_ID, NOTES_COLLECTION_ID, [ Query.equal("userId", userId), Query.orderDesc("createdAt") ] ); }

Lưu ý: field dùng query cần index tương ứng trong Console.

Cập nhật document

export async function updateNote(noteId, data) {
  return await databases.updateDocument(
    DATABASE_ID,
    NOTES_COLLECTION_ID,
    noteId,
    data
  );
}

Xóa document

export async function deleteNote(noteId) {
  return await databases.deleteDocument(
    DATABASE_ID,
    NOTES_COLLECTION_ID,
    noteId
  );
}

Mapping tư duy Supabase → Appwrite

| Supabase | Appwrite |
|—|—|
| Table | Collection |
| Row | Document |
| Column | Attribute |
| Policy/RLS | Permissions |
| SQL query | SDK query |
| Storage bucket | Storage bucket |
| Edge Functions | Functions |

Lưu ý về quan hệ dữ liệu

Appwrite hỗ trợ relationship attributes, nhưng nếu ứng dụng cần join phức tạp, hãy cân nhắc kỹ. Với mô hình phổ biến như user → notes, user → posts, post → comments, Appwrite xử lý tốt. Với dashboard analytics, reporting, truy vấn tổng hợp sâu, PostgreSQL vẫn thuận hơn.


5. File Storage: thay Supabase Storage bằng Appwrite Storage

Supabase Storage dùng bucket + policy. Appwrite cũng dùng bucket, nhưng quyền nằm ở bucket/file permissions.

Tạo bucket

Trong Console:

Storage → Create bucket
Bucket ID: avatars
Allowed extensions: jpg,png,webp
Maximum file size: tùy nhu cầu
Permissions: tùy mô hình

Nếu avatar riêng tư theo user, cấp quyền từng file. Nếu ảnh public, bucket/file có quyền đọc public.

Upload file

import { storage, ID } from "./appwrite";
import { Permission, Role } from "appwrite";

const AVATAR_BUCKET_ID = "avatars";

export async function uploadAvatar(userId, file) { return await storage.createFile( AVATAR_BUCKET_ID, ID.unique(), file, [ Permission.read(Role.any()), Permission.update(Role.user(userId)), Permission.delete(Role.user(userId)) ] ); }

Ở đây avatar cho phép public read, owner được update/delete.

Lấy URL xem file

export function getAvatarUrl(fileId) {
  return storage.getFileView(AVATAR_BUCKET_ID, fileId).href;
}

Lấy preview ảnh

export function getAvatarPreview(fileId) {
  return storage.getFilePreview(
    AVATAR_BUCKET_ID,
    fileId,
    300,
    300
  ).href;
}

Xóa file

export async function deleteAvatar(fileId) {
  return await storage.deleteFile(AVATAR_BUCKET_ID, fileId);
}

Lưu metadata file vào database

Thực tế nên lưu fileId vào document user/profile.

await databases.updateDocument(
  DATABASE_ID,
  "profiles",
  profileId,
  {
    avatarFileId: uploaded.$id
  }
);

Như vậy UI chỉ cần gọi:

getAvatarPreview(profile.avatarFileId)

6. Thiết kế permissions an toàn

Đây là phần dễ sai nhất khi chuyển từ Supabase.

Không nên

– Cho Role.any() quyền write.
– Tin userId từ client mà không kiểm tra.
– Dùng collection public nếu chứa dữ liệu riêng tư.
– Bỏ index rồi query tùy tiện.
– Đưa API key server vào frontend.

Nên

– Dùng account.get() lấy user hiện tại.
– Cấp permission theo Role.user(user.$id).
– Public read chỉ cho dữ liệu thật sự public.
– Server-side Function xử lý logic nhạy cảm.
– Tách collection public/private.
– Review quyền bucket/file trước production.

Ví dụ document riêng tư:

[
  Permission.read(Role.user(userId)),
  Permission.update(Role.user(userId)),
  Permission.delete(Role.user(userId))
]

Ví dụ bài viết public:

[
  Permission.read(Role.any()),
  Permission.update(Role.user(authorId)),
  Permission.delete(Role.user(authorId))
]

7. Migration từ Supabase sang Appwrite

Quy trình thực tế:

Bước 1: Audit schema Supabase

Liệt kê:

– Tables.
– Columns.
– Foreign keys.
– RLS policies.
– Storage buckets.
– Auth providers.
– Edge functions.

Bước 2: Thiết kế lại collection

Không bê nguyên SQL sang Appwrite. Hãy hỏi:

– Entity nào thành collection?
– Field nào cần query?
– Field nào cần index?
– Document nào private/public?
– Có cần relationship không?

Bước 3: Export dữ liệu

Với Supabase PostgreSQL:

pg_dump

Hoặc export CSV từ dashboard.

Bước 4: Import bằng server script

Dùng Appwrite Server SDK + API key. Không import bằng frontend.

Pseudo-flow:

Read CSV/JSON → transform → createDocument → set permissions

Bước 5: Chuyển storage

Tải file từ Supabase Storage, upload sang Appwrite Storage, lưu mapping:

old_path → new_file_id

Sau đó update document liên quan.

Bước 6: Chuyển auth

Phần khó nhất. Mật khẩu hash thường không thể migrate trực tiếp giữa hệ thống. Cách thực tế:

– Cho user reset password.
– Hoặc dùng custom migration flow.
– Hoặc giữ Supabase Auth tạm thời trong giai đoạn chuyển tiếp.


8. Ví dụ flow hoàn chỉnh: app ghi chú cá nhân

Luồng:

1. User đăng ký/đăng nhập.
2. App gọi account.get().
3. User tạo note.
4. Note được lưu với permission theo user.
5. User chỉ query được note của mình.
6. User upload file đính kèm nếu cần.
7. File cũng có permission theo user.

Điểm mấu chốt:

const user = await account.get();

await createNote(user.$id, "Tiêu đề", "Nội dung");

Không dùng userId từ input form. Lấy từ session hiện tại.


Kết luận

Appwrite là lựa chọn mạnh nếu bạn muốn thay Supabase bằng một backend service dễ self-host, SDK rõ ràng, auth/database/storage tích hợp tốt. Nhưng đừng kỳ vọng chuyển đổi 1:1. Supabase mạnh ở SQL và PostgreSQL; Appwrite mạnh ở API, document model, permissions trực tiếp, trải nghiệm backend-as-a-service gọn.

Nếu app của bạn là SaaS nhỏ, mobile app, MVP, dashboard nội bộ, nền tảng nội dung, app cộng đồng: Appwrite rất đáng thử. Nếu app cần truy vấn SQL phức tạp, reporting nặng, transaction sâu: nên giữ Supabase/PostgreSQL hoặc kết hợp thêm database chuyên dụng.

Cách tiếp cận tốt nhất: migrate từng phần. Bắt đầu với Auth + Storage, sau đó chuyển các module database đơn giản, cuối cùng mới xử lý nghiệp vụ phức tạp. Làm chậm nhưng chắc → ít lỗi quyền, ít mất dữ liệu, ít downtime.

#appwrite #auth #bang #supabase #thay
Chia sẻ:
← Trước
Top thay thế Supabase giúp startup scale nhanh năm 2026

Bài viết tương tự

Bình luận

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