Skip to main content

Server Actions

Quy tắc định nghĩa và sử dụng Server Actions trong Next.js App Router. Actions thuộc tầng entry point (xem Kiến trúc & các tầng): nhận input, gọi Services, trả kết quả — không gọi Prisma trực tiếp.

Định nghĩa file Action​

Directive 'use server' ở dòng đầu tiên​

Mỗi file Server Action đặt 'use server' ở dòng đầu tiên.

'use server';

import { withActionExceptionHandler } from '@/lib/action-handler';
import { createCustomerService } from '@/services/customer';

Dòng trống sau directive​

Ngay sau 'use server' phải có một dòng trống rồi mới đến các câu lệnh import. Xem thêm Comments & Formatting - Directive.


Exception handling​

Bọc Action bằng withActionExceptionHandler​

Tất cả Server Actions bọc bởi withActionExceptionHandler (hoặc tương đương) để xử lý lỗi tập trung: log, map lỗi sang message user, trả về format thống nhất.

'use server';

import { withActionExceptionHandler } from '@/lib/action-handler';
import { createCustomerService } from '@/services/customer';

/**
* Create a new customer.
* @param input - Data required to create the customer.
* @returns The created customer in standard response shape.
*/
export const createCustomerAction = withActionExceptionHandler(async (input: CreateCustomerInput) => {
const customer = await createCustomerService(input);
return { data: customer };
});

Transaction (nhiều thao tác DB atomic)​

Mở transaction trong Action​

Khi nghiệp vụ cần nhiều thao tác DB và phải atomic, transaction được mở trong Action (dùng prisma.$transaction).

"use server";

import { prisma } from "@/configs/prisma";

/**
* Create a new order with order items and update inventory in a single atomic transaction.
* @param input - Data required to create the order, including items.
* @returns The created order wrapped in a standard response shape.
*/
export const createOrderAction = withActionExceptionHandler(async (input: CreateOrderInput) => {
const createdOrder = await prisma.$transaction(async (tx) => {
const order = await createOrder(tx, { data: { ... } });
await createOrderItemsService(tx, order.id, input.items);
await updateInventoryService(tx, input.items);
return order;
});

return {
status: HttpStatusCode.Ok,
data: createdOrder
};
});


Service layer​

Logic DB qua Service; Action không gọi Prisma​

  • Mọi logic truy cập database thực hiện trong Service layer (/src/services). Action không gọi prisma.* trừ prisma.$transaction.
  • Trách nhiệm Action: (1) nhận input, (2) validate sơ bộ nếu cần, (3) gọi Service hoặc mở transaction (một hoặc nhiều), (4) trả kết quả format chuẩn.

Đặt tên Action​

Cấu trúc: <operation><EntityName>Action​

  • operation: create, update, delete, get, …
  • EntityName: tên resource PascalCase; số ít khi trả về một bản ghi, số nhiều khi trả về danh sách.
  • Hậu tố cố định: Action.
export const createCustomerAction = ...
export const updateCustomerAction = ...
export const deleteCustomerAction = ...
export const getCustomerAction = ... // 1 bản ghi
export const getCustomersAction = ... // danh sách

JSDoc​

Mọi Action có JSDoc​

Mỗi Server Action có JSDoc ngay phía trên: mô tả ngắn, @param, @returns khi cần.

/**
* Create a new customer.
* @param input - Customer data.
* @returns Created customer.
*/
export const createCustomerAction = withActionExceptionHandler(async (input) => {
return createCustomerService(input);
});

Validation & constraint​

Kiểm tra trùng (unique) trong Service​

Trước khi create/update, nếu entity có trường unique theo nghiệp vụ thì bắt buộc kiểm tra trùng trong Service (không làm trong Action). Đặt tên hàm rõ ràng (ví dụ checkDuplicateEmail, isDuplicateCode).

Exclusive constraint khi update/delete​

Khi sửa hoặc xóa, kiểm tra exclusive constraint (optimistic lock, version field) trong Service để tránh ghi đè dữ liệu cũ.