GraphQL - Mutation
GraphQL - Mutation(变更操作)
Section titled “GraphQL - Mutation(变更操作)”虽然 Query(查询操作)用于获取数据,但 Mutation(变更操作)用于修改服务器端数据。这包括创建新数据、更新现有数据或删除数据等操作。可以将 mutation 理解为 GraphQL 中对应 REST 请求的 POST、PUT、PATCH 或 DELETE。
Mutation 在 GraphQL 模式(Schema)中的根 Mutation 类型下被定义为字段(Field)。与 Query 类似,它们可以接受参数(通常分组在 input 对象类型中),并返回特定数据,通常是刚刚被修改的数据。
Mutation 语法
Section titled “Mutation 语法”Mutation 请求看起来与 Query 非常相似,但它必须以 mutation 关键字开头。
# 基本 Mutation 语法mutation OptionalOperationName { mutationFieldName(argumentName: "value", ...) { # Fields to return after the mutation succeeds field1 field2 ... }}与 Query 的主要区别:
- 关键字: 必须使用
mutation关键字。 - 执行: 虽然服务器通常可以并行执行 Query 字段,但顶层 Mutation 字段通常是串行执行的(一个接一个),以确保可预测的副作用(Side effects)并避免竞态条件(Race conditions)。
- 返回值: Mutation 返回反映状态变化的数据至关重要。这使得客户端(如 Apollo Client)能够准确地更新其缓存(Cache)。
Mutation 的 Input 类型
Section titled “Mutation 的 Input 类型”为每个 mutation 的参数定义一个单独的、专用的 input 对象类型是一个强烈建议的约定和最佳实践。这使得 mutation 更容易演进(稍后添加可选字段)并保持参数列表的整洁。
# 带有 Input 类型的模式定义
# 用于创建学生的 Input 对象类型input CreateStudentInput { firstName: String! lastName: String! collegeId: ID! # Assuming college must exist email: String # Optional field}
# 定义 Mutation 返回的 Student 类型type Student { id: ID! firstName: String! lastName: String! email: String collegeId: ID!}
# 使用 Input 类型定义 Mutationtype Mutation { createStudent(input: CreateStudentInput!): Student # 返回创建的学生对象}示例:创建学生
Section titled “示例:创建学生”让我们实现上面定义的 createStudent mutation。
步骤 1:服务器和数据设置
Section titled “步骤 1:服务器和数据设置”假设一个基本的 Apollo Server 设置和一个带有添加学生功能的模拟数据源 (db.js)。
// db.js(模拟数据 - 带有创建函数的示例)let students = [ { id: 'S1001', firstName: 'Mohtashim', lastName: 'Mohammad', collegeId: 'col-102' }, { id: 'S1002', firstName: 'Kannan', lastName: 'Sudhakaran', collegeId: 'col-101' },];let nextStudentId = 1003; // 模拟用的简单 ID 生成
export const db = { students: { list: () => students, get: (id) => students.find(s => s.id === id), create: (input) => { const newStudent = { id: `S${nextStudentId++}`, ...input // 展开 input 对象中的属性 }; students.push(newStudent); console.log("Current students:", students); // 记录当前状态 return newStudent; // 返回新创建的学生对象 } }, // 假设 Colleges 数据存在,用于需要时进行验证 colleges: { get: (id) => { /* ... 查找 College 的逻辑 ... */ return { id: id, name: 'Some College'}; }, }};步骤 2:定义模式(schema.graphql 或在 server.js 中)
Section titled “步骤 2:定义模式(schema.graphql 或在 server.js 中)”使用之前定义的模式,包括 CreateStudentInput、Student 和 Mutation 类型。
#graphqltype Query { # 有效模式至少需要一个 Query 字段 _: Boolean}
input CreateStudentInput { firstName: String! lastName: String! collegeId: ID! email: String}
type Student { id: ID! firstName: String! lastName: String! email: String collegeId: ID!}
type Mutation { createStudent(input: CreateStudentInput!): Student}步骤 3:为 Mutation 创建解析器(Resolver)
Section titled “步骤 3:为 Mutation 创建解析器(Resolver)”在 Mutation 类型解析器映射中,实现 createStudent 字段的解析器函数。
// resolvers.jsimport { db } from './db.js';import { GraphQLError } from 'graphql';
const resolvers = { Query: { _: () => true, // 占位符 Query 的解析器 },
Mutation: { createStudent: (parent, args, context, info) => { // input 对象可在 args.input 中获取 const { input } = args; console.log('Resolving: Mutation.createStudent with input:', input); // 正在解析:Mutation.createStudent,输入为:
// --- 可选验证 --- // 示例:检查 collegeId 是否存在(在实际应用中) const collegeExists = db.colleges.get(input.collegeId); if (!collegeExists) { throw new GraphQLError(`College with ID ${input.collegeId} not found.`, { extensions: { code: 'BAD_USER_INPUT' } }); } // 根据需要添加其他验证(邮箱格式等) // --- 验证结束 ---
// 调用数据源函数创建学生 try { const newStudent = db.students.create(input); // 返回新创建的学生对象 // 这与模式中定义的 'Student' 返回类型相匹配 return newStudent; } catch (error) { // 处理创建过程中可能出现的错误 console.error("Error creating student:", error); throw new GraphQLError('Failed to create student.', { extensions: { code: 'INTERNAL_SERVER_ERROR' } }); } }, },
// 可选:如果需要,添加 Student 解析器(例如,用于解析 College 对象) // Student: { // college: (parent) => db.colleges.get(parent.collegeId) // }};
export default resolvers;要点:
- 解析器通过
args.input访问参数。 - 它执行可选的验证(检查
collegeId是否存在)。 - 它调用适当的数据源方法(
db.students.create)来执行写入操作。 - 至关重要的是,它返回新创建的学生对象。这使得客户端能够获取服务器生成的 ID 并更新其状态/缓存。
步骤 4:运行和测试 Mutation
Section titled “步骤 4:运行和测试 Mutation”将模式和解析器集成到你的 Apollo Server 设置中并运行服务器(npm start)。
使用 Apollo Sandbox(http://localhost:9000/graphql)执行 mutation。记住使用 mutation 关键字。
# 使用变量的 Mutation 请求mutation AddNewStudent($studentData: CreateStudentInput!) { createStudent(input: $studentData) { # Specify which fields of the created student you want back id firstName lastName email collegeId }}在 ‘Variables’ 面板中,提供输入数据:
{ "studentData": { "firstName": "Tim", "lastName": "George", "collegeId": "col-101", "email": "tim.g@example.com" }}执行 mutation。响应应包含新创建学生的数据,包括其服务器生成的 ID:
{ "data": { "createStudent": { "id": "S1003", // 示例生成的 ID "firstName": "Tim", "lastName": "George", "email": "tim.g@example.com", "collegeId": "col-101" } }}之后你可以通过运行 students 或 studentById 查询来验证创建是否成功,或者检查你的模拟数据控制台日志。
在 Mutation 中返回相关数据
Section titled “在 Mutation 中返回相关数据”因为 mutation 可以返回模式中定义的任何类型,你可以设计它们同时返回相关数据,从而节省客户端额外的获取操作。
示例:如果你希望 createStudent mutation 返回学生以及其关联的学院详细信息:
# 1. 修改模式:添加 College 类型并关联type College { id: ID! name: String!}
type Student { id: ID! # ... 其他字段 college: College # 添加关联字段}
type Mutation { createStudent(input: CreateStudentInput!): Student # 返回类型仍然是 Student}
# 2. 添加 Student.college 的解析器(如 Query 章节所示)# 在 resolvers.js 中# Student: {# college: (parent) => db.colleges.get(parent.collegeId)# }
# 3. 修改 Mutation 请求以请求嵌套数据mutation AddStudentAndGetCollege($studentData: CreateStudentInput!) { createStudent(input: $studentData) { id firstName college { # 请求嵌套的 college 数据 id name } }}createStudent mutation 解析器本身没有显著变化(它仍然只是创建并返回学生对象)。当客户端在 mutation 的返回选择集中请求嵌套的 college 字段时,Student.college 解析器会自动为新创建的学生对象运行,然后在最终响应发送之前将 College 数据填充进去。