上一篇講到的 Query 是 CRUD 裡面的 R (讀取),這篇要講的 Mutation 則是負責處理 C (建立)、U (更新)、D (刪除) 等等的其他操作。延續上一篇的範例,這邊會繼續完成實作新增使用者
、更改使用者名稱
、刪除使用者
三個 Mutation。
雖然 GraphQL 的強項是 Query,但一個完整的系統還是要提供其他操作的能力,因此有了 Mutation。Mutation 看起來跟 Query 差不多,有欄位名稱、參數、回傳 Type:
type Mutation {
mutationName(arg: Type!): ResponseType
}
雖然應該用 Query 來完成沒有副作用的查詢、用 Mutation 來執行其他有副作用的操作,但 GraphQL 跟 REST 一樣無法限制背後的實作。
不過 Query 跟 Mutation 還是有一些實際上的差別,官網上面有說明,當有許多欄位時 Query 會平行的去跑,而 Mutation 則會一個一個執行,這也可以在原始碼中看得到。
官網原文為:While query fields are executed in parallel, mutation fields run in series, one after the other.
接著就直接進入實際的實作來一窺究竟吧。
首先,先來複習一下上一篇所寫完的一些部分,usersById
是用來放置使用者資料的 Map:
const usersById = {
1: {
id: 1,
name: 'chentsulin',
},
};
而 GraphQLUser
則是用來 Resolve User 資料的 Class:
class GraphQLUser {
constructor({ id, name }) {
this.id = id;
this.name = name;
}
// ...
}
複習完之後,我們先來加 addUser
的 Mutation,添加使用者的時候接一個 String
的 name
參數,並回傳建好的 User
:
type Mutation {
addUser(name: String!): User
}
接著要把 addUser
的 Resolving Function 也放到 rootValue
裡面,裡面包含的就是建立使用者的基礎邏輯:
let nextId = 2
const mutations = {
addUser: ({ name }) => {
const newUser = {
id: nextId,
name,
};
usersById[nextId] = newUser;
nextId++;
return new GraphQLUser(newUser);
},
}
exports.rootValue = {
...queries,
...mutations,
};
要注意最下面這邊用到還在 Stage 3 的 Object Spread Properties Syntax,所以必須用 Babel 處理。不想麻煩的話可以利用
Object.assign
。
完成後執行下面的 Mutation 來看一下結果:
mutation {
addUser(name: "Master") {
id
name
}
}
再去查 users
確實能得到多一筆結果:
{
users {
id
name
}
}
在 Mutation Type 上面加上 renameUser
這個 Mutation,接 Int
的 id
與 String
的 name
,回傳修改完的 User
:
type Mutation {
addUser(name: String!): User
renameUser(id: Int!, name: String!): User
}
renameUser
的邏輯則更簡單明瞭,只是修改 name
的部分:
const mutation = {
// ...
renameUser: ({ id, name }) => {
usersById[id].name = name;
return new GraphQLUser(usersById[id]);
},
};
完成後執行下面的 Mutation 來看一下結果:
mutation {
renameUser(id: 1, name: "abcdefg") {
id
name
}
}
在 Mutation Type 上面加上 removeUser
這個 Mutation,接 Int
的 id
然後回傳一個自定義的 Object Type RemoveUserPayload
,上面包含被刪除的使用者 ID - deletedUserId
:
type RemoveUserPayload {
deletedUserId: Int!
}
type Mutation {
addUser(name: String!): User
renameUser(id: Int!, name: String!): User
removeUser(id: Int!): RemoveUserPayload
}
用 delete
來在 usersById
上面刪除對應的資料:
const mutation = {
// ...
removeUser: ({ id }) => {
delete usersById[id];
return {
deletedUserId: id,
};
},
};
完成後執行下面的 Mutation 來看一下結果:
mutation {
removeUser(id: 1) {
deletedUserId
}
}
如果一開始就要做很難的東西自然會很難上手,不過我們從使用者的新增、修改、刪除、查詢的範例中簡單的看過了一下 GraphQL 的基礎,其他很多延伸的部分都環繞在程式語言跟後端本身的技術上,到這邊想必各位讀者已經可以自己去摸索更多的可能性了。
剛做完這篇 分享一下關於Object.assign 的寫法
addUser: (name) => Object.assign({}, mutations.addUser(name)),
renameUser: (id, name) => Object.assign({}, mutations.renameUser(id, name)),
removeUser: (id) => Object.assign({}, mutations.removeUser(id))