跳至主要内容

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

影片講解 Part 1

影片講解 Part 2

影片講解 Part 3

初始設定

在開發的時候建議使用 nodemon,這樣我們變更檔案存檔後就不用再重新啟動 server。

要使用 GrpahQL 必須先安裝以下套件

npm install apollo-server

Apollo Server 裡面就包含使用 GraphQL 的工具與 Server。

安裝完以後直接將 Apollo Server 啟動

server.js
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
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
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 才會回傳值給我們,本範例是使用上面的範例資料來做查詢。

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

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

resolvers.js
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 實作完吧!

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