☰ Mục lục series
Tài liệu NestJS tiếng Việt
  1. Nhập môn (Overview)
    1. Giới thiệu
    2. First steps
    3. Controllers
    4. Providers
    5. Modules
    6. Middleware
    7. Exception filters
    8. Pipes
    9. Guards
    10. Interceptors
    11. Custom decorators
  2. Nền tảng (Fundamentals)
    1. Custom providers
    2. Asynchronous providers
    3. Dynamic modules
    4. Injection scopes
    5. Circular dependency
    6. Module reference
    7. Lazy-loading modules
    8. Execution context
    9. Lifecycle events
    10. Discovery service
    11. Platform agnosticism
    12. Testing
  3. Kỹ thuật (Techniques)
    1. Configuration
    2. Database (SQL)
    3. MongoDB
    4. Validation
    5. Caching
    6. Serialization
    7. Versioning
    8. Task scheduling
    9. Queues
    10. Logging
    11. Cookies
    12. Events
    13. Compression
    14. File upload
    15. Streaming files
    16. HTTP module
    17. Session
    18. MVC
    19. Performance (Fastify)
    20. Server-Sent Events
  4. Bảo mật (Security)
    1. Authentication
    2. Authorization
    3. Encryption & Hashing
    4. Helmet
    5. CORS
    6. CSRF
    7. Rate limiting
    8. Vòng đời request
  5. OpenAPI (Swagger)
    1. Introduction
    2. Types and parameters
    3. Operations
    4. Security
    5. Mapped types
    6. Decorators
    7. CLI plugin
    8. Other features
  6. Recipes
    1. REPL
    2. CRUD generator
    3. SWC (fast compiler)
    4. Passport (auth)
    5. Hot reload
    6. MikroORM
    7. Router module
    8. Health checks (Terminus)
    9. CQRS
    10. Prisma
    11. Serve static
    12. Commander
    13. Async local storage
  7. WebSockets
    1. Gateways
    2. Exception filters
    3. Pipes
    4. Guards
    5. Interceptors
    6. Adapters
  8. Microservices
    1. Overview
    2. Redis
    3. MQTT
    4. NATS
    5. RabbitMQ
    6. Kafka
    7. gRPC
    8. Custom transporters
    9. Exception filters
    10. Pipes
    11. Pre-request hooks
    12. Guards
    13. Interceptors
  9. GraphQL
    1. Quick start
    2. Resolvers
    3. Mutations
    4. Subscriptions
    5. Scalars
    6. Directives
    7. Interfaces
    8. Unions and enums
    9. Field middleware
    10. Mapped types
    11. Plugins
    12. Complexity
    13. Extensions
    14. CLI plugin
    15. Generating SDL
    16. Sharing models
    17. Other features
    18. Federation
  10. CLI & triển khai
    1. CLI overview
    2. Workspaces
    3. Libraries
    4. Usage
    5. Scripts
    6. Deployment
    7. Standalone apps
  11. FAQ
    1. Serverless
    2. HTTP adapter
    3. Keep-Alive connections
    4. Global path prefix
    5. Raw body
    6. Hybrid application
    7. HTTPS & multiple servers
    8. Common errors
  12. Phụ lục
    1. Bảng thuật ngữ
GraphQL

Field middleware

Bản dịch từ docs.nestjs.com/graphql/field-middleware

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ể).

Nội dung gốc: NestJS Documentation © Kamil Myśliwiec, giấy phép MIT. Bản dịch và ghi chú: Lê Văn Dũng.