跳至主要内容

[graphql] GraphQL 與 Apollo Server 使用教學 Part 2

影片講解 Part 1

影片講解 Part 2

影片講解 Part 3

Args

假設今天想要單獨查詢書本的資訊要怎麼做呢? 一般的作法都是利用 id 來找尋資料,而在 GraphQL 一樣可以靠外部參數來找尋特定的資料,來看以下範例:

在 type 定義時必須多加一條規則,也就是程式碼第 13 行,括號的意思是接收的參數名稱與參數型態。

typeDefs.js
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 傳入的名稱一樣。

resolvers.js
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。

server.js
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 做使用

resolvers.js
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

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}`);});
resolvers.js
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 的時候把所有需要的欄位補上。

tempData.js
const Animals = [  {    name: "狼蛛",    footLength: 123,  },  {    name: "奇異鳥",    footLength: 123,    wingLength: 456,    wing: false,  },];

像是這樣

typeDefs.js
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 來解決上述的問題~

typeDefs.js
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 在重新定義一次且型態都要一樣,類似抽象類別。

resolvers.js
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 則不用。

tempData.js
const Animals = [  {    name: "狼蛛",    footLength: 123,  },  {    name: "奇異鳥",    wingLength: 456,  },];
typeDefs.js
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;
resolvers.js
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;