☰ 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

Độ phức tạp (Complexity)

Bản dịch từ docs.nestjs.com/graphql/complexity

Cảnh báo Chương này chỉ áp dụng cho cách tiếp cận code first.

Query complexity (độ phức tạp của query) cho phép bạn định nghĩa mức độ phức tạp của từng field, và giới hạn query bằng một độ phức tạp tối đa. Ý tưởng là gán cho mỗi field một con số thể hiện độ phức tạp của nó. Mặc định phổ biến là gán độ phức tạp 1 cho mỗi field. Ngoài ra, cách tính độ phức tạp của một GraphQL query có thể được tùy biến thông qua các complexity estimator (bộ ước lượng độ phức tạp). Một complexity estimator là một hàm đơn giản dùng để tính độ phức tạp cho một field. Bạn có thể thêm bao nhiêu estimator tùy ý vào rule, chúng sẽ được thực thi lần lượt theo thứ tự. Estimator đầu tiên trả về một giá trị số sẽ được dùng làm độ phức tạp cho field đó.

Package @nestjs/graphql tích hợp rất tốt với các thư viện như graphql-query-complexity, thư viện cung cấp giải pháp phân tích chi phí (cost analysis). Với thư viện này, bạn có thể từ chối các query gửi đến GraphQL server của mình nếu chúng bị đánh giá là quá tốn kém để thực thi.

Cài đặt

Để bắt đầu sử dụng, trước tiên ta cài đặt package cần thiết.

$ npm install --save graphql-query-complexity

Bắt đầu

Sau khi cài đặt xong, ta có thể định nghĩa class ComplexityPlugin:

import { GraphQLSchemaHost } from '@nestjs/graphql';
import { Plugin } from '@nestjs/apollo';
import {
  ApolloServerPlugin,
  BaseContext,
  GraphQLRequestListener,
} from '@apollo/server';
import { GraphQLError } from 'graphql';
import {
  fieldExtensionsEstimator,
  getComplexity,
  simpleEstimator,
} from 'graphql-query-complexity';

@Plugin()
export class ComplexityPlugin implements ApolloServerPlugin {
  constructor(private gqlSchemaHost: GraphQLSchemaHost) {}

  async requestDidStart(): Promise<GraphQLRequestListener<BaseContext>> {
    const maxComplexity = 20;
    const { schema } = this.gqlSchemaHost;

    return {
      async didResolveOperation({ request, document }) {
        const complexity = getComplexity({
          schema,
          operationName: request.operationName,
          query: document,
          variables: request.variables,
          estimators: [
            fieldExtensionsEstimator(),
            simpleEstimator({ defaultComplexity: 1 }),
          ],
        });
        if (complexity > maxComplexity) {
          throw new GraphQLError(
            `Query is too complex: ${complexity}. Maximum allowed complexity: ${maxComplexity}`,
          );
        }
        console.log('Query Complexity:', complexity);
      },
    };
  }
}

Để minh họa, ở đây ta khai báo độ phức tạp tối đa cho phép là 20. Trong ví dụ trên, ta dùng 2 estimator: simpleEstimator và fieldExtensionsEstimator.

  • simpleEstimator: estimator đơn giản này trả về một độ phức tạp cố định cho mỗi field
  • fieldExtensionsEstimator: estimator này lấy giá trị độ phức tạp được khai báo trên từng field của schema

Gợi ý Đừng quên thêm class này vào mảng providers của một module bất kỳ.

Ghi chú người dịch Cơ chế estimator “chạy lần lượt, dùng kết quả đầu tiên trả về giá trị số” khá giống middleware chain trong Express: mỗi hàm có quyền xử lý rồi trả kết quả hoặc nhường cho hàm kế tiếp. Ở đây fieldExtensionsEstimator chạy trước để ưu tiên độ phức tạp bạn khai báo tường minh trên field (qua @Field({ complexity })), còn simpleEstimator chỉ là fallback khi field không khai báo gì. Đây là kỹ thuật chặn tấn công kiểu “query siêu sâu, siêu rộng” thường gặp khi client tự do lồng nested field trong GraphQL — điều mà REST API không gặp phải vì response shape đã cố định.

Độ phức tạp ở cấp field

Với plugin này, giờ ta có thể định nghĩa độ phức tạp cho bất kỳ field nào bằng cách khai báo thuộc tính complexity trong object option truyền vào decorator @Field(), như sau:

@Field({ complexity: 3 })
title: string;

Ngoài ra, bạn cũng có thể định nghĩa một hàm estimator:

@Field({ complexity: (options: ComplexityEstimatorArgs) => ... })
title: string;

Độ phức tạp ở cấp Query/Mutation

Bên cạnh đó, decorator @Query() và @Mutation() cũng có thể khai báo thuộc tính complexity, ví dụ:

@Query({ complexity: (options: ComplexityEstimatorArgs) => options.args.count * options.childComplexity })
items(@Args('count') count: number) {
  return this.itemsService.getItems({ count });
}

Ghi chú người dịch Mẫu count * childComplexity rất hữu ích khi field trả về một danh sách: độ phức tạp thực tế phụ thuộc vào số lượng item client yêu cầu (tham số count) nhân với độ phức tạp của các field con mà client chọn lấy trong mỗi item. Đây là cách tránh trường hợp một query items(count: 10000) { veryExpensiveField } lách qua được giới hạn mặc định chỉ vì bản thân field items có complexity thấp.

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.