☰ 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

Subscriptions

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

Bên cạnh việc lấy dữ liệu bằng query và sửa dữ liệu bằng mutation, đặc tả GraphQL còn hỗ trợ một loại operation thứ ba, gọi là subscription. GraphQL subscription là cách để đẩy dữ liệu từ server tới các client chọn lắng nghe (listen) các message thời gian thực từ server. Subscription tương tự query ở chỗ chúng cũng chỉ định một tập field cần trả về cho client, nhưng thay vì trả về ngay một kết quả duy nhất, một kênh (channel) sẽ được mở ra và một kết quả sẽ được gửi cho client mỗi khi một sự kiện cụ thể xảy ra trên server.

Ghi chú người dịch Nếu bạn quen WebSocket gateway của Nest (@nestjs/websockets) hoặc Socket.IO, subscription GraphQL cũng chạy trên WebSocket, nhưng thay vì bạn tự định nghĩa event name và payload tùy ý, GraphQL bắt bạn phải khai báo trước schema (kiểu trả về) cho từng subscription, giống hệt cách query/mutation phải khai báo type. Đây là điểm khác biệt lớn nhất so với việc tự dựng WebSocket thủ công bằng Express + ws hoặc socket.io.

Một trường hợp sử dụng phổ biến của subscription là thông báo cho phía client về các sự kiện cụ thể, ví dụ việc tạo một object mới, các field được cập nhật, v.v. (đọc thêm tại đây).

Bật subscription với driver Apollo

Subscription được truyền tải qua WebSocket bởi package graphql-ws, package này không được đóng gói kèm theo @nestjs/apollo. Cài nó trước:

$ npm i --save graphql-ws

Sau đó bật transport một cách tường minh:

GraphQLModule.forRoot<ApolloDriverConfig>({
  driver: ApolloDriver,
  subscriptions: {
    'graphql-ws': true,
  },
}),

Cảnh báo Việc hỗ trợ subscriptions-transport-ws đã bị loại bỏ trong @nestjs/graphql v14. Package này không còn là một dependency nữa và key 'subscriptions-transport-ws' không còn được chấp nhận trong object option subscriptions. Nếu bạn đang nâng cấp, hãy cài graphql-ws, thay key đó bằng 'graphql-ws', và cập nhật client của bạn để nói chuyện theo giao thức mới hơn - hai giao thức này không tương thích ở tầng truyền tải (wire-incompatible), nên các client còn dùng subscriptions-transport-ws sẽ không kết nối được. Cũng lưu ý rằng chữ ký (signature) của callback onConnect khác nhau giữa hai giao thức: với graphql-ws, nó nhận vào connection context, và các giá trị context bổ sung phải nằm trong field extra của context đó (xem Xác thực qua WebSocket bên dưới).

Code first

Để tạo một subscription bằng cách tiếp cận code first, chúng ta dùng decorator @Subscription() (được export từ package @nestjs/graphql) và class PubSub từ package graphql-subscriptions, class này cung cấp một API publish/subscribe đơn giản.

Subscription handler dưới đây đảm nhiệm việc đăng ký lắng nghe (subscribing) một sự kiện bằng cách gọi PubSub#asyncIterableIterator. Phương thức này nhận một tham số duy nhất, triggerName, tương ứng với tên topic của sự kiện.

const pubSub = new PubSub();

@Resolver(() => Author)
export class AuthorResolver {
  // ...
  @Subscription(() => Comment)
  commentAdded() {
    return pubSub.asyncIterableIterator('commentAdded');
  }
}

Gợi ý Tất cả decorator đều được export từ package @nestjs/graphql, còn class PubSub được export từ package graphql-subscriptions.

Lưu ý PubSub là một class cung cấp một API publish và subscribe đơn giản. Đọc thêm về nó tại đây. Lưu ý rằng tài liệu Apollo cảnh báo implementation mặc định không phù hợp cho production (đọc thêm tại đây). Ứng dụng production nên dùng một implementation PubSub được hậu thuẫn bởi một external store (đọc thêm tại đây).

Ghi chú người dịch PubSub mặc định chỉ chạy trong bộ nhớ (in-memory) của một process - tương tự việc bạn tự implement EventEmitter của Node để publish/subscribe nội bộ. Vấn đề là khi bạn scale app ra nhiều instance (nhiều pod/container), một event publish ở instance A sẽ không tới được client đang subscribe ở instance B, vì mỗi instance có PubSub riêng, không chia sẻ trạng thái. Đây là lý do production cần một PubSub backed bởi Redis hay message broker (giống việc bạn cần Redis pub/sub thay vì EventEmitter khi scale WebSocket server ngang trong Express/Socket.IO).

Đoạn code trên sẽ sinh ra phần schema GraphQL sau ở dạng SDL:

type Subscription {
  commentAdded(): Comment!
}

Lưu ý rằng theo định nghĩa, subscription trả về một object với một thuộc tính cấp cao nhất (top level) duy nhất, có key là tên của subscription. Tên này hoặc được kế thừa từ tên của phương thức subscription handler (tức commentAdded ở trên), hoặc được cung cấp tường minh bằng cách truyền một option với key name làm tham số thứ hai cho decorator @Subscription(), như minh họa bên dưới.

@Subscription(() => Comment, {
  name: 'commentAdded',
})
subscribeToCommentAdded() {
  return pubSub.asyncIterableIterator('commentAdded');
}

Cách viết này sinh ra cùng một SDL như ví dụ code trước, nhưng cho phép chúng ta tách rời tên phương thức khỏi tên subscription.

Publish (phát) sự kiện

Bây giờ, để publish sự kiện, chúng ta dùng phương thức PubSub#publish. Phương thức này thường được dùng bên trong một mutation để kích hoạt một cập nhật phía client khi một phần của object graph đã thay đổi. Ví dụ:

@Mutation(() => Comment)
async addComment(
  @Args('postId', { type: () => Int }) postId: number,
  @Args('comment', { type: () => Comment }) comment: CommentInput,
) {
  const newComment = this.commentsService.addComment({ id: postId, comment });
  pubSub.publish('commentAdded', { commentAdded: newComment });
  return newComment;
}

Phương thức PubSub#publish nhận một triggerName (một lần nữa, hãy nghĩ đây là tên topic của sự kiện) làm tham số đầu tiên, và một event payload làm tham số thứ hai. Như đã đề cập, theo định nghĩa, subscription trả về một giá trị, và giá trị đó có một hình dạng (shape) cụ thể. Hãy nhìn lại SDL được sinh ra cho subscription commentAdded của chúng ta:

type Subscription {
  commentAdded(): Comment!
}

Điều này cho chúng ta biết rằng subscription phải trả về một object với tên thuộc tính cấp cao nhất là commentAdded, có giá trị là một object Comment. Điểm quan trọng cần lưu ý là hình dạng của event payload do phương thức PubSub#publish phát ra phải tương ứng với hình dạng của giá trị mà subscription được mong đợi trả về. Vì vậy, trong ví dụ trên, câu lệnh pubSub.publish('commentAdded', { commentAdded: newComment }) publish một sự kiện commentAdded với payload có hình dạng phù hợp. Nếu các hình dạng này không khớp nhau, subscription của bạn sẽ thất bại trong giai đoạn kiểm tra hợp lệ (validation) của GraphQL.

Lọc subscription (Filtering subscriptions)

Để lọc bỏ các sự kiện cụ thể, đặt thuộc tính filter thành một hàm lọc (filter function). Hàm này hoạt động tương tự hàm được truyền cho filter của array. Nó nhận hai tham số: payload chứa event payload (do bên publish sự kiện gửi), và variables chứa bất kỳ tham số nào được truyền vào trong request subscription. Nó trả về một boolean xác định liệu sự kiện này có nên được publish tới các client đang lắng nghe hay không.

@Subscription(() => Comment, {
  filter: (payload, variables) =>
    payload.commentAdded.title === variables.title,
})
commentAdded(@Args('title') title: string) {
  return pubSub.asyncIterableIterator('commentAdded');
}

Biến đổi payload của subscription (Mutating subscription payloads)

Để biến đổi (mutate) event payload trước khi publish, đặt thuộc tính resolve thành một hàm. Hàm này nhận event payload (do bên publish sự kiện gửi) và trả về giá trị phù hợp.

@Subscription(() => Comment, {
  resolve: value => value,
})
commentAdded() {
  return pubSub.asyncIterableIterator('commentAdded');
}

Lưu ý Nếu bạn dùng option resolve, bạn nên trả về payload chưa bọc (unwrapped) (ví dụ, với ví dụ của chúng ta, trả về trực tiếp một object newComment, không phải một object { commentAdded: newComment }).

Nếu bạn cần truy cập các provider đã được inject (ví dụ, dùng một service bên ngoài để kiểm tra tính hợp lệ của dữ liệu), hãy dùng cấu trúc sau.

@Subscription(() => Comment, {
  resolve(this: AuthorResolver, value) {
    // "this" tham chiếu tới một instance của "AuthorResolver"
    return value;
  }
})
commentAdded() {
  return pubSub.asyncIterableIterator('commentAdded');
}

Cấu trúc tương tự cũng áp dụng được với filter:

@Subscription(() => Comment, {
  filter(this: AuthorResolver, payload, variables) {
    // "this" tham chiếu tới một instance của "AuthorResolver"
    return payload.commentAdded.title === variables.title;
  }
})
commentAdded() {
  return pubSub.asyncIterableIterator('commentAdded');
}

Schema first

Để tạo một subscription tương đương trong Nest, chúng ta sẽ dùng decorator @Subscription().

const pubSub = new PubSub();

@Resolver('Author')
export class AuthorResolver {
  // ...
  @Subscription()
  commentAdded() {
    return pubSub.asyncIterableIterator('commentAdded');
  }
}

Để lọc bỏ các sự kiện cụ thể dựa trên context và các tham số, đặt thuộc tính filter.

@Subscription('commentAdded', {
  filter: (payload, variables) =>
    payload.commentAdded.title === variables.title,
})
commentAdded() {
  return pubSub.asyncIterableIterator('commentAdded');
}

Để biến đổi payload trước khi publish, chúng ta có thể dùng một hàm resolve.

@Subscription('commentAdded', {
  resolve: value => value,
})
commentAdded() {
  return pubSub.asyncIterableIterator('commentAdded');
}

Nếu bạn cần truy cập các provider đã được inject (ví dụ, dùng một service bên ngoài để kiểm tra tính hợp lệ của dữ liệu), hãy dùng cấu trúc sau:

@Subscription('commentAdded', {
  resolve(this: AuthorResolver, value) {
    // "this" tham chiếu tới một instance của "AuthorResolver"
    return value;
  }
})
commentAdded() {
  return pubSub.asyncIterableIterator('commentAdded');
}

Cấu trúc tương tự cũng áp dụng được với filter:

@Subscription('commentAdded', {
  filter(this: AuthorResolver, payload, variables) {
    // "this" tham chiếu tới một instance của "AuthorResolver"
    return payload.commentAdded.title === variables.title;
  }
})
commentAdded() {
  return pubSub.asyncIterableIterator('commentAdded');
}

Bước cuối cùng là cập nhật file định nghĩa type.

type Author {
  id: Int!
  firstName: String
  lastName: String
  posts: [Post]
}

type Post {
  id: Int!
  title: String
  votes: Int
}

type Query {
  author(id: Int!): Author
}

type Comment {
  id: String
  content: String
}

type Subscription {
  commentAdded(title: String!): Comment
}

Với điều này, chúng ta đã tạo ra một subscription duy nhất commentAdded(title: String!): Comment. Bạn có thể tìm một ví dụ implementation đầy đủ tại đây.

PubSub

Chúng ta đã khởi tạo một instance PubSub cục bộ (local) ở trên. Cách tiếp cận được khuyến nghị là định nghĩa PubSub như một provider và inject nó thông qua constructor (dùng decorator @Inject()). Điều này cho phép chúng ta tái sử dụng cùng một instance trong toàn bộ ứng dụng. Ví dụ, định nghĩa một provider như sau, sau đó inject 'PUB_SUB' ở nơi cần dùng.

{
  provide: 'PUB_SUB',
  useValue: new PubSub(),
}

Tùy chỉnh subscriptions server

Để tùy chỉnh subscriptions server (ví dụ, thay đổi path), dùng thuộc tính option subscriptions với graphql-ws:

GraphQLModule.forRoot<ApolloDriverConfig>({
  driver: ApolloDriver,
  subscriptions: {
    'graphql-ws': {
      path: '/graphql',
    },
  },
});

Xác thực qua WebSocket (Authentication over WebSockets)

Việc kiểm tra xem người dùng đã được xác thực hay chưa có thể thực hiện bên trong hàm callback onConnect mà bạn có thể chỉ định trong option subscriptions.

Với graphql-ws, callback onConnect nhận vào connection context, bao gồm cả connectionParams.

GraphQLModule.forRoot<ApolloDriverConfig>({
  driver: ApolloDriver,
  subscriptions: {
    'graphql-ws': {
      onConnect: (context: Context<any>) => {
        const { connectionParams, extra } = context;
        const authToken = connectionParams.authToken;
        if (!isValid(authToken)) {
          throw new Error('Token is not valid');
        }
        // khi dùng graphql-ws, các giá trị context bổ sung nên được lưu trong field extra
        extra.user = { user: {} };
      },
    },
  },
  context: ({ extra }) => {
    // giờ bạn có thể truy cập giá trị context bổ sung của mình thông qua field extra
  },
});

Ghi chú người dịch Đây là điểm khác biệt với middleware xác thực HTTP thông thường (Express, Laravel…): kết nối WebSocket chỉ được xác thực một lần lúc bắt tay (handshake) qua onConnect, không phải trên từng message như HTTP middleware chạy trên từng request. Nếu token hết hạn giữa chừng, bạn phải tự xử lý việc ngắt kết nối - Nest không tự động làm việc này giúp bạn.

Bật subscription với driver Mercurius

Để bật subscription, đặt thuộc tính subscription thành true.

GraphQLModule.forRoot<MercuriusDriverConfig>({
  driver: MercuriusDriver,
  subscription: true,
}),

Gợi ý Bạn cũng có thể truyền một object option để thiết lập một emitter tùy chỉnh, kiểm tra các kết nối đến, v.v. Đọc thêm tại đây (xem subscription).

Code first

Để tạo một subscription bằng cách tiếp cận code first, chúng ta dùng decorator @Subscription() (được export từ package @nestjs/graphql) và class PubSub từ package mercurius, class này cung cấp một API publish/subscribe đơn giản.

Subscription handler dưới đây đảm nhiệm việc đăng ký lắng nghe (subscribing) một sự kiện bằng cách gọi PubSub#asyncIterableIterator. Phương thức này nhận một tham số duy nhất, triggerName, tương ứng với tên topic của sự kiện.

@Resolver(() => Author)
export class AuthorResolver {
  // ...
  @Subscription(() => Comment)
  commentAdded(@Context('pubsub') pubSub: PubSub) {
    return pubSub.subscribe('commentAdded');
  }
}

Gợi ý Tất cả decorator dùng trong ví dụ trên đều được export từ package @nestjs/graphql, còn class PubSub được export từ package mercurius.

Lưu ý PubSub là một class cung cấp một API publish và subscribe đơn giản. Xem phần này để biết cách đăng ký một class PubSub tùy chỉnh.

Đoạn code trên sẽ sinh ra phần schema GraphQL sau ở dạng SDL:

type Subscription {
  commentAdded(): Comment!
}

Lưu ý rằng theo định nghĩa, subscription trả về một object với một thuộc tính cấp cao nhất duy nhất, có key là tên của subscription. Tên này hoặc được kế thừa từ tên của phương thức subscription handler (tức commentAdded ở trên), hoặc được cung cấp tường minh bằng cách truyền một option với key name làm tham số thứ hai cho decorator @Subscription(), như minh họa bên dưới.

@Subscription(() => Comment, {
  name: 'commentAdded',
})
subscribeToCommentAdded(@Context('pubsub') pubSub: PubSub) {
  return pubSub.subscribe('commentAdded');
}

Cách viết này sinh ra cùng một SDL như ví dụ code trước, nhưng cho phép chúng ta tách rời tên phương thức khỏi tên subscription.

Publish (phát) sự kiện

Bây giờ, để publish sự kiện, chúng ta dùng phương thức PubSub#publish. Phương thức này thường được dùng bên trong một mutation để kích hoạt một cập nhật phía client khi một phần của object graph đã thay đổi. Ví dụ:

@Mutation(() => Comment)
async addComment(
  @Args('postId', { type: () => Int }) postId: number,
  @Args('comment', { type: () => Comment }) comment: CommentInput,
  @Context('pubsub') pubSub: PubSub,
) {
  const newComment = this.commentsService.addComment({ id: postId, comment });
  await pubSub.publish({
    topic: 'commentAdded',
    payload: {
      commentAdded: newComment
    }
  });
  return newComment;
}

Như đã đề cập, theo định nghĩa, subscription trả về một giá trị, và giá trị đó có một hình dạng cụ thể. Hãy nhìn lại SDL được sinh ra cho subscription commentAdded của chúng ta:

type Subscription {
  commentAdded(): Comment!
}

Điều này cho chúng ta biết rằng subscription phải trả về một object với tên thuộc tính cấp cao nhất là commentAdded, có giá trị là một object Comment. Điểm quan trọng cần lưu ý là hình dạng của event payload do phương thức PubSub#publish phát ra phải tương ứng với hình dạng của giá trị mà subscription được mong đợi trả về. Vì vậy, trong ví dụ trên, câu lệnh pubSub.publish({ topic: 'commentAdded', payload: { commentAdded: newComment } }) publish một sự kiện commentAdded với payload có hình dạng phù hợp. Nếu các hình dạng này không khớp nhau, subscription của bạn sẽ thất bại trong giai đoạn kiểm tra hợp lệ của GraphQL.

Lọc subscription (Filtering subscriptions)

Để lọc bỏ các sự kiện cụ thể, đặt thuộc tính filter thành một hàm lọc. Hàm này hoạt động tương tự hàm được truyền cho filter của array. Nó nhận hai tham số: payload chứa event payload (do bên publish sự kiện gửi), và variables chứa bất kỳ tham số nào được truyền vào trong request subscription. Nó trả về một boolean xác định liệu sự kiện này có nên được publish tới các client đang lắng nghe hay không.

@Subscription(() => Comment, {
  filter: (payload, variables) =>
    payload.commentAdded.title === variables.title,
})
commentAdded(@Args('title') title: string, @Context('pubsub') pubSub: PubSub) {
  return pubSub.subscribe('commentAdded');
}

Nếu bạn cần truy cập các provider đã được inject (ví dụ, dùng một service bên ngoài để kiểm tra tính hợp lệ của dữ liệu), hãy dùng cấu trúc sau.

@Subscription(() => Comment, {
  filter(this: AuthorResolver, payload, variables) {
    // "this" tham chiếu tới một instance của "AuthorResolver"
    return payload.commentAdded.title === variables.title;
  }
})
commentAdded(@Args('title') title: string, @Context('pubsub') pubSub: PubSub) {
  return pubSub.subscribe('commentAdded');
}

Schema first

Để tạo một subscription tương đương trong Nest, chúng ta sẽ dùng decorator @Subscription().

const pubSub = new PubSub();

@Resolver('Author')
export class AuthorResolver {
  // ...
  @Subscription()
  commentAdded(@Context('pubsub') pubSub: PubSub) {
    return pubSub.subscribe('commentAdded');
  }
}

Để lọc bỏ các sự kiện cụ thể dựa trên context và các tham số, đặt thuộc tính filter.

@Subscription('commentAdded', {
  filter: (payload, variables) =>
    payload.commentAdded.title === variables.title,
})
commentAdded(@Context('pubsub') pubSub: PubSub) {
  return pubSub.subscribe('commentAdded');
}

Nếu bạn cần truy cập các provider đã được inject (ví dụ, dùng một service bên ngoài để kiểm tra tính hợp lệ của dữ liệu), hãy dùng cấu trúc sau:

@Subscription('commentAdded', {
  filter(this: AuthorResolver, payload, variables) {
    // "this" tham chiếu tới một instance của "AuthorResolver"
    return payload.commentAdded.title === variables.title;
  }
})
commentAdded(@Context('pubsub') pubSub: PubSub) {
  return pubSub.subscribe('commentAdded');
}

Bước cuối cùng là cập nhật file định nghĩa type.

type Author {
  id: Int!
  firstName: String
  lastName: String
  posts: [Post]
}

type Post {
  id: Int!
  title: String
  votes: Int
}

type Query {
  author(id: Int!): Author
}

type Comment {
  id: String
  content: String
}

type Subscription {
  commentAdded(title: String!): Comment
}

Với điều này, chúng ta đã tạo ra một subscription duy nhất commentAdded(title: String!): Comment.

PubSub

Trong các ví dụ trên, chúng ta đã dùng emitter PubSub mặc định (mqemitter). Cách tiếp cận được khuyến nghị (cho production) là dùng mqemitter-redis. Ngoài ra, bạn cũng có thể cung cấp một implementation PubSub tùy chỉnh (đọc thêm tại đây).

GraphQLModule.forRoot<MercuriusDriverConfig>({
  driver: MercuriusDriver,
  subscription: {
    emitter: require('mqemitter-redis')({
      port: 6579,
      host: '127.0.0.1',
    }),
  },
});

Ghi chú người dịch mqemitter mặc định cũng giống PubSub mặc định của Apollo - chỉ chạy trong bộ nhớ của một process. Nếu bạn deploy nhiều instance của app (horizontal scaling), bạn bắt buộc phải chuyển sang mqemitter-redis hoặc một emitter phân tán khác, nếu không client subscribe vào instance này sẽ không nhận được event publish từ instance khác - lỗi rất dễ gặp và khó debug khi mới đưa app lên production với nhiều replica.

Xác thực qua WebSocket (Authentication over WebSockets)

Việc kiểm tra xem người dùng đã được xác thực hay chưa có thể thực hiện bên trong hàm callback verifyClient mà bạn có thể chỉ định trong option subscription.

verifyClient sẽ nhận object info làm tham số đầu tiên, bạn có thể dùng nó để lấy header của request.

GraphQLModule.forRoot<MercuriusDriverConfig>({
  driver: MercuriusDriver,
  subscription: {
    verifyClient: (info, next) => {
      const authorization = info.req.headers?.authorization as string;
      if (!authorization?.startsWith('Bearer ')) {
        return next(false);
      }
      next(true);
    },
  }
}),

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.