tags: GraphQL
GraphQL - Graph Query Language
此篇是站在前端角度,去初探 GraphQL 世界 ~
什麼是 GraphQL ?
GraphQL 是一種用於 API 的查詢語言,可以讓 client 端更彈性去使用後端提供的資料。
最初是由 Facebook 開發的,是為了要解決手機 APP 在初次載入,會發多個 requests 去取得頁面資料,進而導致程式會變慢或直接當掉問題,所以他們想說將資料整合成一筆,送到 client 直接做使用。後來 Facebook 也在 2015 年公開發佈這項技術。
GraphQL v.s. RESTful
1. API endpoints
-
RESTful
根據資源類型,會有多個 endpoints:/users/<id>
/users/<id>/posts
/users/<id>/followers
-
GraphQL
只有一個 endpoint:/graphql
2. Data Fetching
-
RESTful
_ 需要打三次 api,才可以得到頁面要的全部資料
_ 如果 api 本身有依賴前一支 API (A => B => C),需要等到 A response,才能發 B 的 request
-
GraphQL
_ 可選擇三種不同類型的資料,集中在同一筆 request
_ 資料本身有關聯,如 A 的 response 中有 B 所需要的 params,B 可以直嵌入 A 的 query 裡面,這樣也只需打一次 request 即可
3. Overfetching and Underfetching
RESTful
容易含過多或缺少一些資料,因為 endpoint 回的 response 資料是固定的GraphQL
可精準挑選所需要的資料
4. Schema & Type System
Schema 是用來定義 API 的結構和功能,前端可透過套件,映射一份與後端相同的 API Type System,不只 API 底下欄位定義的 Type,連 Type name 都可以跟後端同步,減少雙方對欄位認知不一致,且後端只要一更改,前端立馬隨之也會更新。
如何使用
Query
用於取得資料
1. 基本
# 寫法一:
{
me {
name
}
}
# 寫法二:
query {
me {
name
}
}
# 寫法三: Operation name for identify different GraphQL requests
query getMe {
me {
name
}
}
💡Operation name:
通常實務上都會使用寫法三,也就是會加 Operation name,可以方便 debug 和追蹤一些數據,相當於幫它取一個變數名的概念。
在 Network 裡面也可以看到 Operation name,官方也是建議使用這種寫法。
以上三種寫法都可以得到 server 的 response:
{
"data":{
"me": {
"name": "Jennie"
}
}
}
2. 有使用參數
# 寫法一: query + 參數
{
human(id: "1000") {
name
height(unit: FOOT)
}
}
# 寫法二: query + 參數
query humanInfo($id: Int!) {
human(id: $id) {
name
height(unit: FOOT)
}
}
得到的 response:
{
"data": {
"human": {
"name": "Luke Skywalker",
"height": 5.6430448
}
}
}
3. Aliases - 使用別名
# 使用別名,改變 response key
{
empireHero: hero(episode: EMPIRE) {
name
}
jediHero: hero(episode: JEDI) {
name
}
}
得到的 response:
{
"data": {
"empireHero": {
"name": "Luke Skywalker"
},
"jediHero": {
"name": "R2-D2"
}
}
}
4. Fragments
# 使用 Fragments,可依據不同 episode,打同一隻 api
query getXXX{
leftComparison: hero(episode: EMPIRE) {
...comparisonFields
}
rightComparison: hero(episode: JEDI) {
...comparisonFields
}
}
fragment comparisonFields on Character {
name
appearsIn
friends {
name
}
}
得到的 response:
{
"data": {
"leftComparison": {
"name": "Luke Skywalker",
"appearsIn": [
"NEWHOPE",
"EMPIRE",
"JEDI"
],
"friends": [
{
"name": "Han Solo"
},
{
"name": "Leia Organa"
},
{
"name": "C-3PO"
},
{
"name": "R2-D2"
}
]
},
"rightComparison": {
"name": "R2-D2",
"appearsIn": [
"NEWHOPE",
"EMPIRE",
"JEDI"
],
"friends": [
{
"name": "Luke Skywalker"
},
{
"name": "Han Solo"
},
{
"name": "Leia Organa"
}
]
}
}
}
Mutation
用於新增/更新/刪除資料
基本
# mutation
mutation createReviewForEpisode($ep: Episode!, $review: ReviewInput!) {
createReview(episode: $ep, review: $review) {
stars
commentary
}
}
// variables
{
"ep": "JEDI",
"review": {
"stars": 5,
"commentary": "This is a great movie!"
}
}
API 成功後,回傳的 response:
{
"data": {
"hero": {
"name": "R2-D2",
"friends": [
{
"name": "Luke Skywalker"
},
{
"name": "Han Solo"
},
{
"name": "Leia Organa"
}
]
}
}
}
參數預設值
可以使用 =
帶入參數的預設值
query HeroNameAndFriends($episode: Episode = JEDI) {
hero(episode: $episode) {
name
friends {
name
}
}
}
生態圈 + 工具
Language / Framework
前端 Web 和 App 語言基本 GraphQL 都有支持使用 (e.g. JavaScript、Kotlin、Swift),另外,不論後端語言是使用哪一種,只需要後端也有使用 GraphQL 撰寫 API,前端就可以用 GraphQL 來做前後端的資料傳輸。
前端三大框架,也各有與 GraphQL 搭配的工具使用,所以可以考慮跟後端溝通,一同踏入 GraphQL 世界!
GraphQL Client 常見工具
GraphQL Client 主要工作就是,使用前端寫好的 query 與 server 去溝通,即 client 只需要寫 query,打 API 行為就交給工具函式庫幫你解決,相當於在使用 RESTful API 時候,會去用 axios 幫助處理 API。另外這些工具有些也有整合前端其他框架、資料 Cache 等功能,基本都會選一個來搭配使用。
GraphQL Client
- Apollo Client
- graphql-request
5/27 當天有口誤,主要 GraphQL Client 為
graphql-request
,只是當天有在搭配,react-query 去處理 API - relay
IDE
基本就是 API Documentation,相當於 swagger 在使用 RESTful API 扮演的角色,可以透過 IDE 看到 params/response 的 type 資訊,也能在上面直接送 request,查看 response 的資料,但要使用哪個 IDE 是由後端決定的,因為前端也會使用到,所以也稍微介紹一下。
其他
如果有使用 TypeScript 強烈推薦使用!它會依據 client 定義的 query,自動對到後端提供的 schema 產生對應的 type,連 type name 也不用前端想,對命名障礙的人簡直是福音,而且只要後端有改到 schema,文件也對自己自動修改,開發體驗非常好! 👍
GraphQL 優/缺點
優點
- doc 文件清楚 (我覺得比 Swagger 好用)
- 彈性調整 response,可以剛好配置出前端需要的資料格式
- TypeScript 搭配工具,能自動產出 response type,節省開發和維護時間
- 生態系已滿蓬勃,將來換語言或框架,經驗也可以沿用
缺點
- 需要後端配合使用
- 後端 api error response,相較於 Swagger,較不容易寫清楚
- 學習成本,跟 RESTful 用法還是不同,需付出時間適應學習
- 錯誤訊息需額外處理,基本有成功都是 200,更詳細錯誤訊息,需解構出來看
- query 深度和複雜度都會影響整個 query 效能
- 搭配 cache 功能的話,需考慮到整個專案設計,避免同資料,更新時間點不同步
Apollo + React
- client-js : React + JavaScript + apollo
- client-ts : React + TypeScript + graphql-request
- server-js : Nodejs + MongoDB
💡 想實作前端的話,可以直接拉 feature/template 這個 branch,裡面 UI 已處理好,後端 server 起起來後,就可以自行針對練習 graphql
💡 後端 DB 密碼記得改成 school wifi 密碼,或者改接自己的 DB、server 也可以
1. 安裝
npm install @apollo/client graphql
2. 初始化 ApolloClient
uri
需調整為 GraphQL server URL
// App.jsx
import { ApolloClient, InMemoryCache, ApolloProvider } from "@apollo/client";
// step 1. initialize apollo client
const client = new ApolloClient({
uri: "http://localhost:4000/graphql",
cache: new InMemoryCache()
});
// step 2. add ApolloProvider
const App = () => {
return <ApolloProvider client={client}>Hello world!</ApolloProvider>;
};
export default App;
總結
每個技術都有適合的場景應用,這篇並不是鼓吹棄用 RESTful API,只是從前端角度打開 GraphQL 世界的一篇紀錄。當然,從後端角度看,GraphQL 一定有另一種感悟和不同的考慮方向,例如 N+1 等性能問題。GraphQL 在提供靈活性和效率的同時,也帶來了一些挑戰,了解這些優缺點,才能在做出選擇時考慮得更全面。