上一編Blog已經簡單講解如何實作GraphQL Server。今編Blog會深入講解Frontend如何同GraphQL Server進行溝通。Frontend最常做兩類GraphQL Query Statement,查詢(Query)及 變更(Mutation)。還有其他類型,不過坊間仍未普遍使用,所以在此不作介紹。
Query Statement用途是查詢資訊,所以Query Statement是「習慣」#上是唔會更新數據,即是可以視為資訊是唯讀 (Read-Only)。當然可以是Query Statement上更新資訊,因為GraphQL無機制阻止的。
Git Clone Tec。士多 GraphQL Server Demo落自己部電腦再啟動Server。詳細方法請看Tec‧士多開發日記系列:第二篇:實作GraphQL Server(基礎入門)- Part 2。
是瀏覽器打開GraphQL Playground試下是Client Side做Query。
http://localhost:4000/graphql
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
}
]
}
}
我宜家好口渴,我淨是想知飲品清單,其他貨品我唔關心。但是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左下角輸入變數值。
樓上說到取得不同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
}
]
}
}
大家有無覺得以下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前面。
如果唔需要一次過取得不同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
}
]
}
]
}
}
之前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
}
}
}
"""每一個上課地點的物件"""
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 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。
請先是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。