# GraphQL
[GraphQL 官網](https://graphql.org/)
[Apollo Server v3](https://www.apollographql.com/docs/apollo-server/)
[Github Code](https://github.com/WeiYun0912/GrpahQL-Basic)
:::success
:bookmark: 書籤
[TOC]
:::
## 初始設定
在開發的時候建議使用nodemon,這樣我們變更檔案存檔後就不用再重新啟動server。
要使用GrpahQL必須先安裝以下套件
```javascript=
npm install apollo-server
```
Apollo Server裡面就包含使用GraphQL的工具與Server。
安裝完以後直接將Apollo Server啟動
***server.js***
```javascript=
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。
***tempData.js***
```json=
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行定義的型態。
***typeDefs.js***
```javascript=
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;
```
定義好type以後還沒結束,我們必須來實作resolvers,這樣當我們下達指定的條件server才會回傳值給我們,本範例是使用上面的範例資料來做查詢。
***resolvers.js***
```javascript=
const Books = require("../tempData");
const resolvers = {
Query: {
books: () => {
return Books;
},
},
};
module.exports = resolvers;
```
這邊我們先簡單的把範例資料回傳就好,這邊就如同前面提到的,回傳型態會是Array。
## 啟動Server
定義好type和resolvers以後將他引入至server.js,之後就可以啟動server了!
server預設的port是4000
***server.js***
```javascript=
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}`);
});
```
```javascript=
nodemon server.js
```

接著在瀏覽器就可以輸入localhost:4000進入到後台,畫面如下:

可以在左手邊的Fields看到我們剛剛定義的books!
現在我們直接來下query,來看回傳的是不是範例資料內的資料~
我們直接在**Operations**輸入
```graphql=
query{
books{
id
name
author
publish
}
}
```
也就是我們一開始定義的型態,按下Run以後回傳的值如下:

而我們也可以定義query的名稱,定義的方式非常簡單,只要在query後面加上要自訂的名稱就好,如以下:
```graphql=
query Books{
books{
id
name
author
publish
}
}
```

## Parent
GrpahQL也可以有階層式的查詢,在以上的範例資料不好做示範,所以這邊我們來更新一下我們的範例資料,這邊因為只是簡單的示範,所以欄位名稱上先不要求嚴謹,在以下範例資料我們把作者獨立出來,並把書本的作者欄位改成對應的ID值。
***tempData.js***
```json=
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的地方也要改一下
***typeDefs.js***
```javascript=
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!]
book(id: ID): Book
}
`;
module.exports = typeDefs;
```
我們來看一下階層式查詢的範例
```graphql=
query Books {
books {
id
name
publish
author {
id
name
}
}
}
```
型態定義好了,那現在我們要怎麼查詢到書本的作者呢?
這時候就輪到 **Parent** 登場了~ 直接來看範例
我們必須在Book型態底下去實作author的resolver。
***resolvers.js***
```javascript=
const { Authors, Books } = require("../tempData");
const resolvers = {
Query: {
books: () => {
return Books;
},
book: (parent, args) => {
return Books.find((book) => book.id === +args.id);
},
},
Book: {
author: (parent) => {
console.log(parent);
},
},
};
module.exports = resolvers;
```
這邊我們先不return值,先在後台的**Operation**輸入以下Query,來查看parent裡面的值是什麼。
```graphql=
query Books {
books {
id
name
publish
author {
id
name
}
}
}
```
執行後,我們會在TERMINAL看到以下畫面

這時候"Parent"這個單字應該很明顯了,就是取得父階層的值,那現在我們把resolvers實作完吧!
***resolver.js***
```javascript=
const { Authors, Books } = require("../tempData");
const resolvers = {
Query: {
books: () => {
return Books;
},
book: (parent, args) => {
return Books.find((book) => book.id === +args.id);
},
},
Book: {
author: (parent) => {
return Authors.find((author) => author.id === parent.author);
},
},
};
module.exports = resolvers;
```

## Args
假設今天我們想要單獨查詢書本的資訊要怎麼做呢?
一般的作法都是利用 **id** 來找尋資料,而在GraphQL我們一樣可以靠外部參數來找尋特定的資料,來看以下範例:
在type定義時必須多加一條規則,也就是程式碼第13行,括號的意思是接收的參數名稱與參數型態。
***typeDefs.js***
```javascript=
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***
```javascript=
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***
```javascript=
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***
```javascript=
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***
```javascript=
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***
```javascript=
const resolvers = {
Query: {
books: (parent, args, context) => {
return context.Books;
},
},
};
module.exports = resolvers;
```

## Fragment
當回傳值太多的時候,或是多個type回傳的值都一樣時可以使用**Fragment**來簡化,直接來看以下範例。
假設要回傳的值有以下:
```graphql=
query Books{
books{
id
name
author
publish
}
}
```
我們可以利用**Fragment**來將要回傳的值個別獨立出來。
```graphql=
query Books{
books{
...BookFragement
}
}
fragment BookFragement on Book{
id
name
author
publish
}
```

## Interface
善用**Interface**能夠有效管理型態,Interface type可以提供一組fields讓不同的物件(Object)共享,我們直接來看範例。
我們用以下範例來看,如果要在GraphQL回傳下面的範例資料,則必須在定義type的時候把所有需要的欄位補上。
***tempData.js***
```json=
const Animals = [
{
name: "狼蛛",
footLength: 123,
},
{
name: "奇異鳥",
footLength: 123,
wingLength: 456,
wing: false,
},
];
```
像是這樣
***typeDefs.js***
```javascript=
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***
```javascript=
const {Animals} = require("./tempData")
const resolvers = {
Query: {
animals: () => {
return Animals;
},
},
};
module.exports = resolvers;
```
但我們會發現執行query後,第一筆資料的其他欄位會是null,這不是我們想要的。

這時候,可以利用interface來解決上述的問題~
***typeDefs.js***
```javascript=
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在重新定義一次且型態都要一樣,類似抽象類別。
***resolver.js***
```javascript=
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的部分要怎麼下呢?
```graphql=
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***
```javascript=
const Animals = [
{
name: "狼蛛",
footLength: 123,
},
{
name: "奇異鳥",
wingLength: 456,
},
];
```
***typeDefs.js***
```javascript=
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;
```
***resolver.js***
```javascript=
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;
```
