Cảnh báo Chương này chỉ áp dụng cho cách tiếp cận code first.
Field Middleware cho phép bạn chạy code tùy ý trước hoặc sau khi một field được resolve. Một field middleware có thể được dùng để chuyển đổi kết quả của một field, kiểm tra dữ liệu (validation) argument của field, hoặc thậm chí kiểm tra role ở cấp field (ví dụ, cần thiết để truy cập field đích mà middleware function được thực thi cho).
Bạn có thể gắn nhiều middleware function vào một field. Trong trường hợp này, chúng sẽ được gọi tuần tự theo chuỗi, nơi middleware trước quyết định có gọi middleware tiếp theo hay không. Thứ tự các middleware function trong mảng middleware rất quan trọng. Resolver đầu tiên là lớp “ngoài cùng” (most-outer), nên nó được thực thi đầu tiên và cuối cùng (tương tự package graphql-middleware). Resolver thứ hai là lớp “ngoài thứ nhì” (second-outer), nên nó được thực thi thứ hai và áp chót.
Bắt đầu (Getting started)
Hãy bắt đầu bằng cách tạo một middleware đơn giản sẽ log giá trị của field trước khi nó được gửi về client:
import { FieldMiddleware, MiddlewareContext, NextFn } from '@nestjs/graphql';
const loggerMiddleware: FieldMiddleware = async (
ctx: MiddlewareContext,
next: NextFn,
) => {
const value = await next();
console.log(value);
return value;
};
Gợi ý MiddlewareContext là một object gồm các argument giống hệt những gì mà GraphQL resolver function thường nhận được ({ source, args, context, info }), trong khi NextFn là một function cho phép bạn thực thi middleware tiếp theo trong stack (gắn với field này) hoặc field resolver thực sự.
Cảnh báo Các field middleware function không thể inject dependency hay truy cập DI container của Nest vì chúng được thiết kế để rất nhẹ và không nên thực hiện bất kỳ thao tác tốn thời gian nào (như truy vấn dữ liệu từ database). Nếu bạn cần gọi service bên ngoài/truy vấn dữ liệu từ nguồn dữ liệu, bạn nên làm điều đó trong một guard/interceptor gắn với root query/mutation handler và gán vào object context mà bạn có thể truy cập từ bên trong field middleware (cụ thể là từ object MiddlewareContext).
Ghi chú người dịch Đây là một ràng buộc quan trọng, không phải chỉ là khuyến nghị “best practice” suông: field middleware có thể chạy hàng nghìn lần cho một response (một lần cho mỗi phần tử trong danh sách), nên nếu bạn lỡ inject service và query DB ngay trong middleware, bạn dễ tạo ra vấn đề N+1 nghiêm trọng hơn cả N+1 query thông thường ở resolver. Cách đúng là chạy guard/interceptor một lần ở tầng root query/mutation, gom dữ liệu cần thiết vào context, rồi field middleware chỉ đọc lại từ đó.
Ghi chú rằng field middleware phải khớp với interface FieldMiddleware. Trong ví dụ trên, đầu tiên chúng ta chạy function next() (thực thi field resolver thực sự và trả về giá trị field) rồi sau đó, chúng ta log giá trị này ra terminal. Ngoài ra, giá trị trả về từ middleware function sẽ ghi đè hoàn toàn giá trị trước đó, và vì chúng ta không muốn thực hiện bất kỳ thay đổi nào, chúng ta chỉ đơn giản trả về giá trị gốc.
Với điều này, chúng ta có thể đăng ký middleware của mình trực tiếp trong decorator @Field(), như sau:
@ObjectType()
export class Recipe {
@Field({ middleware: [loggerMiddleware] })
title: string;
}
Giờ đây bất cứ khi nào chúng ta request field title của object type Recipe, giá trị gốc của field sẽ được log ra console.
Gợi ý Để tìm hiểu cách bạn có thể triển khai một hệ thống phân quyền ở cấp field bằng tính năng extensions, xem phần này.
Cảnh báo Field middleware chỉ có thể áp dụng cho các class ObjectType. Để biết thêm chi tiết, xem issue này.
Ngoài ra, như đã đề cập ở trên, chúng ta có thể kiểm soát giá trị của field từ bên trong middleware function. Để minh họa, hãy viết hoa tiêu đề của recipe (nếu có):
const value = await next();
return value?.toUpperCase();
Trong trường hợp này, mọi title sẽ tự động được viết hoa khi được request.
Tương tự, bạn có thể gắn một field middleware vào một custom field resolver (một phương thức được gắn decorator @ResolveField()), như sau:
@ResolveField(() => String, { middleware: [loggerMiddleware] })
title() {
return 'Placeholder';
}
Cảnh báo Trong trường hợp enhancer được bật ở cấp field resolver (đọc thêm), các field middleware function sẽ chạy trước bất kỳ interceptor, guard nào, v.v., gắn với method (nhưng sau các enhancer ở cấp root được đăng ký cho query hoặc mutation handler).
Global field middleware
Bên cạnh việc gắn middleware trực tiếp vào một field cụ thể, bạn cũng có thể đăng ký một hoặc nhiều middleware function ở phạm vi toàn cục (global). Trong trường hợp này, chúng sẽ tự động được gắn vào tất cả các field của các object type của bạn.
GraphQLModule.forRoot({
autoSchemaFile: 'schema.gql',
buildSchemaOptions: {
fieldMiddleware: [loggerMiddleware],
},
}),
Gợi ý Các field middleware function được đăng ký toàn cục sẽ được thực thi trước các middleware được đăng ký cục bộ (những middleware gắn trực tiếp vào field cụ thể).