Tec。士多開發日記系列:第三篇:實作Tec記士多Frontend(GraphQL Client入門)- Part 1
2022-02-21
上一編Blog已經簡單講解如何實作GraphQL Server。今編Blog會深入講解Frontend如何同GraphQL Server進行溝通。Frontend最常做兩類GraphQL Query Statement,查詢(Query)及 變更(Mutation)。還有其他類型,不過坊間仍未普遍使用,所以在此不作介紹。
Query 查詢
Query Statement用途是查詢資訊,所以Query Statement是__「習慣」#__上是唔會更新數據,即是可以視為資訊是唯讀 (Read-Only)。當然可以是Query Statement上更新資訊,因為GraphQL無機制阻止的。
啟動Tec。士多 GraphQL Server
Git Clone Tec。士多 GraphQL Server Demo落自己部電腦再啟動Server。詳細方法請看Tec‧士多開發日記系列:第二篇:實作GraphQL Server(基礎入門)- Part 2。
是瀏覽器打開GraphQL Playground試下是Client Side做Query。
http://localhost:4000/graphql
GraphQL Playground
GraphQL Playground是提供一個圖形使用者介面(Graphical User Interface,簡稱GUI)做Query測試。類似於Postman同Insomnia,可以讓使用者是無Frontend的情況下對Server進利測試。
左邊Panel是編寫Query,右邊Panel是顯示查詢結果。
想知道呢台GraphQL Server提供資訊(API)俾你,可以打開最右邊"DOCS" Tab。
基本格式
是左邊Panel編寫Query。所有Query是要用"query"Tag開始。
query{ }
下一步,因為要諗下今次查詢的目的,所以打開"DOCS" Tab找下有無相關資料可以提供。例如,想知Tec。士多完整售賣貨品清單。
query{ Commodities{ } }
最後,是諗一諗使用者介面(User Interface)要顯示的資料。例如,想俾使用者選擇貨品,所以今次需要貨品名稱,單位及現貨存量。
query{ Commodities{ name unit stockQuantity } }
查詢結果是一個JSON Object。識做呢,Developers?!
{ "data": { "Commodities": [ { "name": "240ml經典橙汁", "unit": "CAN", "stockQuantity": 0 }, { "name": "朱古力夾心餅", "unit": "PACK", "stockQuantity": 0 }, { "name": "XX杯麵。五香牛肉", "unit": "CUP", "stockQuantity": 0 } ] } }
參數 (Arguments)與變數(Variables)
我宜家好口渴,我淨是想知飲品清單,其他貨品我唔關心。但是Tec。士多完整售賣貨品清單咁長,好難找所以飲品。透過參數,例如,category(貨品種類),找出飲品清單方便簡潔。另外一個原因是一條query可以選擇出不同category既清單,不需要逐個category砌query。既可以慳development時間,又易管理。你唔是唔用下嗎?
query($categoryName:CATEGORY) { CommoditiesByCategory(category: $categoryName) { name category unit stockQuantity } }
看返Server點定義CommoditiesByCategory呢。。。
Server-Side:Schema | Client-Side:Query |
---|---|
type Query{ ... CommoditiesByCategory( category:CATEGORY) :[Commodity] } | query ($categoryName:CATEGORY){ CommoditiesByCategory( category: $categoryName) { name category unit stockQuantity } } |
是Schema,CommoditiesByCategory被定義需要提供一個參數,名叫"category"。 是Query,CommoditiesByCategory必需提供一個參數,名叫"category"。利用變數提供參數值,例如,$categoryName。所有變數數值必須經query傳入,再傳結對應的field。例如,$categoryName的值是經query再傳給CommoditiesByCategory field。
所有變數是用**$**開頭; 是GraphQL Playground左下角輸入變數值。
別名 (Alias)
樓上說到取得不同category清單,我地可唔可以是同一條query下取得不同category清單?試下用以下呢方法。
query ($categoryName1:CATEGORY,$categoryName2:CATEGORY){ CommoditiesByCategory(category: $categoryName1) { name category unit stockQuantity } CommoditiesByCategory(category: $categoryName2) { name category unit stockQuantity } }
是會出error的。alias就可以解決問題。query被改成如下:
query ($categoryName1:CATEGORY,$categoryName2:CATEGORY){ getDrink:CommoditiesByCategory(category: $categoryName1) { name category unit stockQuantity }, getSnack:CommoditiesByCategory(category: $categoryName2) { name category unit stockQuantity } }
是每一個query加上一個alias,例如,getDrink、getSnack。返回結果會放是對應alias key name入面:
{ "data": { "getDrink": [ { "name": "240ml經典橙汁", "category": "DRINK", "unit": "CAN", "stockQuantity": 0 } ], "getSnack": [ { "name": "朱古力夾心餅", "category": "SNACK", "unit": "PACK", "stockQuantity": 0 } ] } }
片段 (Fragment)
大家有無覺得以下Query太冗長呢?有無方法簡化佢呢?有。Fragment幫到你!
query ($categoryName1:CATEGORY,$categoryName2:CATEGORY){ CommoditiesByCategory(category: $categoryName1) { name category unit stockQuantity } CommoditiesByCategory(category: $categoryName2) { name category unit stockQuantity } }
是Playground入面,先定義好一個fragment如下:
fragment CommodityInfo on Commodity{ name category unit stockQuantity }
Fragment定義格式如下:
fragment <Fragment Name> on <Object Type>{
The set of fields in <Object Type> above
}
定義一個fragment叫CommodityInfo。呢個CommodityInfo擁有Commodity物件部分的fields。query被改成如下:
query ($categoryName1:CATEGORY,$categoryName2:CATEGORY){ getDrink:CommoditiesByCategory(category: $categoryName1) { ...CommodityInfo }, getSnack:CommoditiesByCategory(category: $categoryName2) { ...CommodityInfo } } fragment CommodityInfo on Commodity{ name category unit stockQuantity }
是CommoditiesByCategory當中的fields會被fragment所取代。"..."必須放是fragment前面。
操作名稱 (Operation Name)
如果唔需要一次過取得不同category清單,但是都你都想寫低query備用。如何用最簡潔方法編寫query呢?
query ($categoryName1:CATEGORY,$categoryName2:CATEGORY){ getDrink:CommoditiesByCategory(category: $categoryName1) { ...CommodityInfo }, getSnack:CommoditiesByCategory(category: $categoryName2) { ...CommodityInfo } } fragment CommodityInfo on Commodity{ name category unit stockQuantity }
答案是。。。用Operation Name, 例如,getDrinkOperation及getSnackOperation。同alias相似,今次只是放是query後面加上Operation Name;alias就是放是field name之前。
query getDrinkOperation($categoryName1:CATEGORY){ CommoditiesByCategory(category: $categoryName1) { ...CommodityInfo } } query getSnackOperation($categoryName2:CATEGORY){ CommoditiesByCategory(category: $categoryName2) { ...CommodityInfo } } fragment CommodityInfo on Commodity{ name category unit stockQuantity }
選擇那一個operation name,執行對應的query。
右邊Panel出結果。
進階操作
GraphQL不只訪問單層物件,它還可以訪問巢狀物件(nested objects)。舉個例子, 先是schema.ts先定義一個Store物件。因為每一間Tecky上堂地點都有一間Store賣零食,所以Store物件被定義記低每一個上堂地點(location)、每一個Commodity物件存貨量(每一款售賣貨品存貨量)。
type Store{ id:ID! location:STORE_LOCATION! Commodities:[Commodity] }
基於以上設計,所以Store物件與Commodity物件有一個關連。如果你想知每一個上堂地點、每一款售賣貨品存貨量,你可以用一個nested objects query取得相關存貨量:
query{ Stores{ location Commodities{ id name unit category stockQuantity } } }
結果都會以nested objects形式表達:
{ "data": { "Stores": [ { "location": "TW_27F", "Commodities": [ { "id": "0", "name": "240ml經典橙汁", "unit": "CAN", "category": "DRINK", "stockQuantity": 0 }, { "id": "1", "name": "朱古力夾心餅", "unit": "PACK", "category": "SNACK", "stockQuantity": 0 }, { "id": "2", "name": "XX杯麵。五香牛肉", "unit": "CUP", "category": "NOODLES", "stockQuantity": 0 } ] } ] } }
指令 (Directive)
之前Query/Mutation 都是以文字方法訪問結構資料,我們可否幫Query/Mutation加入功能達成某D工作,例如,檢查權限,只有Admin有權取得所有Stores的售賣貨品存貨量。
query($isAdmin:Boolean!){ Stores @include(if:$isAdmin){//如果$isAdmin是true,query先可以取得所有Stores的售賣貨品存貨量。 location Commodities{ id name unit category stockQuantity } } }
所有Directive是@開始,例如 @include,@skip。 例子 2 : 只想取得上堂地點清單:
query($getStoreLocationListOnly:Boolean!){ Stores{ location Commodities @skip(if:$getStoreLocationListOnly){//如果$getStoreLocationListOnly是true,就可以取得上堂地點清單 id name unit category stockQuantity } } }
Comments and Documentation
"""每一個上課地點的物件""" type Store{ id:ID! location:STORE_LOCATION! Commodities:[Commodity] }
Developer要養成一個好習慣,寫comment同documentation。是GraphLQL加入「"""」就可以同時寫comment同documentation。
""" 我是comment! """
#點解呢?點解呢?看返Tec。士多開發日記系列:第二篇:實作GraphQL Server(基礎入門)- Part 1 ~ 「Query 查詢/Mutation 變更 」Section
Mutation 變更
Mutation Statement用途是更新資料,可以做CRUD operations。做法是同Query Statement無異。如果要提供更新資料(參數,Arguments)太多,使用上不方便了,如以下例子:
mutation($name:String!,$unit:CATEGORY,$category:CATEGORY!,$stockQuantity:Int){ addCommodity(name:$name,unit:$unit,category:$category,stockQuantity:$stockQuantity){ ...CommodityInfo } } fragment CommodityInfo on Commodity{ name category unit stockQuantity }
Input Object Type可以提供一個方便方法「打包」好全部參數再交俾Mutation Statement。
輸入(Input Object Type)
請先是schema.ts定義一個Input Object Type如下:
input CommodityInput{ name:String! unit:UNIT! category:CATEGORY! stockQuantity:Int = 0 }
Input Object Type格式如下:
input <Input Object Type Name>{ The set of arguments in key-value pair }
大家有無留意到stockQuantity有點特別?
stockQuantity:Int = 0
意思是提供一個預設值(default value)給某一個field。例如,給stockQuantity一個預設值0 。
在Playground打入以下Mutation Statement:
mutation($commodityInput:CommodityInput!){ addCommodity(newCommodity:$commodityInput){ ...CommodityInfo } } fragment CommodityInfo on Commodity{ name category unit stockQuantity }
是Query Variable@Playground提供$commodityInput變數資訊:
{ "commodityInput":{ "name":"Item#4", "unit":"CAN", "category":"DRINK", "stockQuantity":1 } }
執行訪問後就會取得最新加入的Commodity物件的資料。
{ "data": { "addCommodity": { "name": "Item#4", "category": "DRINK", "unit": "CAN", "stockQuantity": 0 } } }
恭喜大家🥳! 大家已經成功了解如何寫Query/Mutation Statement及如何是Apollo GraphQL提供的Playground做測試。下一編就講解如何是React使用GraphQL。
References
Comments
Read More
Tec ‧ 士多開發日記系列:第一篇:GraphQL簡介
2021-05-21
話說Tecky入面有間「Tec ‧ 士多」,大家攞完零食飲品,放低錢入錢箱就可以了。因為小弟最近研究緊GraphQL,所以小弟就諗可唔可以用GraphQL開發一個網上版「Tec。士多」。
Tec ‧ 士多開發日記系列:第二篇:實作GraphQL Server(基礎入門)- Part 1
2021-06-21
上一編Blog已經詳細講解GraphQL Frontend既實作方法。今編Blog就深入講解GraphQL Server運作模式及如何實作。開始講解GraphQL Server運作原理及實作之前,有三個概念一定要了解左先,模式(Schema)、解析器(Resolver)及資訊源(Data Sources)。
Tec‧士多開發日記系列:第二篇:實作GraphQL Server(基礎入門)- Part 2
2021-06-22
Part 1 講完理論部分。Part 2就開始實作了。