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.