Dùng Appwrite thay Supabase: Dựng backend full-stack từ A-Z

23/05/2026 · P T P · Chung

Dùng Appwrite thay Supabase: hướng dẫn dựng backend full-stack từ đầu

Supabase nổi mạnh vì đưa PostgreSQL, Auth, Storage, Realtime vào một bộ dễ dùng. Nhưng không phải dự án nào cũng cần SQL, row-level security, hoặc hệ sinh thái Postgres. Nếu muốn backend tự host, API rõ, tích hợp nhanh với web/mobile, có Auth, Database, Storage, Functions, Realtime trong một nơi, Appwrite là lựa chọn đáng cân nhắc.

Appwrite hợp với đội muốn dựng backend full-stack nhanh, kiểm soát hạ tầng, dùng document database, ít cấu hình SQL phức tạp. Bài này hướng dẫn dựng backend từ đầu: cài Appwrite, tạo project, cấu hình Auth, Database, Storage, Function, rồi kết nối frontend.

Appwrite khác Supabase ở đâu?

Mô hình dữ liệu

Supabase dùng PostgreSQL. Mạnh khi dữ liệu quan hệ nhiều, query phức tạp, cần transaction sâu, reporting, analytics.

Appwrite dùng Database dạng collection/document. Gần MongoDB hơn. Mỗi collection có attributes, indexes, permissions. Hợp app CRUD, dashboard, SaaS nhỏ, mobile app, prototype, nội dung user tạo.

Ví dụ:

– Supabase: users, posts, comments nối bằng foreign key.
– Appwrite: collection posts, document chứa userId, title, content, status.

Appwrite vẫn query được theo field, sort, paginate, filter. Nhưng không có join SQL. Muốn dữ liệu liên kết, cần thiết kế denormalized hoặc gọi nhiều query.

Quyền truy cập

Supabase mạnh với Row Level Security. Appwrite dùng permissions theo document/collection.

Ví dụ document chỉ user sở hữu được đọc/sửa:

Permission.read(Role.user(userId))
Permission.update(Role.user(userId))
Permission.delete(Role.user(userId))

Cách này trực quan, dễ hiểu với app nhỏ-vừa. Nhưng nếu policy phức tạp theo nhiều điều kiện, Supabase RLS linh hoạt hơn.

Self-hosting

Appwrite thiết kế self-host tốt qua Docker. Có cloud riêng, nhưng bản self-host dễ dựng.

Supabase cũng self-host được, nhưng stack nặng hơn: Postgres, Kong, GoTrue, Realtime, Storage, Studio, v.v.

Nếu mục tiêu: “chạy một backend đầy đủ trên VPS nhỏ”, Appwrite thường nhẹ đầu hơn.

Kiến trúc full-stack với Appwrite

Một app Appwrite phổ biến gồm:

Frontend: Next.js, React, Vue, Svelte, Flutter, React Native.
Appwrite API: Auth, Databases, Storage, Functions.
Appwrite Console: quản trị project.
Functions: chạy logic server-side như gửi email, xử lý payment, webhook.
Realtime: lắng nghe thay đổi document, session, file.

Luồng cơ bản:

1. User đăng ký/đăng nhập qua Auth.
2. Frontend gọi Database API để tạo/read document.
3. Permission kiểm soát user nào đọc/sửa được gì.
4. File upload vào Storage bucket.
5. Function xử lý logic nhạy cảm, dùng API key server-side.

Cài Appwrite bằng Docker

Cần Docker và Docker Compose. Trên VPS Linux, chạy 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

Installer hỏi domain, port, SSL, SMTP. Với local dev, có thể dùng localhost. Sau khi chạy xong, mở:

http://localhost

Tạo tài khoản admin trong Console.

Nếu deploy production, cần domain thật, reverse proxy/SSL đúng, SMTP đúng, backup volume định kỳ.

Tạo project mới

Trong Appwrite Console:

1. Vào Projects.
2. Chọn Create project.
3. Đặt tên, ví dụ blog-platform.
4. Ghi lại Project ID.

Tiếp theo, thêm platform:

– Web app: nhập domain frontend, ví dụ http://localhost:3000.
– Production: thêm domain thật, ví dụ https://app.example.com.

Nếu thiếu platform hoặc sai domain, request từ frontend bị chặn do CORS/origin.

Cấu hình Auth

Vào Auth > Settings. Bật phương thức cần dùng:

– Email/password.
– OAuth2: Google, GitHub, Facebook.
– Magic URL.
– Phone nếu có nhu cầu.

Ví dụ dùng email/password trong frontend:

npm install appwrite

Tạo client:

// src/lib/appwrite.ts
import { Client, Account, Databases, Storage, ID } from "appwrite";

export const client = new Client() .setEndpoint("https://YOUR_APPWRITE_ENDPOINT/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 };

Đăng ký:

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();

Đăng xuất:

await account.deleteSession("current");

Điểm cần nhớ: frontend SDK chạy với quyền user hiện tại. Không đặt API key trong frontend.

Thiết kế Database

Vào Databases > Create database. Ví dụ tạo database:

main

Tạo collection posts với attributes:

title: string, required.
slug: string, required.
content: string, required.
status: enum/string, ví dụ draft, published.
authorId: string, required.
createdAt: datetime.
updatedAt: datetime.

Tạo indexes:

slug unique nếu mỗi bài cần slug riêng.
authorId để query bài theo user.
status để lọc bài published.
createdAt để sort danh sách.

Permissions collection nên để chặt. Ví dụ:

– User authenticated được create.
– Document owner được read/update/delete.
– Public chỉ read bài published? Appwrite permission không tự kiểm tra status, nên với public read published, nên dùng Function hoặc tách collection.

Cách đơn giản:

– Draft/private: permission riêng user.
– Published: khi publish, thêm Permission.read(Role.any()).

Tạo bài từ frontend:

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

const doc = await databases.createDocument( "main", "posts", ID.unique(), { title, slug, content, status: "draft", authorId: user.$id, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }, [ Permission.read(Role.user(user.$id)), Permission.update(Role.user(user.$id)), Permission.delete(Role.user(user.$id)), ] );

Query danh sách bài của user:

import { Query } from "appwrite";

const posts = await databases.listDocuments("main", "posts", [ Query.equal("authorId", user.$id), Query.orderDesc("createdAt"), Query.limit(20), ]);

Storage cho ảnh và file

Vào Storage > Create bucket. Ví dụ bucket:

post-images

Cấu hình:

– Max file size phù hợp.
– Allowed extensions: jpg, png, webp.
– Permissions: user đăng nhập được tạo file; owner được update/delete.

Upload ảnh:

const file = await storage.createFile(
  "post-images",
  ID.unique(),
  selectedFile
);

Lấy preview URL:

const url = storage.getFilePreview("post-images", file.$id);

Nếu ảnh public, cấp Permission.read(Role.any()). Nếu ảnh riêng tư, giữ permission theo user và tải qua SDK sau khi đăng nhập.

Functions cho logic server-side

Không đưa logic nhạy cảm vào frontend. Ví dụ:

– Tạo Stripe checkout.
– Gửi email.
– Xử lý webhook.
– Publish bài kèm kiểm tra quyền.
– Đồng bộ search index.

Vào Functions > Create function. Chọn runtime Node.js. Function có thể dùng API key qua biến môi trường.

Ví dụ function publish bài:

import { Client, Databases } from "node-appwrite";

export default async ({ req, res, log, error }) => { const client = new Client() .setEndpoint(process.env.APPWRITE_ENDPOINT) .setProject(process.env.APPWRITE_PROJECT_ID) .setKey(process.env.APPWRITE_API_KEY);

const databases = new Databases(client);

const { postId } = JSON.parse(req.body || "{}");

const post = await databases.getDocument("main", "posts", postId);

await databases.updateDocument("main", "posts", postId, { status: "published", updatedAt: new Date().toISOString(), });

return res.json({ ok: true, postId }); };

Trong thực tế, cần kiểm tra user gọi function có phải author không. Có thể đọc JWT/session từ request hoặc thiết kế endpoint dùng Appwrite execution context.

Realtime cho trải nghiệm sống

Appwrite Realtime giúp frontend nghe thay đổi database:

const unsubscribe = client.subscribe(
  databases.main.collections.posts.documents,
  (response) => {
    console.log(response.events, response.payload);
  }
);

// khi unmount unsubscribe();

Dùng cho:

– Dashboard cập nhật live.
– Chat nhỏ.
– Notification.
– Trạng thái upload/xử lý.

Không lạm dụng Realtime cho mọi thứ. Với danh sách lớn, vẫn nên dùng pagination + refresh có kiểm soát.

Deploy frontend

Nếu dùng Next.js/Vite, đặt biến môi trường:

NEXT_PUBLIC_APPWRITE_ENDPOINT=https://api.example.com/v1
NEXT_PUBLIC_APPWRITE_PROJECT_ID=your_project_id

Chỉ biến public không chứa secret. API key chỉ dùng trong server route hoặc Appwrite Function.

Checklist deploy:

– Domain frontend đã thêm vào Appwrite platform.
– HTTPS hoạt động.
– SMTP cấu hình nếu dùng email verification/password reset.
– Backup volume Appwrite.
– Rate limit/WAF nếu public lớn.
– Logs và monitoring có chỗ xem.

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

Chọn Appwrite nếu:

– Muốn backend full-stack dễ self-host.
– App CRUD/document nhiều hơn relational query.
– Team thích permission rõ theo document.
– Cần Auth, Storage, Functions, Realtime trong một console.
– Muốn dùng chung cho web và mobile.

Giữ Supabase nếu:

– Cần PostgreSQL mạnh.
– Query nhiều join, aggregate, report.
– Cần RLS phức tạp.
– Dữ liệu quan hệ là lõi sản phẩm.
– Team đã quen SQL sâu.

Kết luận thực tế

Appwrite không phải “Supabase nhưng khác màu”. Nó là hướng backend khác: document-first, permission-first, self-host thân thiện. Với SaaS nhỏ, app nội bộ, mobile app, CMS tùy biến, MVP cần ra nhanh, Appwrite giúp giảm nhiều việc lặp: Auth, Database, Storage, Realtime, Functions.

Cách dựng tốt: bắt đầu nhỏ, thiết kế collection rõ, tạo index từ sớm, giữ permission chặt, đẩy logic nhạy cảm vào Functions, không để API key ra frontend. Nếu sau này dữ liệu cần quan hệ sâu hoặc analytics nặng, có thể ghép thêm Postgres/service riêng.

Appwrite thay Supabase tốt khi bài toán hợp mô hình. Chọn theo dữ liệu, quyền truy cập, vận hành. Không chọn theo độ hot.

#appwrite #backend #dung #supabase #thay
Chia sẻ:
← Trước
Supabase quá đắt? Cách giảm chi phí backend khi app tăng trưởng

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!