翻譯
|
《JavaScript Everywhere
》第4
章
咱們的第一個GraphQL API
(^_^)前端
寫在最前面
你們好呀,我是毛小悠,是一位前端開發工程師。正在翻譯一本英文技術書籍。node
爲了提升你們的閱讀體驗,對語句的結構和內容略有調整。若是發現本文中有存在瑕疵的地方,或者你有任何意見或者建議,能夠在評論區留言,或者加個人微信:code
_maomao
,歡迎相互溝通交流學習。數據庫
(σ゚∀゚)σ..:
*☆哎喲不錯哦express
第4章 咱們的第一個GraphQL API
根據推測,若是你正在閱讀本文,那麼你就是一我的。做爲人類,你有不少興趣和愛好,你也有家人、朋友、熟人、同窗和同事。這些人也有本身的社會關係、興趣和愛好。這些關係和興趣中有些重疊,有些沒有。總而言之,咱們每一個人都有咱們生活中人的關係圖。這些類型的互連數據正是GraphQL
最初提出要在API
開發中解決的挑戰。npm
經過編寫GraphQL API
,咱們可以高效地鏈接數據,從而下降了複雜性和請求數量,同時使咱們可以準確地爲客戶提供所需的數據。聽起來對Notes
應用程序來講有點過了,是嗎?也許是吧,可是正如你將看到的,GraphQL JavaScript
生態系統提供的工具和技術均可以啓用和簡化全部類型的API
開發。json
在本章中,咱們將使用apollo-server-express
包來構建GraphQL API
。爲此,咱們將探究GraphQL
的基本主題,編寫GraphQL
模式,編寫代碼以解析模式功能,並使用GraphQL Playground
用戶界面訪問API
。api
將咱們的服務器轉換爲API(排序)
讓咱們經過使用如下命令將Express
服務器變成GraphQL
服務器來開始API
開發。數組
apollo-server-express
軟件包。瀏覽器
Apollo Server
是一個開源GraphQL
服務器庫,可與大量Node.js
服務器框架一塊兒使用,包括Express
,Connect
,Hapi
和Koa
。服務器
GraphQL API
,可從Node.js
應用程序中獲取數據,還提供了有用的工具,例如GraphQL Playground
,一個可視化的輔助器,幫助咱們在開發中查看API
。
要編寫咱們的API
,咱們將修改上一章中編寫的Web
應用程序的代碼。讓咱們首先開始於apollo-server-express
軟件包。
將如下內容添加到src/index.js
文件的頂部:
const { ApolloServer, gql } = require('apollo-server-express');
如今咱們已經導入了apollo
服務器,咱們將創建一個基本的GraphQL
應用程序。
GraphQL
應用程序由兩個主要組件組成:類型定義和解析器,用於解析針對數據執行的查詢和修改。
這聽起來像是胡話,但不要緊。
咱們將實現「 Hello World
」 API
響應,並將在咱們API
的整個開發過程當中進一步探討這些GraphQL
主題。
首先,讓咱們構建一個基本模式,將其存儲在一個名爲typeDefs
的變量中。
該模式將描述一個名爲hello
的查詢,該查詢將返回一個字符串:
// Construct a schema, using GraphQL schema language const typeDefs = gql` type Query { hello: String } `;
如今咱們已經設置告終構,咱們能夠添加一個解析器,該解析器將向用戶返回一個值。這只是一個簡單的函數,返回字符串「 Hello world
!」:
// Provide resolver functions for our schema fields const resolvers = { Query: { hello: () => 'Hello world!' } };
最後,咱們將集成Apollo Server
以提供GraphQL API
。
爲此,咱們將添加一些特定於Apollo Server
的設置和中間件,並更新咱們的應用程序。
監聽代碼:
// Apollo Server setup const server = new ApolloServer({ typeDefs, resolvers }); // Apply the Apollo GraphQL middleware and set the path to /api server.applyMiddleware({ app, path: '/api' }); app.listen({ port }, () => console.log( `GraphQL Server running at http://localhost:${port}${server.graphqlPath}` ) );
綜上,咱們的src/index.js
文件如今應以下所示:
const express = require('express'); const { ApolloServer, gql } = require('apollo-server-express'); // Run the server on a port specified in our .env file or port 4000 const port = process.env.PORT || 4000; // Construct a schema, using GraphQL's schema language const typeDefs = gql` type Query { hello: String } `; // Provide resolver functions for our schema fields const resolvers = { Query: { hello: () => 'Hello world!' } }; const app = express(); // Apollo Server setup const server = new ApolloServer({ typeDefs, resolvers }); // Apply the Apollo GraphQL middleware and set the path to /api server.applyMiddleware({ app, path: '/api' }); app.listen({ port }, () => console.log( `GraphQL Server running at http://localhost:${port}${server.graphqlPath}` ) );
若是你沒有運行nodemon
進程,則能夠直接進入瀏覽器。不然,你必須在終端應用程序中輸入npm
運行dev
以啓動服務器。而後訪問http://localhost:4000/api
,在這裏你會看到GraphQL Playground
(圖4-1
)。
該Web
應用程序與Apollo Server
捆綁在一塊兒,是使用GraphQL
的巨大好處之一。從這裏,你能夠運行GraphQL
查詢和修改並查看結果。
你也能夠單擊「Schema
」選項卡來訪問爲API
自動建立的文檔。
圖4-1
。GraphQL Playground
注意
GraphQL Playground
具備深色的默認語法主題。在整本書中,我將使用「淺色」主題以提升對比度。
這能夠在GraphQL Playground
的設置中進行配置,能夠經過單擊齒輪圖標進行訪問。如今,咱們能夠針對咱們的GraphQL API
編寫查詢。爲此,在GraphQL Playground
中鍵入如下內容:
query { hello }
當你單擊「播放」按鈕時,查詢應返回如下內容(圖4-2
):
{ "data": { "hello": "Hello world!" } }
圖4-2
。你好查詢
就是這樣!如今,咱們已經能夠經過GraphQL Playground
訪問有效的GraphQL API
。咱們的API
會查詢hello
,並返回字符串Hello world
!。
更重要的是,咱們如今具備了功能構建齊全的API
的結構。
😙
GraphQL基礎
在上一節中,咱們深刻探討並開發了咱們的第一個API
,先讓咱們花一些時間後退一步來看看GraphQL API
的不一樣部分。
GraphQL API
的兩個主要構建模塊是模式和解析器。
經過理解這兩個組件,能夠更有效地將它們應用於API
設計和開發。
Schemas
模式是咱們的數據和交互的書面表示。
經過請求一個模式,GraphQL
規範了咱們的API
請求。
這是由於你的API
只能返回數據並執行架構中定義的交互。
GraphQL
模式的基本組成部分是對象類型。
在前面的示例中,咱們建立了一個GraphQL
對象類型的Query
,帶有一個hello
字段,該對象返回了標準的String
類型。
GraphQL
包含五種內置標量類型:
-
串
:String
具備
UTF-8
字符編碼的字符串 -
布爾型
Boolean
正確或錯誤的值
-
整數
Int
32
位整數 -
浮點型
Flaot
浮點值
-
ID
惟一標識符
使用這些基本組件,咱們能夠爲API
構建一個模式。
咱們首先定義類型。假設咱們正在爲披薩菜單建立一個API
。
這時,咱們能夠定義披薩的GraphQL
模式類型,以下所示:
type Pizza { }
如今,每一個披薩餅都有惟一的ID
,大小(例如小,中或大),切片數和可選的澆頭。
Pizza
模式可能看起來像這樣:
type Pizza { id: ID size: String slices: Int toppings: [String] }
在此架構中,某些字段值是必需的(例如ID
,大小和切片),而其餘字段值則是可選的(例如配料)。咱們可使用感嘆號來表示字段必須包含一個值。
讓咱們更新結構以表示所需的值:
type Pizza { id: ID! size: String! slices: Int! toppings: [String] }
在本書中,咱們將編寫一個基本模式,這將使咱們可以執行常見API
中的絕大多數操做。若是你想探索全部的GraphQL
模式選項,建議你閱讀GraphQL
模式文檔。
解析器Resolvers
GraphQL API
的第二部分是解析器。
解析程序徹底執行其名稱所暗示的操做;他們解析API
而後獲取用戶已請求的數據。
咱們將首先在結構中定義這些解析器,而後在JavaScript
代碼中實現邏輯,以編寫這些解析器。
咱們的API
將包含兩種類型的解析器:查詢和修改。
查詢
查詢從API
請求中獲取所需格式的特定數據。
在咱們假設的披薩API
中,咱們能夠編寫一個查詢,該查詢將返回菜單上披薩的完整列表,而另外一個查詢將返回有關單個披薩的詳細信息。而後查詢將返回一個對象,其中包含API
中用戶請求的數據。查詢從不修改數據,僅訪問數據。
修改
當咱們想要修改API
中的數據時,咱們使用一個修改。
在咱們的披薩示例中,咱們可能編寫了一個修改,該修改能夠更改給定披薩的配料,而另外一個修改則能夠調整切片數。
與查詢相似,也指望修改以對象的形式返回結果,一般是所執行操做的最終結果。
調整咱們的API
如今你已經對GraphQL
的組件有了很好的瞭解,讓咱們爲Notes
應用程序調整咱們的初始API
代碼。
首先,咱們將編寫一些代碼來閱讀和建立筆記。咱們須要作的第一件事是爲API
提供一些數據。讓咱們建立一個「筆記」對象數組,將其用做API
提供的基本數據。隨着項目的發展,咱們將用數據庫替換這個數據。
如今,咱們將數據存儲在一個名爲notes
的變量中。數組中的每一個筆記將是一個具備三個屬性(id
,content
和author
)的對象:
let notes = [ { id: '1', content: 'This is a note', author: 'Adam Scott' }, { id: '2', content: 'This is another note', author: 'Harlow Everly' }, { id: '3', content: 'Oh hey look, another note!', author: 'Riley Harrison' } ];
如今咱們有了一些數據,咱們將調整咱們的GraphQL API
來使用它。
讓咱們從關注咱們的模式開始。咱們的模式是GraphQL
對咱們的數據以及如何與之交互的表示。已知咱們會有筆記,這些筆記將被查詢和修改。這些筆記目前將包含ID
、內容和做者3
個字段。
讓咱們在typeDefs GraphQL
模式中建立一個相應的筆記類型。
如下表示咱們API
中筆記的屬性:
type Note { id: ID! content: String! author: String! }
如今,讓咱們添加一個查詢,該查詢將容許咱們檢索全部筆記的列表。
讓咱們更新一個筆記查詢,這將返回筆記對象的數組:
type Query { hello: String! notes: [Note!]! }
如今,咱們能夠更新解析器代碼以執行返回數據數組的工做。
讓咱們更新包括如下筆記解析器的查詢代碼,該解析器返回原始數據對象:
Query: { hello: () => 'Hello world!', notes: () => notes },
若是如今切換到運行在http://localhost:4000/api
的GraphQL Playground
,咱們能夠測試筆記查詢。
爲此,請鍵入如下查詢:
query { notes { id content author } }
而後,當你單擊「播放」按鈕時,應該看到返回的數據對象,其中包含數據數組(圖4-3
)。
圖4-3
。筆記查詢。
在嘗試GraphQL
最酷的方面之一是咱們能夠刪除任何請求的字段,例如id
或author
。當咱們這樣作時,API
精確地返回咱們所請求的數據。
這樣,使用數據的客戶端能夠控制每一個請求中發送的數據量,並將該數據限制在所需的範圍內(圖4-4
)。
圖4-4
。筆記查詢,僅請求內容數據。
如今咱們能夠查詢完整的筆記列表,讓咱們編寫一些代碼,使咱們能夠查詢單個筆記。
從用戶界面的角度,你能夠想象這樣作的用處,能夠顯示包含單個特定筆記的視圖。爲此,咱們但願請求帶有特定id
值的筆記。這要求咱們使用GraphQL
模式中的參數。參數容許API
使用者將特定的值傳遞給解析器函數,從而提供解析所需的信息。讓咱們添加一個筆記查詢,該查詢將使用id
類型的ID
做爲參數。
咱們將typeDefs
中的Query
對象更新爲如下內容,其中包括新的筆記查詢:
type Query { hello: String notes: [Note!]! note(id: ID!): Note! }
更新結構後,咱們已經能夠編寫在查詢解析器後返回的筆記了。如今咱們須要可以讀取API
用戶的參數值。
有用信息
Apollo Server
將如下有用的參數傳遞給咱們的解析器功能:
-
parent
父查詢的結果,在嵌套查詢時頗有用。
-
args
這些是用戶在查詢中傳遞的參數。
-
context
信息從服務器應用程序傳遞到解析器功能。
這可能包括諸如當前用戶或數據庫信息之類的信息。
-
info
有關查詢自己的信息。
咱們將根據須要在代碼中進一步探索這些內容。若是你感到好奇,能夠在Apollo
服務器的文檔中瞭解有關這些參數的更多信息。
如今,咱們僅須要第二個參數args
中包含的信息。
筆記查詢將筆記ID
做爲參數,在咱們的筆記對象數組中找到它。將如下內容添加到查詢解析器代碼中:
note: (parent, args) => { return notes.find(note => note.id === args.id); }
解析器代碼如今應以下所示:
const resolvers = { Query: { hello: () => 'Hello world!', notes: () => notes, note: (parent, args) => { return notes.find(note => note.id === args.id); } } };
運行咱們的查詢,讓咱們返回到Web
瀏覽器並訪問位於http://localhost:4000/api
的GraphQL Playground
。
如今,咱們能夠查詢具備特定ID
的筆記,以下所示:
query { note(id: "1") { id content author } }
運行此查詢時,你應該收到帶有請求的id
值的筆記的結果。若是你嘗試查詢不存在的筆記,則應收到結果爲null
的結果。要對此進行測試,請嘗試更改id
值以返回不一樣的結果。
讓咱們經過使用GraphQL
修改引入建立新筆記的功能來強化咱們的初始API
代碼。在這種修改中,用戶將傳遞筆記的內容。
如今,咱們將對筆記的做者進行編碼。
讓咱們首先使用Mutation
類型更新咱們的typeDefs
模式,咱們將其稱爲newNote
:
type Mutation { newNote(content: String!): Note! }
如今,咱們將編寫一個修改解析器,它將筆記內容做爲參數,將筆記存儲爲對象,而後將其添加到notes
數組中。咱們將Mutation
對象添加到解析器。
在Mutation
對象中,咱們將添加一個名爲newNote
的函數,該函數具備parent
和args
參數。在此函數中,咱們將使用參數content
並建立一個具備id
,content
和author
鍵的對象。
你可能已經注意到,這與筆記的當前模式匹配。而後,咱們將該對象加入到notes
數組並返回該對象。返回的對象容許GraphQL
修改接收預期格式的響應。
繼續並編寫如下代碼:
Mutation: { newNote: (parent, args) => { let noteValue = { id: String(notes.length + 1), content: args.content, author: 'Adam Scott' }; notes.push(noteValue); return noteValue; } }
咱們的src/index.js
文件如今將以下所示:
const express = require('express'); const { ApolloServer, gql } = require('apollo-server-express'); // Run our server on a port specified in our .env file or port 4000 const port = process.env.PORT || 4000; let notes = [ { id: '1', content: 'This is a note', author: 'Adam Scott' }, { id: '2', content: 'This is another note', author: 'Harlow Everly' }, { id: '3', content: 'Oh hey look, another note!', author: 'Riley Harrison' } ]; // Construct a schema, using GraphQL's schema language const typeDefs = gql` type Note { id: ID! content: String! author: String! } type Query { hello: String notes: [Note!]! note(id: ID!): Note! } type Mutation { newNote(content: String!): Note! } `; // Provide resolver functions for our schema fields const resolvers = { Query: { hello: () => 'Hello world!', notes: () => notes, note: (parent, args) => { return notes.find(note => note.id === args.id); } }, Mutation: { newNote: (parent, args) => { let noteValue = { id: String(notes.length + 1), content: args.content, author: 'Adam Scott' }; notes.push(noteValue); return noteValue; } } }; const app = express(); // Apollo Server setup const server = new ApolloServer({ typeDefs, resolvers }); // Apply the Apollo GraphQL middleware and set the path to /api server.applyMiddleware({ app, path: '/api' }); app.listen({ port }, () => console.log( `GraphQL Server running at http://localhost:${port}${server.graphqlPath}` ) );
更新結構和解析器以接受修改後,讓咱們在GraphQL Playground
的http://localhost:4000/api
上進行嘗試。
在Playground
上,單擊+號以建立一個新選項卡,並按以下所示編寫變量:
mutation { newNote (content: "This is a mutant note!") { content id author } }
當你單擊「播放」按鈕時,你應該會收到包含新筆記的內容、ID
和做者的結果。
你還能夠經過從新運行notes
查詢來查看該修改是否起做用。
爲此,請切換回包含該查詢的GraphQL Playground
選項卡,或鍵入如下內容:
query { notes { content id author } }
運行此查詢時,你如今應該看到四個筆記,包括最近添加的筆記。
數據存儲
咱們將數據存儲在內存中。這意味着,只要咱們從新啓動服務器,就會丟失該數據。在下一章中,咱們將使用數據庫來持久化數據。
如今,咱們已經成功實現了查詢和修改解析器,並在GraphQL Playground
用戶界面中對其進行了測試。
結論
在本章中,咱們已經使用apollo-server-express
模塊成功構建了GraphQL API
。如今,咱們能夠對內存中的數據對象運行查詢和修改。此設置爲咱們提供了構建任何API
的堅實基礎。在下一章中,咱們將探討使用數據庫持久化數據的能力。
若是有理解不到位的地方,歡迎你們糾錯。若是以爲還能夠,麻煩您點贊收藏或者分享一下,但願能夠幫到更多人。