[graphql] GraphQL 與 Apollo Server 使用教學 Part 1
初始設定
在開發的時候建議使用 nodemon,這樣我們變更檔案存檔後就不用再重新啟動 server。
要使用 GrpahQL 必須先安裝以下套件
npm install apollo-server
Apollo Server 裡面就包含使用 GraphQL 的工具與 Server。
安裝完以後直接將 Apollo Server 啟動
const { ApolloServer } = require("apollo-server");const server = new ApolloServer({ typeDefs, resolvers,});server.listen({ port: process.env.PORT || 4000 }).then(({ url }) => { console.log(`Server is ${url}`);});
這邊如果直接執行該程式會報錯,因為我們還沒用定義 type 和 resolvers。
範例資料
因為 GraphQL 是查詢語言並不是資料庫,所以這邊我們必須要提供資料來做查詢。
本範例省略掉資料庫的連線設定與使用,如果需要連線資料庫的話推薦使用 Mongo DB。
const Books = [ { id: 1, name: "WeiWei Adventure Ep1", author: "Wei", publish: "Wei's Publish", }, { id: 2, name: "WeiWei Adventure Ep2", author: "Wei", publish: "Wei's Publish", }, { id: 3, name: "WeiWei Adventure Ep3", author: "Wei", publish: "Wei's Publish", }, { id: 4, name: "YunYun Adventure Ep1", author: "Yun", publish: "Yun's Publish", }, { id: 5, name: "YunYun Adventure Ep2", author: "Yun", publish: "Yun's Publish", }, { id: 6, name: "YunYun Adventure Ep3", author: "Yun", publish: "Yun's Publish", },];module.exports = Books;
型態
要定義 type 之前當然要知道 GraphQL 有提供哪些 type
- Int : 32-bit 的整數型態
- Float : 符號雙精度的小數點型態
- String : UTF-8 字串型態
- Boolean : 布林值型態 (true or false)
- ID : 唯一編號
定義 type 與 resolvers
知道了有哪些型態以後就來先定義 type 吧,這邊先以書本為例子, 一本書會有書名、作者、出版社,當然,每本書都是獨一的,所以也必須加上 ID。
type Query裡面要放的是想查詢的資料與回傳格式,可以看到程式第 12 行,輸入 books 查詢後,回傳的是陣列型態,而該陣列裡面包含的型態就是程式碼第 4 行~第 9 行定義的型態。
const { gql } = require("apollo-server");const typeDefs = gql` type Book { id: ID! name: String! author: String! publish: String! } type Query { books: [Book!] }`;module.exports = typeDefs;
在形態後面加上 ! 代表我們希望回傳的資料不要有 null 的值,如果回傳的值有 null 就會報錯。
定義好 type 以後還沒結束,必須來實作 resolvers,這樣當下達指定的條件 server 才會回傳值給我們,本範例是使用上面的範例資料來做查詢。
const Books = require("../tempData");const resolvers = { Query: { books: () => { return Books; }, },};module.exports = resolvers;
這邊先簡單的把範例資料回傳就好,這邊就如同前面提到的,回傳型態會是 Array。
啟動 Server
定義好 type 和 resolvers 以後將他引入至 server.js,之後就可以啟動 server 了!
server 預設的 port 是 4000
const { ApolloServer } = require("apollo-server");const resolvers = require("./schema/resolvers");const typeDefs = require("./schema/typeDefs");const server = new ApolloServer({ typeDefs, resolvers,});server.listen().then(({ url }) => { console.log(`Server Url : ${url}`);});
nodemon server.js
接著在瀏覽器就可以輸入 localhost:4000 進入到後台,畫面如下:
可以在左手邊的 Fields 看到剛剛定義的 books!
現在直接來下 query,來看回傳的是不是範例資料內的資料~
在Operations輸入
query { books { id name author publish }}
也就是我們一開始定義的型態,按下 Run 以後回傳的值如下:
而我們也可以定義 query 的名稱,定義的方式非常簡單,只要在 query 後面加上要自訂的名稱就好,如下:
query Books { books { id name author publish }}
Parent
GrpahQL 也可以有階層式的查詢,在以上的範例資料不好做示範,所以這邊來更新一下範例資料,因為只是簡單的示範,欄位名稱上先不要求嚴謹,在以下範例資料把作者獨立出來,並把書本的作者欄位改成對應的 ID 值。
const Authors = [ { id: 1, name: "Wei", }, { id: 2, name: "Yun", },];const Books = [ { id: 1, name: "WeiWei Adventure Ep1", author: 1, publish: "Wei's Publish", }, { id: 2, name: "WeiWei Adventure Ep2", author: 1, publish: "Wei's Publish", }, { id: 3, name: "WeiWei Adventure Ep3", author: 1, publish: "Wei's Publish", }, { id: 4, name: "YunYun Adventure Ep1", author: 2, publish: "Yun's Publish", }, { id: 5, name: "YunYun Adventure Ep2", author: 2, publish: "Yun's Publish", }, { id: 6, name: "YunYun Adventure Ep3", author: 2, publish: "Yun's Publish", },];module.exports = { Authors, Books };
在定義 type 的地方也要改一下
const { gql } = require("apollo-server");/* Book內的author型態 改為 [Author!] */const typeDefs = gql` type Book { id: ID! name: String! author: Author! publish: String! } type Author { id: ID! name: String! } type Query { books: [Book!] }`;module.exports = typeDefs;
來看一下階層式查詢的範例
query Books {
books {
id
name
publish
author {
id
name
}
}
}
型態定義好了,那要怎麼查詢到書本的作者呢?
這時候就輪到 Parent 登場了~ 直接來看範例
我們必須在 Book 型態底下去實作 author 的 resolver。
const { Authors, Books } = require("../tempData");const resolvers = { Query: { books: () => { return Books; }, Book: { author: (parent) => { console.log(parent); }, },};module.exports = resolvers;
這邊先不 return 值,先在後台的Operation輸入以下 Query,來查看 parent 裡面的值是什麼。
query Books {
books {
id
name
publish
author {
id
name
}
}
}
執行後,會在 TERMINAL 看到以下畫面
這時候"Parent"這個單字應該很明顯了,就是取得父階層的值,那現在把 resolvers 實作完吧!
const { Authors, Books } = require("../tempData");const resolvers = { Query: { books: () => { return Books; }, }, Book: { author: (parent) => { return Authors.find((author) => author.id === parent.author); }, },};module.exports = resolvers;