Sẽ mở trong thẻ mới
PDL AIAI Worker — trợ lý AI trực 24/7: trả lời khách, xử lý yêu cầu, báo cáo tự động

Kỹ sư phần mềm thời AITờ 01-03 / 112

Frontend, backend, database: ba tầng của một ứng dụng

Mặt cắt tiệm bánh: phiếu khách tự ghi giá thấp bị bếp chặn lại và tính lại, ví cho ba tầng ứng dụng
Tờ
01-03 / 112
Hạng mục
M01 · Nền tảng máy tính và web cho người không chuyên
Cấp độ
Cơ bản
Thời gian đọc
18 phút
Giai đoạn công trình
0 · Khám phá
Cập nhật
Bản đồ công trìnhTờ này lắp: Giao diện app, API, Database
Bản đồ công trình Mộc OMSBài này: Giao diện app, API, Database. Đã học: Coding agent, Khách hàng, Kênh bán, Sheet & sổ tay. Chưa tới: PRD & spec, Repo & CI, Kiểm thử, Hosting & tên miền, Giám sát lỗi, Dashboard, báo cáo, Trợ lý AI nhận đơn, Màn hình bếp, Đăng nhập, vai trò, Thanh toán VietQR, Thông báo khách, Nhiều tiệm (MộcOS), Kế toán, hoá đơn.Thi công & hạ tầngDòng đơn hàngNghiệp vụ & dịch vụMở rộng MộcOSPRD & specspecCoding agentagentRepo & CIrepo-ciKiểm thửtestsHosting & tên miềnhostingGiám sát lỗimonitoringKhách hàngcustomerKênh bánchannelsGiao diện appapp-uiAPIapiDatabasedatabaseDashboard, báo cáoreportsSheet & sổ taysheetTrợ lý AI nhận đơnai-assistantMàn hình bếpkitchenĐăng nhập, vai tròauthThanh toán VietQRpaymentsThông báo kháchnotificationsNhiều tiệm (MộcOS)tenantsKế toán, hoá đơnintegrations0 Khám phá1 MVP2 Ra mắt3 Thêm AI4 Mở rộng
Bản đồ công trình Mộc OMSBài này: Giao diện app, API, Database. Đã học: Coding agent, Khách hàng, Kênh bán, Sheet & sổ tay. Chưa tới: PRD & spec, Repo & CI, Kiểm thử, Hosting & tên miền, Giám sát lỗi, Dashboard, báo cáo, Trợ lý AI nhận đơn, Màn hình bếp, Đăng nhập, vai trò, Thanh toán VietQR, Thông báo khách, Nhiều tiệm (MộcOS), Kế toán, hoá đơn.Dòng đơn hàngKhách hàngKênh bánGiao diệnAPIDatabaseBáo cáoNghiệp vụ & dịch vụSheet, sổ tayTrợ lý AIMàn hình bếpĐăng nhậpThanh toánThông báoThi công & hạ tầngPRD & specCoding agentRepo & CIKiểm thửHostingGiám sátMở rộng MộcOSNhiều tiệmKế toán0 Khám phá1234

Bài nàyĐã họcChưa tớiĐã thay thế

Bạn sẽ học được gì

Tiêu chí nghiệm thu

Ba tầng qua hình ảnh một tiệm bánh

Ở bài Phần mềm thực chất là gì01-01, bạn đã biết mọi phần mềm có ba thành phần: dữ liệu, logic, giao diện. Trong một ứng dụng web, ba thành phần đó được “đặt” vào ba nơi vật lý khác nhau, gọi là ba tầng (tier/layer).

Hình chữ ký Tầng tách lớp
  1. Gian hàng, quầy order
  2. Bếp và quầy thu ngân
  3. Kho và sổ cái
  4. lịch sự
  5. an toàn
  6. chắc chắn

Hãy đi một vòng quanh tiệm Bánh Mộc chi nhánh Thảo Điền:

  • Gian hàng phía trước có tủ kính, bảng giá, quầy order. Khách nhìn thấy, chạm vào, chỉ trỏ. Đây là frontend.
  • Bếp và quầy thu ngân phía sau: nơi nhận phiếu order, kiểm tra còn nguyên liệu không, tính tiền, quyết định làm hay không. Khách không được vào. Đây là backend.
  • Kho và tủ hồ sơ: nơi cất nguyên liệu và sổ cái ghi mọi đơn hàng. Chỉ bếp được ra vào. Đây là database.

Điểm mấu chốt trong ẩn dụ này: khách có thể làm bất cứ điều gì ở gian hàng phía trước. Họ có thể tự viết phiếu order, tự ghi giá, thậm chí mang phiếu giả từ nhà tới. Bếp phía sau phải luôn kiểm tra lại. Giữ hình ảnh này trong đầu, bạn sẽ hiểu được phần lớn các lỗ hổng bảo mật phổ biến.

Frontend: phần chạy trên máy người dùng

Frontend là mọi thứ người dùng nhìn thấy và tương tác: màn hình, nút bấm, form, hiệu ứng. Nó được xây bằng ba công nghệ nền của web:

  • HTML: khung và nội dung (tiêu đề, ô nhập, nút).
  • CSS: trang trí (màu, cỡ chữ, bố cục). Trong stack Bánh Mộc, chúng ta dùng Tailwind CSS để viết CSS nhanh hơn.
  • JavaScript: hành vi (bấm nút thì làm gì, kiểm tra ô nhập, cập nhật danh sách không cần tải lại trang). Thư viện React giúp tổ chức JavaScript thành các khối giao diện tái sử dụng.

Điều quan trọng nhất về frontend: code frontend được tải về và chạy trên thiết bị của người dùng. Điện thoại của Hà, laptop của khách, máy của bất kỳ ai mở trang web. Hệ quả:

  1. Ai cũng xem được code frontend. Chỉ cần bấm F12 trên trình duyệt. Mọi thứ nằm trong code frontend, kể cả một “khóa bí mật” vô tình để ở đó, đều công khai.
  2. Ai cũng sửa được hành vi frontend. Người dùng có kỹ thuật có thể bỏ qua giao diện và gửi thẳng request tới server với bất kỳ dữ liệu nào họ muốn.
  3. Bạn không kiểm soát được môi trường chạy. Trình duyệt cũ, mạng chập chờn, màn hình nhỏ, tất cả đều ảnh hưởng tới trải nghiệm.

Backend: phần chạy trên server bạn kiểm soát

Backend là phần chạy trên server, nơi người dùng không nhìn thấy và không chạm tới được. Nhiệm vụ của backend:

  • Xác thực và phân quyền: người này là ai, có được làm việc này không.
  • Thực thi quy tắc nghiệp vụ: tính tổng tiền, sinh mã đơn BM-YYMMDD-XXX, kiểm tra chuyển trạng thái hợp lệ.
  • Kiểm tra dữ liệu (validate): số lượng phải là số nguyên dương, giờ nhận phải ở tương lai.
  • Nói chuyện với database và các dịch vụ bên ngoài (gửi Zalo, tạo mã VietQR).
  • Giữ bí mật: khóa API, mật khẩu kết nối database chỉ nằm ở backend.

Backend có thể viết bằng nhiều ngôn ngữ (TypeScript, Python, Go, PHP…). Trong stack Bánh Mộc, backend viết bằng TypeScript và chạy trong Next.js.

Database: kho và sổ cái

Database (cơ sở dữ liệu) là nơi lưu trữ dữ liệu bền vững, có tổ chức, tìm kiếm nhanh. Database của Mộc OMS là PostgreSQL (thường gọi tắt là Postgres), được Supabase vận hành hộ. Dữ liệu tổ chức thành các bảng như orders, order_items, customers, products.

Database không chỉ là “chỗ chứa”. Một database tốt còn là tuyến phòng thủ cuối cùng cho tính đúng đắn của dữ liệu: nó có thể từ chối một khách hàng thứ hai có cùng số điện thoại (ràng buộc UNIQUE), từ chối một dòng đơn hàng trỏ tới sản phẩm không tồn tại, từ chối số lượng âm. Bạn sẽ học về các ràng buộc này trong bài Cơ sở dữ liệu04-03.

Ba tầng trong một thao tác thật: Hà tạo đơn bánh kem

Hãy xem ba tầng phối hợp khi Hà tạo đơn bánh kem dâu 20cm cho chị Hương, giao lúc 15 giờ:

Vuốt ngang để xem hết sơ đồ

Supabase

Server Mộc OMS (Next.js trên Vercel)

Thiết bị của Hà (trình duyệt)

1. gửi: khách, món, số lượng, giờ nhận

2. Hà là ai?

3. đọc giá, ghi đơn

4. đơn đã lưu

5. mã BM-261012-007, tổng 450.000đ

FRONTEND
Form tạo đơn
kiểm tra sơ bộ, hiển thị tạm tính

BACKEND
xác thực, phân quyền
validate, lấy giá thật
tính tổng, sinh mã đơn

DATABASE
orders, order_items
customers, products

Auth
quản lý đăng nhập

Vuốt ngang để xem hết sơ đồ

Supabase

Server Mộc OMS (Next.js trên Vercel)

Thiết bị của Hà (trình duyệt)

1. gửi: khách, món, số lượng, giờ nhận

2. Hà là ai?

3. đọc giá, ghi đơn

4. đơn đã lưu

5. mã BM-261012-007, tổng 450.000đ

FRONTEND
Form tạo đơn
kiểm tra sơ bộ, hiển thị tạm tính

BACKEND
xác thực, phân quyền
validate, lấy giá thật
tính tổng, sinh mã đơn

DATABASE
orders, order_items
customers, products

Auth
quản lý đăng nhập

Hình 1 Sơ đồ luồng
Đọc sơ đồ dạng chữ
  1. Thiết bị của Hà (trình duyệt)
  2. Server Mộc OMS (Next.js trên Vercel)
  3. Supabase
  4. FRONTEND Form tạo đơn kiểm tra sơ bộ, hiển thị tạm tính → BACKEND xác thực, phân quyền validate, lấy giá thật tính tổng, sinh mã đơn (1. gửi: khách, món, số lượng, giờ nhận)
  5. BACKEND xác thực, phân quyền validate, lấy giá thật tính tổng, sinh mã đơn → Auth quản lý đăng nhập (2. Hà là ai?)
  6. BACKEND xác thực, phân quyền validate, lấy giá thật tính tổng, sinh mã đơn → DATABASE orders, order_items customers, products (3. đọc giá, ghi đơn)
  7. DATABASE orders, order_items customers, products → BACKEND xác thực, phân quyền validate, lấy giá thật tính tổng, sinh mã đơn (4. đơn đã lưu)
  8. BACKEND xác thực, phân quyền validate, lấy giá thật tính tổng, sinh mã đơn → FRONTEND Form tạo đơn kiểm tra sơ bộ, hiển thị tạm tính (5. mã BM-261012-007, tổng 450.000đ)
Tầng Chạy ở đâu Ai xem được code Việc trong thao tác tạo đơn Ẩn dụ
Frontend Trình duyệt của Hà Bất kỳ ai mở trang Hiển thị form, báo lỗi ngay khi bỏ trống ô, hiển thị tạm tính cho tiện Gian hàng, quầy order
Backend Server (Vercel) Chỉ đội làm phần mềm Kiểm tra Hà đã đăng nhập, lấy giá thật từ database, tính tổng, sinh mã, ghi đơn Bếp và quầy thu ngân
Database Supabase Không ai “xem code”, chỉ truy cập dữ liệu khi được phép Lưu đơn bền vững, chặn dữ liệu vi phạm ràng buộc Kho và sổ cái

Chú ý rằng tạm tính xuất hiện ở cả frontend lẫn backend. Frontend tính để Hà thấy ngay con số khi chọn món, cho trải nghiệm mượt. Backend tính lại từ đầu để quyết định con số thật được lưu. Đây không phải lặp thừa; đây là thiết kế đúng.

Quy tắc vàng: không tin dữ liệu từ trình duyệt

Nếu chỉ nhớ một điều từ bài này, hãy nhớ: mọi thứ đến từ trình duyệt đều có thể bị giả mạo.

Ẩn dụ tiệm bánh: khách đưa phiếu order tự ghi “Bánh kem dâu 20cm, giá 50.000đ”. Thu ngân tốt sẽ không nhìn con số khách ghi; thu ngân tra bảng giá của tiệm và tính lại. Thu ngân tồi sẽ thu 50.000đ.

Trong phần mềm, “phiếu tự ghi” chính là request gửi từ trình duyệt. Chỉ cần mở công cụ dành cho lập trình viên (F12) hoặc dùng một công cụ gửi request, bất kỳ ai cũng có thể gửi lên server bất cứ thứ gì: giá 1 đồng, số lượng âm, mã chi nhánh khác, trạng thái hoan_tat cho đơn chưa làm.

Trước và sau: code AI hay viết sai

Đây là kiểu code AI hay viết khi không được dặn kỹ. Hãy đọc chú thích, bạn chưa cần hiểu từng chữ (bài Đọc hiểu code cơ bản01-04 sẽ giúp bạn đọc tốt hơn):

TS
// ❌ SAI: backend tin con số tổng tiền do trình duyệt gửi lên
"use server";

export async function taoDon(input: {
  customerPhone: string;
  items: { productId: number; quantity: number }[];
  total: number; // tổng tiền do frontend tự tính rồi gửi lên
}) {
  // Lưu thẳng con số input.total vào database, không kiểm tra gì cả
  await luuDonVaoDatabase({ ...input, total: input.total });
}

Phiên bản đúng: backend kiểm tra người gọi, kiểm tra hình dạng dữ liệu, lấy giá thật từ database và tự tính tổng.

TS
// ✅ ĐÚNG: backend tự kiểm tra và tự tính, không tin frontend
"use server";
import { z } from "zod";

// Mô tả hình dạng dữ liệu hợp lệ. Lưu ý: KHÔNG nhận giá hay tổng tiền từ frontend.
const TaoDonSchema = z.object({
  customerPhone: z.string().regex(/^0\d{9}$/, "Số điện thoại phải có 10 chữ số, bắt đầu bằng 0"),
  items: z
    .array(
      z.object({
        productId: z.number().int().positive(),
        quantity: z.number().int().min(1).max(50),
      })
    )
    .min(1, "Đơn phải có ít nhất 1 món"),
});

export async function taoDon(raw: unknown) {
  // 1. Ai đang gọi? Chưa đăng nhập thì từ chối.
  const nhanVien = await layNhanVienDangNhap();
  if (!nhanVien) throw new Error("Bạn cần đăng nhập");

  // 2. Dữ liệu có đúng hình dạng không? Sai là dừng ngay.
  const input = TaoDonSchema.parse(raw);

  // 3. Lấy giá THẬT từ database, tự tính tạm tính (tiền là số nguyên VNĐ).
  let subtotal = 0;
  for (const item of input.items) {
    const sp = await laySanPham(item.productId);
    if (!sp || !sp.isActive) throw new Error("Sản phẩm không tồn tại hoặc đã ngừng bán");
    subtotal += sp.price * item.quantity;
  }

  // 4. Chi nhánh lấy từ hồ sơ nhân viên, không lấy từ dữ liệu frontend gửi.
  await luuDonVaoDatabase({ ...input, subtotal, branchId: nhanVien.branchId });
}

Hai đoạn code trông đều “chạy được”. Trên giao diện, chúng cho kết quả giống hệt nhau với người dùng bình thường. Khác biệt chỉ lộ ra khi có người cố tình gian lận, hoặc khi frontend có lỗi tính sai. Đây chính là loại lỗi mà “trông có vẻ xong” không bao giờ phát hiện ra.

Kiểm tra ở đâu cho đúng

Vậy có nên bỏ kiểm tra ở frontend không? Không. Mỗi tầng kiểm tra vì một lý do khác nhau:

Tầng kiểm tra Mục đích Ví dụ Có thể bị vượt qua?
Frontend Trải nghiệm: báo lỗi ngay, không bắt người dùng chờ “Vui lòng nhập số điện thoại” hiện ngay dưới ô Có, rất dễ
Backend An toàn và đúng nghiệp vụ: quyết định cuối cùng Từ chối đơn có giờ nhận trong quá khứ, tính lại tổng Không, nếu viết đúng
Database Lưới an toàn cuối cùng cho tính toàn vẹn dữ liệu Không cho hai khách trùng số điện thoại Không

Một cách nhớ: frontend kiểm tra để lịch sự, backend kiểm tra để an toàn, database kiểm tra để chắc chắn.

Bàn thử

Form tạo đơn giả có ô total sửa được như F12: gửi 50.000đ cho bánh 450.000đ → so phiên bản SAI (lưu thẳng) với ĐÚNG (backend tra giá, tự tính, chi nhánh lấy từ phiên đăng nhập)

Bàn thử cần bật JavaScript.

Kết quả chỉ để tham khảo, không thay cho việc bạn tự duyệt.

Next.js: khi một dự án chứa cả gian hàng lẫn bếp

Ngày trước, frontend và backend thường là hai dự án riêng, đôi khi do hai đội riêng làm. Ngày nay, các framework fullstack như Next.js (trong stack Bánh Mộc) cho phép viết cả hai trong cùng một dự án, cùng một ngôn ngữ. Đây là lý do lớn giúp một người cùng AI có thể làm cả ứng dụng.

Trong Next.js với App Router (tại thời điểm viết, 2026), có vài cách code được đánh dấu để biết nó chạy ở đâu:

  • Server Component: mặc định, các component trong thư mục app/ chạy trên server. Chúng có thể đọc database trực tiếp, và code của chúng không bị gửi xuống trình duyệt.
  • Client Component: file bắt đầu bằng dòng "use client" chạy được trên trình duyệt, dùng cho phần tương tác (bấm nút, gõ chữ, mở hộp thoại).
  • Server Action: hàm được đánh dấu "use server" (như ví dụ taoDon ở trên). Frontend gọi nó như gọi một hàm bình thường, nhưng thực chất đó là một request gửi lên server.
  • Route Handler: file route.ts trong thư mục app/, tạo ra một địa chỉ API như /api/orders cho phần mềm khác gọi tới.

Phiên agent

src/app/don-hang/
├── page.tsx            # Server Component: đọc danh sách đơn từ database (chạy trên server)
├── form-tao-don.tsx    # "use client": form có tương tác (chạy trên trình duyệt)
└── actions.ts          # "use server": hàm taoDon, xử lý và ghi đơn (chạy trên server)

Sự tiện lợi này đi kèm một rủi ro: ranh giới giữa gian hàng và bếp trở nên mờ. Hai file nằm cạnh nhau trong cùng thư mục, nhưng một file ai cũng xem được, file kia thì không. AI đôi khi đặt nhầm logic quan trọng vào Client Component, hoặc tin tưởng dữ liệu từ form vì “cùng một dự án mà”.

Backend-as-a-Service: thuê sẵn bếp và kho

Xây backend và vận hành database từ đầu tốn công: phải lo đăng nhập, mã hóa mật khẩu, sao lưu, bảo mật server, mở rộng khi đông khách. Backend-as-a-Service (BaaS) là dịch vụ cung cấp sẵn những phần này, giống thuê một bếp trung tâm đã có sẵn kho, sổ sách và bảo vệ.

Hai cái tên phổ biến:

  • Supabase (trong stack Bánh Mộc): cung cấp database PostgreSQL, hệ thống đăng nhập (Auth), lưu trữ file (Storage), và tự sinh API để đọc/ghi dữ liệu.
  • Firebase (của Google): cung cấp database dạng tài liệu (Firestore), đăng nhập, lưu trữ file, hàm chạy trên server.

Điểm đặc biệt của BaaS: frontend có thể nói chuyện trực tiếp với database thông qua API mà dịch vụ cung cấp, không cần đi qua backend riêng. Nghe rất tiện, nhưng hãy nhớ lại quy tắc vàng: frontend thì ai cũng xem và sửa được. Vậy cái gì ngăn một người lạ đọc toàn bộ bảng customers?

Câu trả lời là luật truy cập đặt ngay tại database:

  • Với Supabase, đó là Row Level Security (RLS, bảo mật theo từng dòng): các luật kiểu “nhân viên chỉ đọc được đơn có branch_id bằng chi nhánh của mình”.
  • Với Firebase, đó là Security Rules.
Tiêu chí Tự xây backend + database Dùng BaaS (Supabase, Firebase)
Tốc độ khởi đầu Chậm hơn Nhanh, có sẵn đăng nhập, lưu file
Vận hành (sao lưu, cập nhật bảo mật server) Tự lo Nhà cung cấp lo phần lớn
Kiểm soát Toàn quyền Theo khả năng của dịch vụ
Rủi ro chính Tự cấu hình sai server Cấu hình sai luật truy cập (RLS/Rules)
Chi phí Tùy hạ tầng Thường có gói miễn phí để bắt đầu; tăng theo quy mô (kiểm tra bảng giá chính thức)

Mộc OMS chọn cách nào?

Chị Lan cùng agent chọn một cách kết hợp, phổ biến và an toàn cho người mới:

  • Dùng Supabase làm database Postgres và hệ thống đăng nhập (không phải tự vận hành).
  • Mọi thao tác ghi quan trọng (tạo đơn, đổi trạng thái, ghi nhận thanh toán) đi qua backend Next.js (Server Action), nơi kiểm tra quyền, validate và tính tiền.
  • Vẫn bật RLS cho mọi bảng như lớp phòng thủ thứ hai, phòng khi có chỗ nào đó frontend truy cập trực tiếp.

Đây là nguyên tắc “phòng thủ nhiều lớp”: không đặt toàn bộ niềm tin vào một chỗ duy nhất.

Áp dụng vào dự án Bánh Mộc

Nhật ký công trình · Bánh Mộc

BM-261013-009

13.10.2026

  • Chị LanChủ chuỗi
Chị Lan đọc báo cáo về app cũ và ghi ba quy tắc ranh giới vào sổ, cạnh chiếc ổ khóa mở

Chị Lan mở lại app “vibe” ngày trước, giờ với con mắt mới. Chị nhờ một coding agent đọc code và trả lời ba câu: logic tính tiền nằm ở đâu, dữ liệu lưu ở đâu, khóa bí mật nằm ở đâu. Kết quả:

  • Logic tính tiền: nằm hoàn toàn trong một Client Component. Tổng tiền được tính trên trình duyệt rồi gửi thẳng vào database.
  • Dữ liệu: frontend gọi trực tiếp Supabase, nhưng bảng orders và customers chưa bật RLS. Agent giải thích: ai có địa chỉ trang web đều có thể đọc danh sách số điện thoại khách hàng.
  • Khóa bí mật: một khóa API của dịch vụ AI được đặt trong biến NEXT_PUBLIC_AI_KEY. Bài 01-08 sẽ kể chuyện gì đã xảy ra với chiếc khóa này.

Chị Lan không tự sửa được. Nhưng lần này chị biết mình cần yêu cầu gì. Chị ghi vào sổ tay ba quy tắc cho dự án Mộc OMS mới, sau này sẽ đưa vào file hướng dẫn agent (AGENTS.md):

MARKDOWN
## Quy tắc ranh giới frontend/backend
- Mọi thao tác ghi dữ liệu (tạo/sửa/hủy đơn, thanh toán) đi qua Server Action ở backend.
- Backend không bao giờ nhận giá, tổng tiền, vai trò, chi nhánh từ dữ liệu frontend gửi lên;
  luôn tự lấy từ database hoặc từ phiên đăng nhập.
- Mọi bảng trong Supabase phải bật RLS. Không dùng service role key ở code chạy trên trình duyệt.

Bài học chị rút ra: app cũ trông giống hệt một app tốt. Khác biệt nằm ở chỗ đặt logic và chỗ đặt niềm tin, những thứ không nhìn thấy trên màn hình.

Prompt mẫu

Prompt 1: kiểm tra một tính năng theo ba tầng.

Đọc code của tính năng [tạo đơn hàng] và trả lời dưới dạng bảng:
1. Những file nào chạy trên trình duyệt (frontend), những file nào chạy trên server (backend)?
2. Logic tính tiền, kiểm tra quyền, sinh mã đơn nằm ở tầng nào, file nào?
3. Liệt kê mọi trường dữ liệu backend nhận từ frontend. Với mỗi trường, backend có
   tự kiểm tra lại hoặc tự xác định lại không? Đánh dấu trường nào đang bị tin tưởng mù quáng.
Chưa sửa gì cả, chỉ báo cáo.

Prompt 2: yêu cầu viết tính năng với ranh giới rõ ràng.

Viết Server Action đổi trạng thái đơn hàng. Yêu cầu:
- Chỉ nhận từ frontend: mã đơn và trạng thái mới.
- Backend tự lấy nhân viên đang đăng nhập; staff chỉ đổi được đơn thuộc chi nhánh của mình.
- Chỉ cho phép chuyển trạng thái theo thứ tự: moi -> da_xac_nhan -> dang_lam -> dang_giao -> hoan_tat,
  hoặc chuyển sang da_huy khi đơn còn ở moi, da_xac_nhan hoặc dang_lam (trước dang_giao).
- Ghi một dòng vào order_status_history cho mỗi lần đổi.
- Validate đầu vào bằng Zod. Báo lỗi tiếng Việt.
Sau khi viết, giải thích ngắn đoạn nào chạy ở đâu và chạy test liên quan.

Prompt 3: kiểm tra RLS khi dùng Supabase.

Liệt kê tất cả bảng trong database Supabase của dự án. Với mỗi bảng: RLS đã bật chưa,
có những policy nào, policy đó cho phép ai đọc/ghi gì (giải thích bằng tiếng Việt dễ hiểu).
Chỉ ra bảng nào có rủi ro bị người lạ đọc dữ liệu. Không thay đổi gì, chỉ báo cáo.

Sai lầm thường gặp

  1. Sự cố 01

    Đặt logic nghiệp vụ quan trọng ở frontend.

    Triệu chứng

    Tính tiền, kiểm tra quyền, đổi trạng thái chỉ trên trình duyệt. Chạy đúng với người dùng ngoan, sai với người gian lận.

    Cách tránh

    Quy định rõ trong file hướng dẫn agent: logic nghiệp vụ và mọi thao tác ghi nằm ở backend.

  2. Sự cố 02

    Bỏ kiểm tra ở frontend vì “đằng nào backend cũng kiểm tra”.

    Triệu chứng

    Người dùng phải bấm gửi, chờ, rồi mới thấy lỗi. Trải nghiệm tệ.

    Cách tránh

    Kiểm tra ở cả hai nơi; frontend để lịch sự, backend để an toàn. Có thể dùng chung một schema Zod cho cả hai.

  3. Sự cố 03

    Để khóa bí mật trong code frontend.

    Triệu chứng

    Mọi thứ trong frontend đều công khai, kể cả khi nằm trong file “ẩn” hay đã được nén.

    Cách tránh

    Khóa bí mật chỉ nằm ở backend, đọc từ biến môi trường không có tiền tố NEXT_PUBLIC_.

  4. Sự cố 04

    Dùng BaaS mà không bật luật truy cập.

    Triệu chứng

    Supabase không bật RLS, Firebase để luật “cho phép tất cả” trong lúc thử nghiệm rồi quên.

    Cách tránh

    Bật RLS cho mọi bảng ngay khi tạo; đưa vào checklist trước khi ra mắt; yêu cầu agent báo cáo trạng thái RLS định kỳ.

  5. Sự cố 05

    Không biết đoạn code nào chạy ở đâu.

    Triệu chứng

    Trong Next.js, file cạnh nhau có thể chạy ở hai nơi khác nhau.

    Cách tránh

    Mỗi khi review, hỏi agent “file này chạy trên server hay trình duyệt?” cho đến khi bạn tự nhận ra được qua dấu hiệu "use client", "use server".

Checklist

Phiếu nghiệm thu

Bài tập thực hành

  1. Bài 1

    Cơ bản

    Phân loại tầng

    Với mỗi việc sau trong Mộc OMS, ghi rõ nên làm ở frontend, backend, database, hay nhiều tầng: (a) đổi màu nút khi rê chuột; (b) tính tổng tiền đơn; (c) không cho hai khách cùng số điện thoại; (d) báo “chưa nhập số điện thoại”; (e) kiểm tra nhân viên có thuộc chi nhánh của đơn không; (f) gửi tin nhắn Zalo xác nhận đơn.

    Kết quả mong đợi

    (a) frontend; (b) backend quyết định, frontend chỉ hiển thị tạm; (c) database (và backend báo lỗi thân thiện); (d) frontend và backend; (e) backend và RLS ở database; (f) backend.

  2. Bài 2

    Cơ bản

    Nhìn code frontend của một trang web

    Mở một trang web bất kỳ, bấm F12, vào tab Sources (hoặc Debugger). Duyệt qua các file JavaScript. Mục tiêu không phải hiểu code, mà là tự thấy rằng code frontend công khai.

    Kết quả mong đợi

    Bạn thấy hàng loạt file JavaScript, có file đọc được, có file bị nén thành một dòng dài. Tất cả đều tải về được.

  3. Bài 3

    Nâng cao

    Tìm lỗi “tin trình duyệt”

    Nếu bạn có một dự án (kể cả dự án vibe cũ), dùng Prompt mẫu 1 để agent liệt kê các trường dữ liệu backend nhận từ frontend. Đánh dấu những trường đang bị tin tưởng mù quáng.

    Kết quả mong đợi

    Một danh sách cụ thể các trường cần sửa, ưu tiên những trường liên quan đến tiền, quyền và dữ liệu khách hàng.

Thuật ngữ trong bài

Frontend
Phần giao diện chạy trên thiết bị người dùng
Backend
Phần xử lý chạy trên server, người dùng không chạm tới
Database
Nơi lưu dữ liệu bền vững, có tổ chức
HTML / CSS / JavaScript
Khung nội dung / trang trí / hành vi của trang web
React
Thư viện xây giao diện thành các khối tái sử dụng
Fullstack framework
Framework cho phép viết cả frontend và backend trong một dự án (như Next.js)
Server Component
Component Next.js chạy trên server
Client Component
Component Next.js chạy trên trình duyệt, đánh dấu "use client"
Server Action
Hàm chạy trên server, frontend gọi tới, đánh dấu "use server"
Validate
Kiểm tra dữ liệu có hợp lệ không
Zod
Thư viện TypeScript để mô tả và kiểm tra hình dạng dữ liệu
BaaS (Backend-as-a-Service)
Dịch vụ cung cấp sẵn database, đăng nhập, lưu trữ
RLS (Row Level Security)
Luật truy cập theo từng dòng dữ liệu trong PostgreSQL/Supabase
Phòng thủ nhiều lớp
Kiểm tra ở nhiều tầng, không dựa vào một chỗ duy nhất

Đọc thêm

Nghiệm thu tờ 01-03

Đọc xong và tự kiểm theo phiếu nghiệm thu, rồi đánh dấu tờ này.

Tác giả
Phú Digital
Đội ngũ PDL Digital — triển khai website, giải pháp AI và digital marketing cho doanh nghiệp.
Tư vấn 1:1

Muốn dựng phần mềm riêng cho doanh nghiệp của bạn?

Kỹ sư PDL cùng bạn chọn bước đầu tiên phù hợp — phản hồi trong giờ làm việc (T2–T7, 8h–18h).

<script src="https://cdn.jsdelivr.net/npm/page-flip/dist/js/page-flip.browser.js"></script>

<div id="container-flipbook" style="display:flex; justify-content:center;">
    <div id="my-flipbook">
       <div class="page"> <img src="https://pdl.vn/wp-content/uploads/2025/05/AI-Google-Search.jpg" /> </div>
       <div class="page"> <img src="https://pdl.vn/wp-content/uploads/2025/05/wearable-tech-advertising.jpg" /> </div>
       <div class="page"> <img src="https://pdl.vn/wp-content/uploads/2025/05/multimodal-embeddings-digital-marketing.jpg" /> </div>
    </div>
</div>
#my-flipbook {
    box-shadow: 0 0 20px rgba(0,0,0,0.2);
    margin: 20px auto;
    background-color: #f0f0f0;
}

.page {
    background-color: white;
    overflow: hidden;
}

.page img {
    width: 100%;
    height: 100%;
    object-fit: cover; /* Giúp ảnh vừa vặn trang sách */
}
// Đảm bảo thư viện đã sẵn sàng
function initFlipbook() {
    const el = document.getElementById("my-flipbook");
    if (!el || !window.St) return; // Nếu chưa thấy thư viện St thì thoát

    const pageFlip = new St.PageFlip(el, {
        width: 550,
        height: 733,
        size: "stretch",
        minWidth: 315,
        maxWidth: 1000,
        minHeight: 420,
        maxHeight: 1350,
        showCover: true,
        maxShadowOpacity: 0.5,
    });

    pageFlip.loadFromHTML(document.querySelectorAll(".page"));
}

// Chạy khởi tạo
if (document.readyState === 'complete') {
    initFlipbook();
} else {
    window.addEventListener('load', initFlipbook);
}
PDL · Tư vấn 1:1

Nhận tư vấn
miễn phí

Để lại thông tin, chuyên viên PDL sẽ liên hệ để hiểu nhu cầu và đề xuất giải pháp phù hợp ngân sách của bạn.

  • Phân tích nhanh hiện trạng website, quảng cáo, kênh bán hàng
  • Đề xuất lộ trình & báo giá minh bạch, rõ từng hạng mục
  • Không ràng buộc: nghe tư vấn xong mới quyết định
Giờ làm việc T2–T7 · 08:00–18:00

Chỉ mất khoảng 30 giây

Bạn cần tư vấn về

🔒 Thông tin được bảo mật, chỉ dùng để PDL liên hệ tư vấn.