GraphQL 简介

Mutation:新增、更新、删除

掌握 GraphQL Mutation 的用法,学会用 input 类型组织入参,实现数据的新增、更新和删除。

🎯 引言

到目前为止,我们的接口只能读数据。实际项目里,注册账号、修改资料、删除记录这些写操作同样重要。GraphQL 里负责写操作的就是 Mutation

学完这篇文章,你能说清楚 Query 和 Mutation 的分工,学会用 input 类型组织复杂入参,并动手实现用户数据的新增、更新、删除三个完整操作,让接口真正读写双全。


🧱 Mutation 是什么

Mutation 是 GraphQL 中用于修改数据的操作类型,地位上和 Query 平级。分工很明确:

  • Query:只读,查多少次都不该改变数据,类似 REST 的 GET。
  • Mutation:会改变数据,涵盖 REST 里 POST、PUT、DELETE 干的事。

写法上两者几乎一样,只是最外层的关键字从 query 换成 mutation

mutation {
    createUser(input: { username: "阿伟", age: 25 }) {
        id
        username
    }
}

注意 Mutation 也要声明返回字段。这是个很实用的设计:创建或修改完成后,服务端把最新数据按你指定的字段返回,客户端不用再发一次查询去刷新。

这个分工是约定而不是强制,语法上在 Query 里改数据也能跑通。但请遵守约定:客户端和缓存工具都会假定 Query 没有副作用,破坏约定会带来难以排查的问题。

🛠 input 输入类型

创建用户要传好几个字段,如果平铺成参数,声明会越来越长:

# 参数一多就不好维护了
type Mutation {
    createUser(username: String!, age: Int, city: String): User!
}

GraphQL 为此提供了 input 类型:把一组入参打包成一个输入对象。

input CreateUserInput {
    username: String!
    age: Int
    city: String
}

type Mutation {
    createUser(input: CreateUserInput!): User!
}

inputtype 的写法几乎一样,区别在于 input 专门用于入参type 用于出参,两者不能混用。这样做的好处:

  • 入参结构集中管理,字段增减只改一处。
  • 配合变量传参特别整齐,一个 JSON 对象搞定:
{
    "input": {
        "username": "阿伟",
        "age": 25
    }
}
mutation Create($input: CreateUserInput!) {
    createUser(input: $input) {
        id
        username
    }
}

✨ 完整的增删改实现

把三个操作一次实现完整。Schema 部分:

input CreateUserInput {
    username: String!
    age: Int
    city: String
}

input UpdateUserInput {
    username: String
    age: Int
    city: String
}

type Mutation {
    createUser(input: CreateUserInput!): User!
    updateUser(id: ID!, input: UpdateUserInput!): User
    deleteUser(id: ID!): Boolean!
}

注意几个设计细节:

  • UpdateUserInput 的字段全部可为空:更新时只传要改的字段,没传的不动。
  • updateUser 返回 User(可空):找不到目标时返回 null,客户端据此提示「用户不存在」。
  • deleteUser 返回 Boolean!:用 true / false 明确表示删除成功与否。

resolvers 部分:

server.js
const resolvers = {
    Mutation: {
        createUser: (parent, args) => {
            const user = {
                id: String(users.length + 1),
                ...args.input,
            };
            users.push(user);
            return user;
        },
        updateUser: (parent, args) => {
            const user = users.find((item) => item.id === args.id);
            if (!user) {
                return null;
            }
            Object.assign(user, args.input);
            return user;
        },
        deleteUser: (parent, args) => {
            const index = users.findIndex((item) => item.id === args.id);
            if (index === -1) {
                return false;
            }
            users.splice(index, 1);
            return true;
        },
    },
};

逻辑都是熟悉的数组操作:push 新增、Object.assign 合并更新、splice 删除。resolver 拿到 args.input 后怎么写数据,和写普通 Node.js 代码没有区别。

启动服务,在 Sandbox 里依次试验:

mutation {
    updateUser(id: "1", input: { age: 21 }) {
        username
        age
    }
}
mutation {
    deleteUser(id: "2")
}
我们的数据放在内存数组里,重启服务后改动全部丢失。学习阶段这样最直观,真实项目把 resolver 里的数组操作换成数据库操作即可,Schema 和客户端查询完全不用变。

💡 一次 Mutation 执行多个操作

和 Query 一样,一次 Mutation 请求里可以写多个操作,用别名区分:

mutation {
    a: createUser(input: { username: "张三" }) {
        id
    }
    b: createUser(input: { username: "李四" }) {
        id
    }
}

这里有个重要的执行规则:同一层级的多个 Mutation 字段是串行执行的,一个一个来;而 Query 的多个字段是并行执行的。这样设计是为了保证写操作的顺序可预期,比如「先创建再更新」不会乱序。


🧾 小节总结

  • Mutation 负责写操作,Query 负责只读查询,这是必须遵守的约定。
  • Mutation 也要声明返回字段,通常返回修改后的数据,省去客户端二次查询。
  • input 类型打包入参,配合变量传参,结构清晰易维护。
  • 更新操作的 input 字段全部可空,只传要改的字段;删除可用 Boolean 返回结果。
  • 同一层级的多个 Mutation 串行执行,多个 Query 并行执行。

❓ 知识问答

Q1:为什么不直接用 REST 风格的增删改?

GraphQL 用统一的入口和语法覆盖读写,客户端不用记一堆 URL 和 HTTP 方法,而且返回值可以按需声明,这是它的设计取向。两种风格没有绝对优劣。

Q2:input 类型能嵌套 input 吗?

可以,比如 CreateArticleInput 里包含一个 CreateAuthorInput 字段。但建议保持扁平,嵌套太深会让调用方难以维护。

Q3:删除用户后,他的文章怎么办?

这是个业务设计问题:可以连带删除,也可以留着标记作者已注销。GraphQL 本身不管这些,逻辑都写在 resolver 里,按业务需要实现。

Q4:更新时想把某个字段改回「空」怎么办?

null 即可,比如 input: { city: null }Object.assign 会把它覆盖成 null。注意「不传这个字段」和「传 null」是两回事,前者不改动,后者会清空。

Q5:Mutation 里可以查询吗?

可以。resolver 是普通函数,里面既能写数据也能读数据,返回修改后的完整对象就是「写 + 读」的组合。


🧪 小练习

练习一:给文章实现 createArticle(input: CreateArticleInput!): Article!,input 包含 titleauthorId,创建后返回文章的 id、标题。

# 请在这里编写代码

练习二:实现 updateArticle(id: ID!, input: UpdateArticleInput!): Article,要求找不到文章时返回 null,然后在 Sandbox 里验证两种情况。

const resolvers = {
    Mutation: {
        // 请在这里编写代码
    },
};

🎉 恭喜你已经掌握 GraphQL 的增删改啦!目前服务还是独立运行的,下一篇我们把它集成进 Express,和已有的 REST 接口共存于同一个项目。