☰ 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

Mutations (Mutation)

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

Hầu hết các cuộc thảo luận về GraphQL tập trung vào việc lấy dữ liệu (data fetching), nhưng bất kỳ nền tảng dữ liệu hoàn chỉnh nào cũng cần một cách để sửa đổi dữ liệu phía server. Trong REST, bất kỳ request nào cũng có thể gây ra side-effect trên server, nhưng best practice khuyến nghị chúng ta không nên sửa dữ liệu trong các GET request. GraphQL cũng tương tự - về mặt kỹ thuật, bất kỳ query nào cũng có thể được implement để gây ra một lần ghi dữ liệu. Tuy nhiên, giống như REST, cách làm được khuyến nghị là tuân theo quy ước: mọi thao tác gây ra việc ghi dữ liệu nên được gửi tường minh thông qua một mutation (đọc thêm tại đây).

Ghi chú người dịch Nếu bạn từng làm REST hoặc GraphQL trong Go/PHP, có thể hiểu mutation đơn giản là “endpoint ghi dữ liệu” (POST/PUT/DELETE), còn query là “endpoint đọc dữ liệu” (GET) - khác biệt là GraphQL không dựa vào HTTP method để phân biệt hai loại này (cả hai thường đi qua cùng một endpoint POST /graphql), mà dựa vào từ khóa mutation/query khai báo trong request và trong schema.

Tài liệu chính thức của Apollo sử dụng ví dụ mutation upvotePost(). Mutation này implement một phương thức để tăng giá trị thuộc tính votes của một post. Để tạo một mutation tương đương trong Nest, chúng ta sẽ sử dụng decorator @Mutation().

Code first

Hãy thêm một phương thức khác vào AuthorResolver đã dùng ở phần trước (xem resolvers).

@Mutation(() => Post)
async upvotePost(@Args({ name: 'postId', type: () => Int }) postId: number) {
  return this.postsService.upvoteById({ id: postId });
}

Gợi ý Tất cả decorator (ví dụ: @Resolver, @ResolveField, @Args, v.v.) đều được export từ package @nestjs/graphql.

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

type Mutation {
  upvotePost(postId: Int!): Post
}

Phương thức upvotePost() nhận postId (Int) làm tham số và trả về một entity Post đã được cập nhật. Vì những lý do đã giải thích ở phần resolvers, chúng ta phải khai báo tường minh kiểu dữ liệu mong đợi.

Nếu mutation cần nhận một object làm tham số, chúng ta có thể tạo một input type. Input type là một kiểu object đặc biệt có thể được truyền vào như một tham số (đọc thêm tại đây). Để khai báo một input type, dùng decorator @InputType().

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

@InputType()
export class UpvotePostInput {
  @Field()
  postId: number;
}

Gợi ý Decorator @InputType() nhận một object option làm tham số, vì vậy bạn có thể, ví dụ, chỉ định mô tả (description) cho input type. Lưu ý rằng, do giới hạn của hệ thống metadata reflection trong TypeScript, bạn phải dùng decorator @Field để khai báo kiểu dữ liệu thủ công, hoặc dùng CLI plugin.

Sau đó chúng ta có thể dùng type này trong resolver class:

@Mutation(() => Post)
async upvotePost(
  @Args('upvotePostData') upvotePostData: UpvotePostInput,
) {}

Schema first

Hãy mở rộng AuthorResolver đã dùng ở phần trước (xem resolvers).

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

Lưu ý rằng ở trên chúng ta giả định business logic đã được chuyển sang PostsService (truy vấn post và tăng thuộc tính votes của nó). Logic bên trong class PostsService có thể đơn giản hoặc phức tạp tùy theo nhu cầu. Điểm chính của ví dụ này là cho thấy resolver có thể tương tác với các provider khác như thế nào.

Ghi chú người dịch Đây chính là điểm nhiều người vibe-code NestJS hay bỏ qua: resolver chỉ nên đóng vai trò “route handler” mỏng, còn logic nghiệp vụ thật sự (query DB, kiểm tra điều kiện, ghi log…) nên nằm trong service - giống hệt việc bạn tách route/controller khỏi service trong Express hay tách handler khỏi usecase trong Go. Nhồi logic thẳng vào resolver sẽ khiến việc test và tái sử dụng logic (ví dụ gọi lại từ REST controller) khó hơn nhiều.

Bước cuối cùng là thêm mutation của chúng ta vào file định nghĩa type (type definitions) hiện có.

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 Mutation {
  upvotePost(postId: Int!): Post
}

Mutation upvotePost(postId: Int!): Post giờ đã có thể được gọi như một phần trong GraphQL API của ứng dụng.

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.