☰ 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

Unions

Bản dịch từ docs.nestjs.com/graphql/unions-and-enums

Union type khá giống interface, nhưng chúng không được phép chỉ định bất kỳ field chung nào giữa các type (đọc thêm tại đây). Union hữu ích khi bạn cần trả về các kiểu dữ liệu rời rạc (disjoint) từ một field duy nhất.

Code first

Để định nghĩa một GraphQL union type, chúng ta phải định nghĩa các class mà union này sẽ được cấu thành từ đó. Theo ví dụ từ tài liệu Apollo, chúng ta sẽ tạo hai class. Đầu tiên, Book:

import { Field, ObjectType } from '@nestjs/graphql';

@ObjectType()
export class Book {
  @Field()
  title: string;
}

Và sau đó Author:

import { Field, ObjectType } from '@nestjs/graphql';

@ObjectType()
export class Author {
  @Field()
  name: string;
}

Với hai class này, đăng ký union ResultUnion bằng hàm createUnionType export từ package @nestjs/graphql:

export const ResultUnion = createUnionType({
  name: 'ResultUnion',
  types: () => [Author, Book] as const,
});

Cảnh báo Mảng trả về bởi thuộc tính types của hàm createUnionType nên được gán const assertion. Nếu không có const assertion, một file khai báo (declaration file) sai sẽ được sinh ra lúc compile, và lỗi sẽ xảy ra khi sử dụng nó từ một project khác.

Ghi chú người dịch as const ở đây tương tự “readonly tuple” — nó khóa mảng lại thành kiểu tuple với từng phần tử literal thay vì kiểu (typeof Author | typeof Book)[] chung chung. Nest cần thông tin kiểu chính xác đó để sinh ra type TypeScript đúng cho union khi bạn dùng công cụ export typing (giống cách bạn cần readonly tuple trong Go khi muốn giữ thứ tự và kiểu cụ thể của từng phần tử thay vì slice interface{}).

Bây giờ, chúng ta có thể tham chiếu ResultUnion trong query:

@Query(() => [ResultUnion])
search(): Array<typeof ResultUnion> {
  return [new Author(), new Book()];
}

Kết quả sẽ sinh ra phần schema GraphQL sau ở dạng SDL:

type Author {
  name: String!
}

type Book {
  title: String!
}

union ResultUnion = Author | Book

type Query {
  search: [ResultUnion!]!
}

Function resolveType() mặc định do thư viện sinh ra sẽ xác định type dựa trên giá trị trả về từ phương thức resolver. Điều đó có nghĩa là việc trả về instance của class thay vì literal JavaScript object là bắt buộc.

Để cung cấp một function resolveType() tùy chỉnh, truyền thuộc tính resolveType vào object options của hàm createUnionType(), như sau:

export const ResultUnion = createUnionType({
  name: 'ResultUnion',
  types: () => [Author, Book] as const,
  resolveType(value) {
    if (value.name) {
      return Author;
    }
    if (value.title) {
      return Book;
    }
    return null;
  },
});

Schema first

Để định nghĩa một union trong cách tiếp cận schema first, chỉ cần tạo một GraphQL union bằng SDL.

type Author {
  name: String!
}

type Book {
  title: String!
}

union ResultUnion = Author | Book

Sau đó, bạn có thể dùng tính năng sinh typing (như đã trình bày ở chương quick start) để sinh ra định nghĩa TypeScript tương ứng:

export class Author {
  name: string;
}

export class Book {
  title: string;
}

export type ResultUnion = Author | Book;

Union yêu cầu thêm một field __resolveType trong resolver map để xác định union nên resolve về type nào. Ngoài ra, lưu ý rằng class ResultUnionResolver phải được đăng ký như một provider trong một module bất kỳ. Hãy tạo một class ResultUnionResolver và định nghĩa phương thức __resolveType.

@Resolver('ResultUnion')
export class ResultUnionResolver {
  @ResolveField()
  __resolveType(value) {
    if (value.name) {
      return 'Author';
    }
    if (value.title) {
      return 'Book';
    }
    return null;
  }
}

Gợi ý Tất cả decorator đều được export từ package @nestjs/graphql.

Enums

Enumeration type là một kiểu scalar đặc biệt bị giới hạn trong một tập giá trị được phép cụ thể (đọc thêm tại đây). Điều này cho phép bạn:

  • kiểm tra rằng bất kỳ argument nào thuộc type này đều nằm trong các giá trị được phép
  • truyền đạt qua hệ thống type rằng một field sẽ luôn là một trong số hữu hạn giá trị

Code first

Khi dùng cách tiếp cận code first, bạn định nghĩa một GraphQL enum type bằng cách đơn giản là tạo một TypeScript enum.

export enum AllowedColor {
  RED,
  GREEN,
  BLUE,
}

Với enum này, đăng ký AllowedColor bằng hàm registerEnumType export từ package @nestjs/graphql:

registerEnumType(AllowedColor, {
  name: 'AllowedColor',
});

Bây giờ bạn có thể tham chiếu AllowedColor trong các type của mình:

@Field(type => AllowedColor)
favoriteColor: AllowedColor;

Kết quả sẽ sinh ra phần schema GraphQL sau ở dạng SDL:

enum AllowedColor {
  RED
  GREEN
  BLUE
}

Để cung cấp mô tả cho enum, truyền thuộc tính description vào hàm registerEnumType().

registerEnumType(AllowedColor, {
  name: 'AllowedColor',
  description: 'The supported colors.',
});

Để cung cấp mô tả cho từng giá trị của enum, hoặc để đánh dấu một giá trị là deprecated, truyền thuộc tính valuesMap, như sau:

registerEnumType(AllowedColor, {
  name: 'AllowedColor',
  description: 'The supported colors.',
  valuesMap: {
    RED: {
      description: 'The default color.',
    },
    BLUE: {
      deprecationReason: 'Too blue.',
    },
  },
});

Điều này sẽ sinh ra schema GraphQL sau ở dạng SDL:

"""
The supported colors.
"""
enum AllowedColor {
  """
  The default color.
  """
  RED
  GREEN
  BLUE @deprecated(reason: "Too blue.")
}

Schema first

Để định nghĩa một enumerator trong cách tiếp cận schema first, chỉ cần tạo một GraphQL enum bằng SDL.

enum AllowedColor {
  RED
  GREEN
  BLUE
}

Sau đó bạn có thể dùng tính năng sinh typing (như đã trình bày ở chương quick start) để sinh ra định nghĩa TypeScript tương ứng:

export enum AllowedColor {
  RED
  GREEN
  BLUE
}

Đôi khi backend cần dùng một giá trị nội bộ khác cho enum so với giá trị công khai trong API. Trong ví dụ này, API chứa RED, nhưng trong resolver ta có thể dùng #f00 thay vào đó (đọc thêm tại đây). Để làm được điều này, khai báo một resolver object cho enum AllowedColor:

export const allowedColorResolver: Record<keyof typeof AllowedColor, any> = {
  RED: '#f00',
};

Ghi chú người dịch Cơ chế “internal value” này giống với việc bạn map một enum backend sang giá trị lưu trong DB hoặc dùng trong logic nội bộ (ví dụ PHP-Laravel enum cast, hoặc Go dùng một map để chuyển đổi giữa hằng số nội bộ và chuỗi trả về client). Client vẫn thấy RED trong schema, nhưng khi resolver đọc field đó, Nest sẽ trả #f00 thay vì RED thô.

Gợi ý Tất cả decorator đều được export từ package @nestjs/graphql.

Sau đó dùng resolver object này cùng với thuộc tính resolvers của phương thức GraphQLModule#forRoot(), như sau:

GraphQLModule.forRoot({
  resolvers: {
    AllowedColor: allowedColorResolver,
  },
});

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.