Thế giới phát triển web liên tục biến đổi với tốc độ chóng mặt. Các bộ khung, thư viện và nền tảng mới xuất hiện định kỳ, mang đến những cách tiếp cận sáng tạo để xây dựng ứng dụng. Trong bối cảnh đó, việc lựa chọn “bộ stack” phù hợp có thể tạo ra sự khác biệt lớn về hiệu quả và trải nghiệm phát triển.
Gần đây, chúng ta đã thấy sự xuất hiện của những bộ stack được “chủ xị” bởi các nhân vật nổi tiếng trong cộng đồng, như T3 Stack của Theo. Và một cái tên mới hơn, đang dần thu hút sự chú ý, là JStack, do Josh – Trưởng bộ phận Devrel tại Upstash – khởi xướng.
JStack mang đến một cách tiếp cận mới mẻ, kết hợp những công cụ mạnh mẽ như Next.js, Bun, Hono, TanStack Query và Zod. Nhưng liệu JStack có thực sự là một lựa chọn tối ưu cho dự án của bạn? Quan trọng hơn, nó có tương thích tốt với Appwrite – một nền tảng Backend-as-a-Service (BaaS) mã nguồn mở phổ biến – hay không? Và chúng ta có thể triển khai ứng dụng JStack trên Appwrite Sites không?
Hãy cùng đi sâu vào khám phá sự kết hợp đầy tiềm năng giữa JStack và Appwrite trong bài viết này.
Mục lục
Bắt Đầu Với JStack: Khởi Tạo Dự Án
Để bắt đầu hành trình tích hợp, việc đầu tiên là khởi tạo một dự án JStack. Quá trình này được đơn giản hóa nhờ vào CLI (Command Line Interface) của JStack.
Mở terminal hoặc command prompt và chạy lệnh sau:
bunx create-jstack-app@latest
Công cụ khởi tạo sẽ đặt ra một vài câu hỏi để cấu hình dự án của bạn. Đối với mục đích tích hợp với Appwrite, chúng ta sẽ chọn các tùy chọn sau:
┌ jStack CLI
│
◇ What will your project be called?
│ ten-du-an-cua-ban (ví dụ: testing-jstack-appwrite)
│
◇ Which database ORM would you like to use?
│ None (Không)
│
◇ Should we run 'bun install' for you?
│ Yes (Có)
Using: bun
✔ ten-du-an-cua-ban scaffolded successfully!
Lý do chúng ta không chọn ORM (Object-Relational Mapper) ngay từ đầu là vì Appwrite cung cấp cơ chế quản lý schema và tương tác dữ liệu thông qua SDK riêng của nó, giúp giảm thiểu sự phức tạp không cần thiết.
Sau khi khởi tạo thành công, di chuyển vào thư mục dự án và chạy máy chủ phát triển để xem kết quả ban đầu:
cd ten-du-an-cua-ban
bun dev
Ứng dụng sẽ khởi động trên http://localhost:3000/. Bạn sẽ thấy trang chào mừng mặc định của JStack.
Tích Hợp Appwrite: Cấu Hình Ban Đầu
Mặc dù chúng ta đã bỏ qua tùy chọn ORM khi khởi tạo JStack, bộ khung vẫn tạo ra thư mục /src/server
với một ví dụ về router xử lý “bài viết”. Tuy nhiên, nó chỉ mô phỏng cơ sở dữ liệu bằng một mảng không có tính năng lưu trữ liên tục:
// Mocked DB
interface Post {
id: number
name: string
}
const posts: Post[] = [
{
id: 1,
name: "Hello World",
},
]
// ... code sử dụng mảng 'posts'
Nhiệm vụ của chúng ta là thay thế cơ sở dữ liệu giả lập này bằng Appwrite.
Thiết lập Dự án trên Appwrite Cloud
Truy cập https://cloud.appwrite.io/ để thiết lập dự án Appwrite của bạn. Nếu bạn mới sử dụng Appwrite, tài liệu Start with Web docs là một nguồn tài nguyên tuyệt vời.
Các bước cơ bản:
- Tạo một dự án mới.
- Thêm một nền tảng (platform) web và chọn Next.js (vì JStack dựa trên Next.js).
- Truy cập trang tổng quan dự án (Project Overview) để lấy ID dự án (Project ID) và Endpoint dựa trên khu vực của bạn.
Sau khi có ID và Endpoint, tạo một tệp .env
mới tại thư mục gốc của dự án JStack và thêm các biến môi trường sau:
NEXT_PUBLIC_APPWRITE_PROJECT_ID=ID_DU_AN_CUA_BAN
NEXT_PUBLIC_APPWRITE_ENDPOINT=ENDPOINT_CUA_BAN
NEXT_PUBLIC_APP_DOMAIN=localhost # Chúng ta sẽ cập nhật sau khi triển khai
Thay thế ID_DU_AN_CUA_BAN
và ENDPOINT_CUA_BAN
bằng thông tin bạn lấy từ Appwrite Console.
Cài đặt Appwrite SDK
Tiếp theo, cài đặt SDK của Appwrite vào dự án JStack để có thể tương tác với backend Appwrite từ code của bạn:
bun add appwrite
Bước này hoàn tất việc thiết lập Appwrite cơ bản trong dự án Next.js/JStack của bạn.
Xây Dựng Cấu Trúc Dữ Liệu (Schema) Trong Appwrite
Giờ là lúc cấu hình cơ sở dữ liệu Appwrite để lưu trữ dữ liệu. Chúng ta sẽ tạo một bộ sưu tập (collection) Posts
để thay thế mảng giả lập trong ví dụ của JStack.
- Trong Appwrite Console, điều hướng đến Databases > Create Database. Đặt tên là
main
và giữ nguyên ID làmain
(hoặc chọn ID tùy ý nhưng cần ghi nhớ). - Trong database
main
vừa tạo, tạo một Collection mới. Đặt tên làposts
và giữ nguyên ID làposts
. -
Trong collection
posts
, thêm thuộc tính (attribute). Mỗi tài liệu (document) trong Appwrite đã có sẵn một ID duy nhất được tạo tự động. Với ví dụ này, chúng ta chỉ cần thêm một thuộc tính làname
để lưu nội dung bài viết.- Chọn loại thuộc tính: String
- Đặt tên (Key):
name
- Chọn kích thước (Size): Ví dụ 255 ký tự
- Thiết lập là Bắt buộc (Required): Yes
-
Thiết lập Quyền truy cập (Permissions) cho collection này. Để đơn giản cho ví dụ, chúng ta sẽ cho phép bất kỳ ai (
any
) có quyền đọc và tạo tài liệu. Tuy nhiên, trong các ứng dụng thực tế, bạn cần cấu hình quyền truy cập chi tiết hơn dựa trên vai trò người dùng hoặc Team.Tìm hiểu thêm về Permissions tại tài liệu Appwrite Permissions.
Với các bước trên, chúng ta đã định nghĩa cấu trúc dữ liệu Posts
trong backend Appwrite.
Đồng Bộ Kiểu Dữ Liệu Với Appwrite CLI
Một tính năng hữu ích của Appwrite là khả năng tạo các kiểu dữ liệu TypeScript dựa trên schema bạn đã định nghĩa trong Console. Điều này giúp đảm bảo an toàn kiểu dữ liệu (type-safety) khi làm việc với dữ liệu từ backend.
Để làm được điều này, bạn cần cài đặt Appwrite CLI và liên kết nó với dự án của bạn.
Sau khi cài đặt CLI, chạy lệnh sau trong thư mục gốc của dự án JStack:
appwrite init project
Chọn tùy chọn “Link directory to an existing project” và làm theo hướng dẫn để chọn tổ chức và dự án Appwrite bạn vừa thiết lập. Khi được hỏi “Would you like to pull all resources from project you just linked?”, chọn Yes.
Tiếp theo, sử dụng tính năng tạo kiểu (Types Generation):
appwrite types src/types
CLI sẽ phát hiện ngôn ngữ (ts), tạo thư mục src/types
nếu chưa có, và tạo tệp appwrite.d.ts
chứa các kiểu dữ liệu. Kết quả sẽ tương tự như sau:
import { type Models } from 'appwrite';
/**
* This file is auto-generated by the Appwrite CLI.
* You can regenerate it by running `appwrite types -l ts src/types`.
*/
export type Posts = Models.Document & {
name: string;
}
Đây là một tính năng vô cùng hữu ích, đặc biệt khi dự án của bạn phát triển với nhiều database và collection hơn.
Kết Nối Backend Appwrite Với JStack Code
Bây giờ là lúc điều chỉnh code của JStack để sử dụng Appwrite thay vì dữ liệu giả lập.
Tạo một tệp mới appwrite.ts
trong thư mục src/lib
:
import { Client, Databases, ID } from "appwrite";
const client = new Client()
.setEndpoint(process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT!)
.setProject(process.env.NEXT_PUBLIC_APPWRITE_PROJECT_ID!);
const databases = new Databases(client);
export { client, databases };
Tệp này khởi tạo Client và Databases object của Appwrite SDK, sử dụng các biến môi trường chúng ta đã thiết lập trong tệp .env
. Chúng ta export chúng để sử dụng ở các phần khác của ứng dụng.
Tiếp theo, chỉnh sửa tệp src/server/post-router.ts
để sử dụng đối tượng databases
này và tương tác với collection posts
trên Appwrite.
Thay thế nội dung hiện tại bằng code sau:
import { z } from "zod";
import { j, publicProcedure } from "../jstack";
import { databases } from "@/lib/appwrite"; // Import từ tệp vừa tạo
import { type Posts } from "@/types/appwrite"; // Import kiểu dữ liệu từ Appwrite CLI
import { ID } from "appwrite";
// Định nghĩa ID của Database và Collection
const DATABASE_ID = "main"; // Sử dụng ID database bạn đã tạo
const POSTS_COLLECTION_ID = "posts"; // Sử dụng ID collection bạn đã tạo
export const postRouter = j.router({
// Procedure để lấy bài viết gần nhất
recent: publicProcedure.query(async ({ c }) => {
// Sử dụng databases.listDocuments để lấy dữ liệu từ Appwrite
const posts = await databases.listDocuments<Posts>(
DATABASE_ID,
POSTS_COLLECTION_ID,
// Thêm Query để sắp xếp theo thời gian tạo giảm dần và giới hạn 1 bản ghi nếu cần
// Ví dụ: [Query.orderDesc('$createdAt'), Query.limit(1)]
);
// Lấy bản ghi cuối cùng trong danh sách (có thể cần điều chỉnh dựa trên logic sắp xếp)
return c.superjson(posts.documents.at(-1) ?? null);
}),
// Procedure để tạo bài viết mới
create: publicProcedure
.input(z.object({ name: z.string().min(1) })) // Sử dụng Zod để validate input
.mutation(async ({ c, input }) => {
// Sử dụng databases.createDocument để tạo tài liệu mới trong Appwrite
const post = await databases.createDocument<Posts>(
DATABASE_ID,
POSTS_COLLECTION_ID,
ID.unique(), // Sử dụng ID.unique() để Appwrite tạo ID duy nhất
{
name: input.name, // Dữ liệu bài viết
},
);
return c.superjson(post);
}),
});
Trong code này, chúng ta đã thay thế mảng giả lập posts
bằng các lệnh gọi đến databases.listDocuments
và databases.createDocument
của Appwrite SDK. ID của database và collection được định nghĩa dưới dạng hằng số để dễ quản lý. Kiểu dữ liệu Posts
được import từ tệp appwrite.d.ts
đã tạo trước đó, mang lại lợi ích về type-safety.
Giờ đây, ứng dụng JStack của bạn đã được kết nối và sử dụng Appwrite làm backend.
Kiểm Tra Hoạt Động
Sau khi cấu hình code, đã đến lúc kiểm tra xem mọi thứ có hoạt động như mong đợi không.
- Đảm bảo máy chủ phát triển JStack đang chạy bằng lệnh:
- Mở trình duyệt và truy cập http://localhost:3000/.
- Sử dụng giao diện người dùng của ứng dụng JStack (thường là một form đơn giản hoặc nút để tạo bài viết) để tạo một bài viết mới.
- Kiểm tra xem bài viết mới tạo có hiển thị trong mục “recent posts” trên giao diện người dùng hay không.
-
Truy cập lại Appwrite Console > Databases > main > posts > Documents. Dữ liệu của bài viết bạn vừa tạo sẽ hiển thị trong danh sách tài liệu tại đây, xác nhận rằng dữ liệu đã được lưu trữ thành công trong Appwrite.
Ví dụ về dữ liệu trong Appwrite Console:
bun run dev
Nếu các bước trên thành công, xin chúc mừng! Bạn đã tích hợp JStack và Appwrite thành công trên môi trường phát triển cục bộ.
Triển Khai Ứng Dụng Với Appwrite Sites
Một trong những tính năng mới và hấp dẫn của Appwrite là Appwrite Sites, cho phép bạn lưu trữ cả frontend (ứng dụng JStack/Next.js) cùng với backend Appwrite của mình.
Tìm hiểu thêm về Appwrite Sites tại https://appwrite.io/products/sites và tài liệu https://appwrite.io/docs/advanced/self-hosting/sites.
Các bước để triển khai ứng dụng JStack/Next.js của bạn lên Appwrite Sites:
- Đẩy code dự án của bạn lên một kho lưu trữ Git (ví dụ: GitHub, GitLab, Bitbucket).
- Trong Appwrite Console, điều hướng đến Sites > Create Site.
- Bạn có thể tải lên tệp nén (tar file) trực tiếp, nhưng cách phổ biến và tiện lợi hơn là kết nối với kho lưu trữ Git của bạn (đặc biệt là GitHub để tự động triển khai khi có commit mới). Chọn tùy chọn kết nối với kho lưu trữ.
- Chọn kho lưu trữ chứa code dự án JStack của bạn.
-
Cấu hình các cài đặt triển khai. Bạn có thể giữ các cài đặt mặc định cho hầu hết các tùy chọn (như build command, publish directory, v.v. vì Appwrite Sites hỗ trợ sẵn Next.js). Tuy nhiên, bắt buộc phải thêm các biến môi trường mà chúng ta đã định nghĩa trong tệp
.env
:NEXT_PUBLIC_APPWRITE_PROJECT_ID
NEXT_PUBLIC_APPWRITE_ENDPOINT
NEXT_PUBLIC_APP_DOMAIN
Nhập các giá trị tương ứng. Đối với
NEXT_PUBLIC_APP_DOMAIN
, bạn cần cập nhật nó thành tên miền mà Appwrite Sites sẽ gán cho ứng dụng của bạn, hoặc tên miền tùy chỉnh mà bạn sẽ cấu hình sau. Ví dụ: nếu tên miền được gán làten-du-an-cua-ban.appwrite.network
, bạn sẽ đặt biến này làten-du-an-cua-ban.appwrite.network
. - Nhấp vào nút “Deploy”.
Appwrite Sites sẽ bắt đầu quá trình build và triển khai ứng dụng của bạn từ kho lưu trữ Git. Quá trình này có thể mất vài phút tùy thuộc vào kích thước dự án và kết nối mạng.
Khi quá trình hoàn tất, ứng dụng JStack của bạn sẽ khả dụng trên tên miền được gán bởi Appwrite Sites!
Bạn có thể kiểm tra ứng dụng demo được triển khai tại đây (dựa trên ví dụ gốc): https://jstack-appwrite-template.appwrite.network/
JStack + Appwrite: Sự Kết Hợp Lý Tưởng?
Quay trở lại câu hỏi ban đầu: Liệu JStack có phải là lựa chọn tốt hơn T3 Stack, và sự kết hợp JStack + Appwrite có mang lại lợi ích gì?
Từ trải nghiệm thực tế, JStack giải quyết một số vấn đề cơ bản mà nhiều nhà phát triển gặp phải với Next.js truyền thống:
- Sử dụng Hono: Thay vì dựa vào các convention định tuyến API tích hợp sẵn của Next.js, JStack sử dụng Hono – một framework web nhỏ, nhanh và hiện đại cho Edge Function và Node.js. Điều này mang lại sự linh hoạt và hiệu suất cao hơn.
- TanStack Query (React Query): JStack tích hợp sẵn TanStack Query (trước đây là React Query), một thư viện quản lý state server tuyệt vời. Nó giúp đơn giản hóa việc fetching, caching, đồng bộ và cập nhật dữ liệu từ server, mang lại trải nghiệm người dùng mượt mà hơn và code clean hơn đáng kể.
- Type-safe và sử dụng Zod: Với việc sử dụng TypeScript và Zod cho validation schema, JStack đảm bảo ứng dụng của bạn an toàn kiểu dữ liệu từ frontend đến backend, giảm thiểu lỗi runtime.
So với T3 Stack, điểm khác biệt rõ rệt nhất của JStack là sự “nhẹ nhàng” hơn. Mặc dù cả hai đều sử dụng tRPC, JStack dường như có cách triển khai tRPC đơn giản hơn. Đối với nhiều dự án, sự phức tạp mà tRPC đầy đủ mang lại là không cần thiết, và JStack cung cấp một giải pháp type-safe tương tự mà vẫn giữ cho code dễ hiểu và bảo trì.
Khi kết hợp với Appwrite, JStack trở nên mạnh mẽ hơn nữa. Appwrite đóng vai trò là một backend đầy đủ tính năng, loại bỏ nhu cầu về ORM phức tạp hay xây dựng API thủ công cho các tác vụ cơ bản như xác thực, quản lý database, lưu trữ file. Appwrite SDK tích hợp mượt mà với môi trường TypeScript của JStack, đặc biệt là khi sử dụng Appwrite CLI để tạo kiểu dữ liệu. Khả năng triển khai frontend trực tiếp trên Appwrite Sites cùng với backend tạo thành một giải pháp toàn diện và hiệu quả.
Kết Luận
JStack và Appwrite là hai công cụ hiện đại, mỗi công cụ mang đến những lợi ích riêng. JStack cung cấp một bộ khung phát triển frontend và backend hiệu quả, type-safe với những công cụ mạnh mẽ. Appwrite đơn giản hóa việc quản lý backend với các tính năng BaaS sẵn có và khả năng hosting.
Sự kết hợp giữa JStack và Appwrite tạo ra một giải pháp toàn diện và hiệu quả cho việc xây dựng các ứng dụng web hiện đại, từ ý tưởng đến triển khai. Nếu bạn đang tìm kiếm một bộ stack mới để bắt đầu dự án hoặc muốn nâng cấp stack hiện tại, sự kết hợp này chắc chắn xứng đáng để bạn cân nhắc và trải nghiệm.
Xin gửi lời cảm ơn đặc biệt đến Josh vì đã tạo ra JStack – một bộ khung tuyệt vời. Và đừng ngần ngại cho Appwrite cơ hội trở thành nền tảng đám mây “all-in-one” tiếp theo của bạn cho cả nhu cầu backend và frontend.
Tài Nguyên Tham Khảo
- JStack: https://jstack.app/
- Appwrite Docs: https://appwrite.io/docs
- Mã nguồn ví dụ: https://github.com/ChiragAgg5k/jstack-appwrite-template