Cảnh báo Chương này chỉ áp dụng cho cách tiếp cận code first.
Extensions là một tính năng nâng cao, ở mức thấp (low-level) cho phép bạn định nghĩa dữ liệu tùy ý ngay trong phần cấu hình type. Việc gắn metadata tùy chỉnh vào một field nào đó giúp bạn xây dựng những giải pháp tinh vi và tổng quát hơn. Ví dụ, với extensions, bạn có thể định nghĩa role (vai trò) cần có ở cấp field để truy cập field đó. Các role này có thể được đọc lại (reflect) lúc runtime để xác định caller (bên gọi) có đủ quyền lấy field cụ thể hay không.
Thêm metadata tùy chỉnh
Để gắn metadata tùy chỉnh cho một field, sử dụng decorator @Extensions() được export từ package @nestjs/graphql.
@Field()
@Extensions({ role: Role.ADMIN })
password: string;
Trong ví dụ trên, ta gán cho thuộc tính metadata role giá trị Role.ADMIN. Role là một enum TypeScript đơn giản, gom nhóm tất cả các role người dùng có trong hệ thống.
Lưu ý, ngoài việc gắn metadata trên field, bạn cũng có thể dùng decorator @Extensions() ở cấp class và cấp method (ví dụ, trên query handler).
Sử dụng metadata tùy chỉnh
Logic tận dụng metadata tùy chỉnh này có thể phức tạp tùy ý. Ví dụ, bạn có thể tạo một interceptor đơn giản để lưu/log sự kiện mỗi lần method được gọi, hoặc một field middleware đối chiếu role cần thiết để lấy một field với quyền của caller (hệ thống phân quyền ở cấp field).
Để minh họa, hãy định nghĩa một checkRoleMiddleware so sánh role của user (ở đây hardcode sẵn) với role cần có để truy cập field mục tiêu:
export const checkRoleMiddleware: FieldMiddleware = async (
ctx: MiddlewareContext,
next: NextFn,
) => {
const { info } = ctx;
const { extensions } = info.parentType.getFields()[info.fieldName];
/**
* In a real-world application, the "userRole" variable
* should represent the caller's (user) role (for example, "ctx.user.role").
*/
const userRole = Role.USER;
if (userRole === extensions.role) {
// or just "return null" to ignore
throw new ForbiddenException(
`User does not have sufficient permissions to access "${info.fieldName}" field.`,
);
}
return next();
};
Với đoạn code này, ta có thể đăng ký một middleware cho field password, như sau:
@Field({ middleware: [checkRoleMiddleware] })
@Extensions({ role: Role.ADMIN })
password: string;
Ghi chú người dịch Có thể hình dung @Extensions() như việc gắn thêm một “custom tag” vào định nghĩa field trong GraphQL schema — tương tự cách bạn gắn struct tag trong Go (json:"...") hay annotation trong Laravel để mô tả metadata không ảnh hưởng logic type, rồi đọc lại metadata đó ở nơi khác (middleware, guard, interceptor) để ra quyết định runtime. Field middleware ở đây đóng vai trò tương tự middleware Express bọc quanh req/res, chỉ khác là nó bọc quanh việc resolve một field GraphQL cụ thể thay vì bọc quanh cả request HTTP.