☰ 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

Other features

Bản dịch từ docs.nestjs.com/graphql/other-features

Trong thế giới GraphQL, có rất nhiều tranh luận về cách xử lý các vấn đề như authentication (xác thực), hay side-effect (hiệu ứng phụ) của các operation. Chúng ta nên xử lý những thứ này bên trong business logic? Chúng ta nên dùng higher-order function để tăng cường query và mutation với logic authorization (phân quyền)? Hay chúng ta nên dùng schema directive? Không có một câu trả lời chung duy nhất cho những câu hỏi này.

Nest giúp giải quyết những vấn đề này bằng các tính năng cross-platform (đa nền tảng) của nó như guard và interceptor. Triết lý ở đây là giảm thiểu sự trùng lặp và cung cấp công cụ giúp tạo ra các ứng dụng có cấu trúc tốt, dễ đọc và nhất quán.

Overview (Tổng quan)

Bạn có thể dùng guard, interceptor, filter và pipe tiêu chuẩn theo cùng một cách với GraphQL như với bất kỳ ứng dụng RESTful nào. Ngoài ra, bạn có thể dễ dàng tạo decorator riêng bằng cách tận dụng tính năng custom decorator. Hãy cùng xem một ví dụ về GraphQL query handler.

@Query('author')
@UseGuards(AuthGuard)
async getAuthor(@Args('id', ParseIntPipe) id: number) {
  return this.authorsService.findOneById(id);
}

Như bạn thấy, GraphQL hoạt động với cả guard và pipe theo cùng một cách như HTTP REST handler. Vì vậy, bạn có thể chuyển logic authentication của mình sang một guard; bạn thậm chí có thể tái sử dụng cùng một guard class trên cả interface REST và GraphQL API. Tương tự, interceptor hoạt động trên cả hai loại ứng dụng theo cùng một cách:

@Mutation()
@UseInterceptors(EventsInterceptor)
async upvotePost(@Args('postId') postId: number) {
  return this.postsService.upvoteById({ id: postId });
}

Ghi chú người dịch Đây là điểm mạnh thực sự của kiến trúc Nest so với việc tự dựng GraphQL server bằng Apollo Server thuần: cùng một AuthGuard bạn viết cho REST controller có thể tái sử dụng nguyên vẹn cho GraphQL resolver, vì cả hai đều đi qua chung cơ chế ExecutionContext. Nếu bạn từng viết Express middleware riêng cho REST và một context function riêng cho Apollo Server, bạn sẽ thấy đây tiết kiệm khá nhiều code trùng lặp.

Execution context (Ngữ cảnh thực thi)

Vì GraphQL nhận một loại dữ liệu khác trong request đến, ngữ cảnh thực thi (execution context) mà cả guard và interceptor nhận được có phần khác biệt giữa GraphQL và REST. GraphQL resolver có một tập hợp argument riêng biệt: root, args, context, và info. Do đó guard và interceptor phải chuyển đổi ExecutionContext chung thành GqlExecutionContext. Việc này khá đơn giản:

import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';

@Injectable()
export class AuthGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    const ctx = GqlExecutionContext.create(context);
    return true;
  }
}

Object context GraphQL trả về bởi GqlExecutionContext.create() cung cấp một phương thức get cho mỗi argument của GraphQL resolver (ví dụ, getArgs(), getContext(), v.v.). Sau khi chuyển đổi, chúng ta có thể dễ dàng lấy ra bất kỳ argument GraphQL nào của request hiện tại.

Exception filters (Bộ lọc ngoại lệ)

Các exception filter tiêu chuẩn của Nest cũng tương thích với ứng dụng GraphQL. Giống như với ExecutionContext, ứng dụng GraphQL nên chuyển đổi object ArgumentsHost thành object GqlArgumentsHost.

@Catch(HttpException)
export class HttpExceptionFilter implements GqlExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const gqlHost = GqlArgumentsHost.create(host);
    return exception;
  }
}

Gợi ý Cả GqlExceptionFilter lẫn GqlArgumentsHost đều được import từ package @nestjs/graphql.

Lưu ý rằng khác với trường hợp REST, bạn không dùng object response gốc để tạo ra response.

Custom decorators (Decorator tùy chỉnh)

Như đã đề cập, tính năng custom decorator hoạt động như mong đợi với GraphQL resolver.

export const User = createParamDecorator(
  (data: unknown, ctx: ExecutionContext) =>
    GqlExecutionContext.create(ctx).getContext().user,
);

Dùng custom decorator @User() như sau:

@Mutation()
async upvotePost(
  @User() user: UserEntity,
  @Args('postId') postId: number,
) {}

Gợi ý Trong ví dụ trên, chúng ta giả định rằng object user được gán vào context của ứng dụng GraphQL của bạn.

Execute enhancers at the field resolver level (Thực thi enhancer ở cấp field resolver)

Trong ngữ cảnh GraphQL, Nest không chạy enhancer (tên gọi chung cho interceptor, guard và filter) ở cấp field xem issue này: chúng chỉ chạy cho phương thức @Query()/@Mutation() ở cấp cao nhất. Bạn có thể yêu cầu Nest thực thi interceptor, guard hoặc filter cho các phương thức được gắn decorator @ResolveField() bằng cách đặt tùy chọn fieldResolverEnhancers trong GqlModuleOptions. Truyền vào đó một danh sách 'interceptors', 'guards', và/hoặc 'filters' tùy theo nhu cầu:

GraphQLModule.forRoot({
  fieldResolverEnhancers: ['interceptors']
}),

Cảnh báo Việc bật enhancer cho field resolver có thể gây ra vấn đề hiệu năng khi bạn trả về nhiều bản ghi và field resolver của bạn được thực thi hàng nghìn lần. Vì lý do này, khi bạn bật fieldResolverEnhancers, chúng tôi khuyên bạn nên bỏ qua việc thực thi các enhancer không thực sự cần thiết cho field resolver của mình. Bạn có thể làm điều này bằng hàm hỗ trợ sau:

export function isResolvingGraphQLField(context: ExecutionContext): boolean {
  if (context.getType<GqlContextType>() === 'graphql') {
    const gqlContext = GqlExecutionContext.create(context);
    const info = gqlContext.getInfo();
    const parentType = info.parentType.name;
    return parentType !== 'Query' && parentType !== 'Mutation';
  }
  return false;
}

Ghi chú người dịch Cạm bẫy thực tế hay gặp: nếu schema của bạn có field resolver trả về danh sách lồng nhau (ví dụ Author.books cho mỗi tác giả trong danh sách 100 tác giả), bật fieldResolverEnhancers mà không lọc bằng isResolvingGraphQLField sẽ khiến guard/interceptor của bạn chạy lại 100 lần một cách không cần thiết — tương tự vấn đề N+1 query nhưng xảy ra ở tầng authorization/logging thay vì tầng database. Nên mặc định giữ tắt tùy chọn này trừ khi bạn thực sự cần enhancer ở cấp field.

Creating a custom driver (Tạo driver tùy chỉnh)

Nest cung cấp sẵn hai driver chính thức: @nestjs/apollo và @nestjs/mercurius, cũng như một API cho phép lập trình viên xây dựng driver tùy chỉnh mới. Với một custom driver, bạn có thể tích hợp bất kỳ thư viện GraphQL nào hoặc mở rộng tích hợp hiện có, thêm các tính năng bổ sung lên trên.

Ví dụ, để tích hợp package express-graphql, bạn có thể tạo driver class sau:

import { AbstractGraphQLDriver, GqlModuleOptions } from '@nestjs/graphql';
import { graphqlHTTP } from 'express-graphql';

class ExpressGraphQLDriver extends AbstractGraphQLDriver {
  async start(options: GqlModuleOptions<any>): Promise<void> {
    options = await this.graphQlFactory.mergeWithSchema(options);

    const { httpAdapter } = this.httpAdapterHost;
    httpAdapter.use(
      '/graphql',
      graphqlHTTP({
        schema: options.schema,
        graphiql: true,
      }),
    );
  }

  async stop() {}
}

Và sau đó dùng nó như sau:

GraphQLModule.forRoot({
  driver: ExpressGraphQLDriver,
});

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.