☰ 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

CLI Plugin

Bản dịch từ docs.nestjs.com/graphql/cli-plugin

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

Hệ thống metadata reflection của TypeScript có một số giới hạn khiến nó không thể, ví dụ, xác định một class gồm những property nào hoặc nhận biết một property có phải là optional hay required hay không. Tuy nhiên, một số giới hạn này có thể được giải quyết tại thời điểm biên dịch. Nest cung cấp một plugin giúp tăng cường quá trình biên dịch TypeScript để giảm lượng code khung (boilerplate) cần viết.

Gợi ý Plugin này là tùy chọn (opt-in). Nếu muốn, bạn vẫn có thể tự khai báo tất cả decorator thủ công, hoặc chỉ khai báo một số decorator cụ thể khi cần.

Ghi chú người dịch Đây chính là cơ chế tương tự “code generation” mà nhiều framework Go dùng (ví dụ đọc struct tag rồi sinh code) — chỉ khác là thay vì chạy một bước generate riêng, plugin này gắn thẳng vào compiler TypeScript, chạy tại thời điểm biên dịch (build time) để phân tích AST và tự thêm decorator vào, thay vì bạn phải viết tay từng cái một.

Tổng quan

Plugin GraphQL sẽ tự động:

  • gắn @Field vào tất cả property của class input object, object type và args, trừ khi dùng @HideField
  • set property nullable dựa trên dấu hỏi chấm (ví dụ name?: string sẽ set nullable: true)
  • set property type dựa trên kiểu dữ liệu (hỗ trợ cả mảng)
  • sinh mô tả (description) cho property dựa trên comment (nếu introspectComments được set là true)

Lưu ý rằng tên file của bạn phải có một trong các hậu tố sau để được plugin phân tích: ['.input.ts', '.args.ts', '.entity.ts', '.model.ts'] (ví dụ, author.entity.ts). Nếu bạn dùng hậu tố khác, bạn có thể điều chỉnh hành vi của plugin bằng option typeFileNameSuffix (xem bên dưới).

Với những gì ta đã học được từ trước tới giờ, bạn phải viết trùng lặp khá nhiều code để package biết cách type của bạn nên được khai báo trong GraphQL như thế nào. Ví dụ, bạn có thể định nghĩa một class Author đơn giản như sau:

@ObjectType()
export class Author {
  @Field(type => ID)
  id: number;

  @Field({ nullable: true })
  firstName?: string;

  @Field({ nullable: true })
  lastName?: string;

  @Field(type => [Post])
  posts: Post[];
}

Điều này chưa hẳn là vấn đề lớn với các dự án cỡ vừa, nhưng nó sẽ trở nên dài dòng và khó bảo trì khi bạn có một tập lớn các class.

Bằng cách bật plugin GraphQL, định nghĩa class ở trên có thể được khai báo đơn giản như sau:

@ObjectType()
export class Author {
  @Field(type => ID)
  id: number;
  firstName?: string;
  lastName?: string;
  posts: Post[];
}

Plugin sẽ tự động thêm các decorator phù hợp ngay khi biên dịch, dựa trên cây cú pháp trừu tượng (Abstract Syntax Tree). Nhờ đó, bạn sẽ không phải vật lộn với các decorator @Field rải rác khắp code.

Gợi ý Plugin sẽ tự động sinh các property GraphQL còn thiếu, nhưng nếu bạn cần ghi đè chúng, chỉ cần set tường minh qua @Field().

Introspection comment

Khi bật tính năng introspection comment, CLI plugin sẽ sinh description cho các field dựa trên comment.

Ví dụ, với một property roles như sau:

/**
 * Danh sách các role của user
 */
@Field(() => [String], {
  description: `A list of user's roles`
})
roles: string[];

Bạn phải viết trùng lặp giá trị description. Khi bật introspectComments, CLI plugin có thể trích xuất các comment này và tự động cung cấp description cho property. Giờ đây, field ở trên có thể được khai báo đơn giản như sau:

/**
 * Danh sách các role của user
 */
roles: string[];

Sử dụng CLI plugin

Để bật plugin, mở nest-cli.json (nếu bạn dùng Nest CLI) và thêm cấu hình plugins sau:

{
  "collection": "@nestjs/schematics",
  "sourceRoot": "src",
  "compilerOptions": {
    "plugins": ["@nestjs/graphql"]
  }
}

Bạn có thể dùng property options để tùy chỉnh hành vi của plugin.

{
  "collection": "@nestjs/schematics",
  "sourceRoot": "src",
  "compilerOptions": {
    "plugins": [
      {
        "name": "@nestjs/graphql",
        "options": {
          "typeFileNameSuffix": [".input.ts", ".args.ts"],
          "introspectComments": true
        }
      }
    ]
  }
}

Property options phải tuân theo interface sau:

export interface PluginOptions {
  typeFileNameSuffix?: string[];
  introspectComments?: boolean;
}
Option Mặc định Mô tả
typeFileNameSuffix ['.input.ts', '.args.ts', '.entity.ts', '.model.ts'] Hậu tố file type GraphQL
introspectComments false Nếu set true, plugin sẽ sinh description cho property dựa trên comment

Nếu bạn không dùng CLI mà có cấu hình webpack riêng, bạn có thể dùng plugin này kết hợp với ts-loader:

getCustomTransformers: (program: any) => ({
  before: [require('@nestjs/graphql/plugin').before({}, program)]
}),

SWC builder

Với setup tiêu chuẩn (không phải monorepo), để dùng CLI Plugin với SWC builder, bạn cần bật type checking, như mô tả tại đây.

$ nest start -b swc --type-check

Với setup monorepo, làm theo hướng dẫn tại đây.

$ npx ts-node src/generate-metadata.ts
# HOẶC npx ts-node apps/{YOUR_APP}/src/generate-metadata.ts

Giờ đây, file metadata đã được tuần tự hóa (serialize) phải được load bởi method của GraphQLModule, như dưới đây:

import metadata from './metadata.js'; // <-- file được tự động sinh bởi "PluginMetadataGenerator"

GraphQLModule.forRoot<...>({
  ..., // các option khác
  metadata,
}),

Ghi chú người dịch Vấn đề mà SWC gặp phải ở đây là do bản chất: SWC compile file TypeScript sang JavaScript rất nhanh nhưng bỏ qua bước type-check đầy đủ, trong khi plugin GraphQL cần đọc AST kèm thông tin kiểu (type information) để suy ra nullable, type cho field. Vì vậy Nest phải tách riêng một bước sinh metadata (generate-metadata.ts) chạy bằng ts-node (có type-check đầy đủ) rồi nạp kết quả đó vào lúc khởi động ứng dụng.

Tích hợp với ts-jest (test e2e)

Khi chạy test e2e với plugin này được bật, bạn có thể gặp vấn đề khi biên dịch schema. Ví dụ, một trong những lỗi thường gặp nhất là:

Object type <name> must define one or more fields.

Điều này xảy ra vì cấu hình jest không import plugin @nestjs/graphql/plugin ở đâu cả.

Để khắc phục, tạo file sau trong thư mục test e2e của bạn:

const transformer = require('@nestjs/graphql/plugin');

module.exports.name = 'nestjs-graphql-transformer';
// bạn nên thay đổi số version mỗi khi bạn thay đổi cấu hình bên dưới - nếu không, jest sẽ không phát hiện thay đổi
module.exports.version = 1;

module.exports.factory = (cs) => {
  return transformer.before(
    {
      // các option của @nestjs/graphql/plugin (có thể để trống)
    },
    cs.program, // "cs.tsCompiler.program" cho các phiên bản Jest cũ hơn (<= v27)
  );
};

Với cấu hình này, import AST transformer vào file cấu hình jest của bạn. Mặc định (trong ứng dụng starter), file cấu hình test e2e nằm trong thư mục test và có tên jest-e2e.json.

{
  ... // các cấu hình khác
  "globals": {
    "ts-jest": {
      "astTransformers": {
        "before": ["<đường dẫn đến file vừa tạo ở trên>"]
      }
    }
  }
}

Nếu bạn dùng jest@^29, hãy dùng đoạn code dưới đây, vì cách tiếp cận trước đó đã bị deprecated.

{
  ... // các cấu hình khác
  "transform": {
    "^.+\\.(t|j)s$": [
      "ts-jest",
      {
        "astTransformers": {
          "before": ["<đường dẫn đến file vừa tạo ở trên>"]
        }
      }
    ]
  }
}

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.