[graphql] GraphQL 與 Apollo Server 使用教學 Part 2
Args
假設今天想要單獨查詢書本的資訊要怎麼做呢? 一般的作法都是利用 id 來找尋資料,而在 GraphQL 一樣可以靠外部參數來找尋特定的資料,來看以下範例:
在 type 定義時必須多加一條規則,也就是程式碼第 13 行,括號的意思是接收的參數名稱與參數型態。
const { gql } = require("apollo-server");const typeDefs = gql` type Book { id: ID! name: String! author: String! publish: String! } type Query { books: [Book!] book(id: ID): Book }`;module.exports = typeDefs;
同樣的也必須在 resolvers 實作,先來看如何傳入外部參數。
在下方的 Variables 內定義 bookId,名稱可以自己取,但要跟 query 傳入的名稱一樣。
const Books = require("../tempData");const resolvers = { Query: { books: () => { return Books; }, book: (parent, args) => { console.log(args); // output : { id : '1' } return Books.find((book) => book.id === +args.id); }, },};module.exports = resolvers;
Context
Context 有點像是 Middleware,Context 可以拿來做一些驗證,像是 JWT,直接來看範例。
要啟用 Context 必須在 Server 做設定,這邊直接回傳 request。
const { ApolloServer } = require("apollo-server");const resolvers = require("./schema/resolvers");const typeDefs = require("./schema/typeDefs");const server = new ApolloServer({ typeDefs, resolvers, context: ({ req }) => { return req; },});server.listen().then(({ url }) => { console.log(`Server Url : ${url}`);});
回傳的 context 可以在 resolvers 做使用
const { Authors, Books } = require("../tempData");const resolvers = { Query: { books: (parent, args, context) => { console.log(context.headers); return Books; }, },};module.exports = resolvers;
直接在後台查詢 books 後查看 TERMINAL。
Context 2
當然,也可以直接將範例資料傳入 Context,之後就不用在 resolvers 去引入範例資料!
server.js
const { ApolloServer } = require("apollo-server");const resolvers = require("./schema/resolvers");const typeDefs = require("./schema/typeDefs");const { Books } = require("./tempData");const server = new ApolloServer({ typeDefs, resolvers, context: { Books, },});server.listen().then(({ url }) => { console.log(`Server Url : ${url}`);});
const resolvers = { Query: { books: (parent, args, context) => { return context.Books; }, },};module.exports = resolvers;
Fragment
當回傳值太多的時候,或是多個 type 回傳的值都一樣時可以使用Fragment來簡化,直接來看以下範例。
假設要回傳的值有以下:
query Books {
books {
id
name
author
publish
}
}
則可以利用Fragment來將要回傳的值個別獨立出來。
query Books {
books {
...BookFragement
}
}
fragment BookFragement on Book {
id
name
author
publish
}
Interface
善用Interface能夠有效管理型態,Interface type 可以提供一組 fields 讓不同的物件(Object)共享,我們直接來看範例。
我們用以下範例來看,如果要在 GraphQL 回傳下面的範例資料,則必須在定義 type 的時候把所有需要的欄位補上。
const Animals = [ { name: "狼蛛", footLength: 123, }, { name: "奇異鳥", footLength: 123, wingLength: 456, wing: false, },];
像是這樣
const { gql } = require("apollo-server");const typeDefs = gql` type Animal { name: String footLength: Int wingLength: Int wing: Boolean } type Query { animals: [Animal!]! }`;module.exports = typeDefs;
resolvers.js
const { Animals } = require("./tempData");
const resolvers = {
Query: {
animals: () => {
return Animals;
},
},
};
module.exports = resolvers;
但會發現執行 query 後,第一筆資料的其他欄位會是 null,這並不是我們想要的結果,因為如果該 type 有加上 ! 的話,回傳的資料含有 null 就會報錯。
這時候,可以利用 interface 來解決上述的問題~
const { gql } = require("apollo-server");const typeDefs = gql` interface Animal { name: String footLength: Int } type Spider implements Animal { name: String footLength: Int } type Bird implements Animal { name: String footLength: Int wingLength: Int wing: Boolean } type Query { animals: [Animal] }`;module.exports = typeDefs;
可以看到我們把範例資料的共同鍵值抽取出來,獨立成另外的 type ,這邊要注意,只要該 type 有 implements interface,一定要把 interface 內的 type 在重新定義一次且型態都要一樣,類似抽象類別。
const { Animals } = require("./tempData");const resolvers = { Animal: { __resolveType(obj) { //檢查obj裡面是否有wingLength的鍵值(key) if (obj.wingLength) { //有的話就回傳Bird的type return "Bird"; } //沒有的話就回傳 Spider type return "Spider"; }, }, Query: { animals: () => { return Animals; }, },};module.exports = resolvers;
那 query 的部分要怎麼下呢?
query Query {
animals {
... on Spider {
name
footLength
}
... on Bird {
name
wingLength
footLength
wing
}
}
}
Union
Union 與 Interface 蠻相似的,可以看到以下的定義方式與 Interface 的差異, 最大的差異就在於如果 type 有 implements interface,則該 type 必須將 interface 的內的 type 全部定義,否則會報錯,而 Union 則不用。
const Animals = [ { name: "狼蛛", footLength: 123, }, { name: "奇異鳥", wingLength: 456, },];
const { gql } = require("apollo-server");const typeDefs = gql` union Animal = Spider | Bird type Spider { name: String footLength: Int } type Bird { name: String wingLength: Int } type Query { animals: [Animal] }`;module.exports = typeDefs;
const { Animals } = require("./tempData");const resolvers = { Animal: { __resolveType(obj) { if (obj.footLength) { return "Spider"; } if (obj.wingLength) { return "Bird"; } }, }, Query: { animals: (parent, args, context) => { return Animals; }, },};module.exports = resolvers;