來源:https://medium.com/techtalkers/a-beginners-guide-to-graphql-12d60d3fba03
由Facebook于2015年發布的GraphQL提供了一種新的、有前途的替代傳統REST API的方法。從那時起,包括GitHub、Shopify和Intuit在內的許多公司都在它們的生態系統中采用了GraphQL。雖然REST api可能不會很快過時,但GraphQL正在迅速受到許多開發人員的歡迎和喜愛。因此,對GraphQL的需求急劇增長,并預計在未來十年將呈指數級增長。在本文結束時,您將很好地理解GraphQL的工作方式以及如何使用Node.js構建api。在本教程中,我們將構建一個功能完整的GraphQL API,它將向數組添加和查詢“用戶”。
注意:在生產環境中,您應該查詢和修改數據庫(如MongoDB或Firebase),但為了簡單起見,在本教程中我們將使用硬編碼數據。
前提條件:本教程是為那些對JavaScript和Node.js有很好理解的人準備的。建議您具備事先的GraphQL知識,但不要求您遵循。
一、GraphQL是什么?
GraphQL或“圖形查詢語言”,顧名思義,是一種用于api的查詢語言。
SQL是一種用于管理關系數據庫的查詢語言,而GraphQL是一種允許客戶端(前端)從API請求數據的查詢語言。
二、GraphQL的優點
為什么在傳統的REST API上使用GraphQL ?讓我們來看看GraphQL相對于RESTful對等物所提供的一些優勢:
一個端點:對于傳統的REST api,您必須基于希望請求的數據創建特定的端點。這使得擴展API變得很困難,因為不久之后,您可能會發現自己不得不管理數十甚至數百條必須記住的路由。
更少的服務器請求:GraphQL允許您使用一個服務器請求進行多個查詢和更改。當您的服務器每天只允許有限數量的請求時,這很有用。
聲明性數據獲取:與REST API不同,GraphQL只獲取您實際需要的數據。您需要做的就是指定要返回的字段。
類型系統:GraphQL使用類型系統( type system)來描述數據,這使得開發更加容易。如果你是TypeScript的粉絲,這是雙贏的。
自文檔化:GraphQL是自文檔化的,這意味著GraphQL將自動記錄您的所有查詢和更改。
GraphQL還有許多值得了解的優點(和缺點)。我建議閱讀GraphQL: webab Technology提供的所有你需要知道的東西,以了解更多關于GraphQL的信息(點擊https://medium.com/@weblab_tech/graphql-everything-you-need-to-know-58756ff253d8)。
三、什么是 Apollo 服務器?
注意:Apollo服務器也有一個express集成,但在本教程中我們不需要express .js。
因為GraphQL只是一種查詢語言,所以我們需要一個庫來為我們處理樣板代碼。幸運的是,這樣的庫已經存在了。
進入Apollo Server,這是一個Node.js庫,它提供了一個簡單易用的GraphQL服務器。其他的GraphQL服務器庫也存在,比如express-graphql,但是Apollo server允許更好的可伸縮性,并支持更大的社區。Apollo Server還提供了一個整潔的GraphQL接口,用于在開發過程中執行查詢和更改。
現在我們已經解決了這個問題,我們終于可以開始構建我們的GraphQL API了。以下代碼的鏈接:https://github.com/advaithmalka/graphql-server-tut
步驟1:安裝依賴項
注意:確保你安裝了一個代碼編輯器,就像Visual Studio code一樣。
首先,您需要創建一個包含所有服務器文件的目錄。我們可以在終端中這樣做:
mkdir graphql-server-tut
cd graphql-server-tut
然后,在我們的項目中初始化NPM:
npm init -y
這會在當前目錄中創建一個package.json文件。
現在,我們可以開始為我們的服務器安裝所需的依賴:
npm install apollo-server graphql
這里,我們安裝了兩個必需的依賴項。
apollo-server:允許我們輕松地創建GraphQL服務器。
graphql:apollo-server所需的依賴項。
一旦安裝了這些包,我們就可以開始為服務器編程了。
步驟2:創建類型定義
我們首先需要在當前目錄中創建index.js文件。當我們完成API時,該文件將作為進入服務器的入口點。
接下來,我們需要從apollo-server的NPM模塊中導入ApolloServer和gql。
const { ApolloServer, gql } = require(“apollo-server”);
現在,由于GraphQL是一種類型化語言,我們需要用schema定義數據。
將以下代碼放在index.js文件中。
const { ApolloServer, gql } = require("apollo-server");
// typeDefs tell the GraphQL server what data to expect
// Notice the gql tag, this converts your string into GraphQL strings that can be read by Apollo
const typeDefs = gql`
? type Query {
? ? hello: String!
? ? randomNubmer: Int!
? }
`
// the Query type outlines all the queries that can be called by the client
// hello and randomNumber are the names of the queries
// The exclamation mark (!) tells Apollo Server that a result is required
// Here, we define two queries, one returns a String and another returns a Int
在這里,在導入ApolloServer和gql之后,我們創建了一個包含我們的模式的多行GraphQL字符串。大多數開發人員將他們的模式命名為typedefs,因為當我們稍后初始化ApolloServer時,我們需要將我們的模式傳遞給具有相同名稱的對象鍵。
在這段代碼中有一些關鍵的事情需要注意:
(1)我們的typedef被傳遞到gql標簽中。這個標記將對我們的類型定義進行消毒,并使Apollo服務器能夠讀取它們。這也允許在開發過程中自動完成。
(2)查詢類型(Query type )列出了服務器可以執行的所有可能查詢。
(3)hello和randomNumber是兩個不同的查詢。
(4)在冒號(:)之后定義返回值的類型。在本例中,hello返回一個字符串類型,而randomNumber返回一個整數類型。
(5)類型后面的感嘆號(!)表示返回值是必需的。
步驟3:創建解析器函數
現在,我們需要告訴服務器在調用特定查詢時要做什么或返回什么。我們可以通過創建解析器函數來解決這個問題。
將下面的代碼復制到你的index.js文件中:
// When a query is called a resolver with the same name is run
// The API returns whatever is returned by the resolver
// We are using arrow functions so the "return" keyword is not required
const resolvers = {
? // The name of the resolver must match the name of the query in the typeDefs
? Query: {
? ? // When the hello query is invoked "Hello world" should be returned
? ? hello: () => "Hello world!",
? ? // When we call the randomNumber query, it should return a number between 0 and 10
? ? randomNumber: () => Math.round(Math.random() * 10),
? },
};
讓我們逐行看看這段代碼做了什么:
(1)解析器應該匹配我們的類型定義。就像我們在typedef中有一個查詢類型一樣,我們在解析器中有一個查詢對象。
(2)查詢對象包含與typedef對應的解析器。(每個查詢都有一個名稱相同的對應解析器函數)
(3)無論在解析器中返回什么,查詢都會返回給客戶機。
注意:在現實世界中,解析器通常從數據庫中獲取數據并返回給客戶端。不幸的是,獲取和修改數據庫超出了本教程的范圍。
第四步:把它們放在一起
最后在這里!我們一直在等待的步驟是:該運行服務器了。
首先,我們需要創建一個ApolloServer實例,并傳入typeDefs和解析器。
我們可以這樣做:
// Create an instance of ApolloServer and pass in our typeDefs and resolvers
const server = new ApolloServer({
? // If the object key and value have the same name, you can omit the key
? typeDefs,
? resolvers,
});
// Start the server at port 8080
server.listen({ port: 8080 }).then(({ url }) => console.log(`GraphQL server running at ${url}`));
讓我們來看看這里發生了什么:
首先,我們創建了一個ApolloServer實例(在步驟2中導入),并傳入typeDefs和解析器,這是在步驟2和步驟3中創建的。
然后,我們在端口8080(默認為4000)上啟動服務器。
這就是你的index.js文件現在的樣子:
const { ApolloServer, gql } = require("apollo-server");
// typeDefs tell the GraphQL server what data to expect
// Notice the gql tag, this converts your string into GraphQL strings that can be read by Apollo
const typeDefs = gql`
? type Query {
? ? hello: String!
? ? randomNumber: Int!
? }
`;
// the Query type outlines all the queries that can be called by the client
// hello and randomNumber are the names of the queries
// The exclamation mark (!) tells Apollo Server that a result is required
// Here, we define two queries, one returns a String and another returns a Int
// When a query is called a resolver with the same name is run
// The API returns whatever is returned by the resolver
// We are using arrow functions so the "return" keyword is not required
const resolvers = {
? // The name of the resolver must match the name of the query in the typeDefs
? Query: {
? ? // When the hello query is invoked "Hello world" should be returned
? ? hello: () => "Hello world!",
? ? // When we call the randomNumber query, it should return a number between 0 and 10
? ? randomNumber: () => Math.round(Math.random() * 10),
? },
};
// Create an instance of ApolloServer and pass in our typeDefs and resolvers
const server = new ApolloServer({
? // If the object key and value have the same name, you can omit the key
? typeDefs,
? resolvers,
});
// Start the server at port 8080
server.listen({ port: 8080 }).then(({ url }) => console.log(`GraphQL server running at ${url}`));
我們的服務器已經準備好了!在終端中,通過鍵入node index來運行index.js文件。
如果您按照正確的步驟操作,您應該會看到登錄到控制臺的“GraphQL server running at http://localhost:8080/”。
當你導航到localhost:8080時,你應該看到GraphQL playground:
這個整潔的接口來自于 apollo-server模塊,允許您直接對服務器執行查詢,而不需要連接前端。
注意:如果您的節點環境被設置為生產環境,那么GraphQL playground將不可用。
使用GraphQL playground時有幾個很酷的特性需要注意:
(1)在右側,您將看到兩個選項卡:Schema和Docs。
(2)當API的大小增加時,您可以參考Schema選項卡來查看服務器可以執行的所有可用查詢和更改。
(3)還記得我提到過GraphQL是自文檔化的嗎?您可以在Docs選項卡中看到為您的API生成的文檔GraphQL。
(4)GraphQL playground還允許您向查詢或突變(query or mutation)添加HTTP頭。如果您只想讓授權用戶使用您的API,這是非常有用的。
四、查詢我們的API
現在我們的服務器已經設置好了,我們可以通過GraphQL playground向它發送請求。
要執行查詢,請將以下代碼粘貼到GraphQL playground中:
query {
hello
randomNumber
}
這里,我們調用前面步驟中設置的兩個查詢。一旦你點擊播放按鈕,你應該看到發送回的數據對應于我們的解析器返回的數據:
GraphQL的美妙之處在于,您只返回您指定的內容;如果你刪除hello查詢,它將不再顯示在數據中:
五、創建更高級的API
現在,由于您希望了解一點ApolloServer的工作原理,我們將創建一個能夠添加和查詢用戶的API。這一次,我們不僅可以查詢數據,還可以添加數據。
步驟1:創建我們的“數據庫”
在本教程中,我們將使用硬編碼數據,并將所有數據存儲在一個數組中,而不是使用MongoDB或Firebase這樣的實際數據庫。
首先,我們將創建一個名為users的數組。每個用戶都有一個姓、名和電子郵件字段。如果我們愿意,我們可以像這樣在數組中包含一些硬編碼的數據:
const users = [
? {
? ? firstName: "GraphQL",
? ? lastName: "isCool",
? ? email: "GraphQL@isCool.com"
? },
];
您可以隨意向數組中添加硬編碼的數據。
步驟2:設置typeDefs
現在,我們需要一種方法來查詢“數據庫”中的所有用戶。讓我們更新我們的typeDefs來允許這個函數:
const typeDefs = gql`
? # GraphQL enables us to create our own types
? # Notice the "User" type matches the shape of our "database"
? type User {
? ? firstName: String!
? ? lastName: String!
? ? email: String!
? }
? type Query {
? ? hello: String!
? ? randomNumber: Int!
? ? # This query is going to return all the users in our array
? ? # Since our "database" is an array containing objects, we need to create a "User" type
? ? # Brackets around the type indicates the query is returning an array
? ? queryUsers: [User]!
? }
`;
這里有幾件事需要注意:
(1)queryUsers查詢返回一個對象數組(因此有方括號)。
(2)我們可以使用type關鍵字后跟類型名創建自己的GraphQL類型。
(3)在大括號({})中,我們指定type將返回的字段(我們的用戶類型將返回三個字段:firstName、lastName和email。這三個都是字符串,并且是必需的)。
步驟3:配置解析器
我們只需要再添加一行代碼來完成查詢:
const resolvers = {
? Query: {
? ? hello: () => "Hello world!",
? ? randomNumber: () => Math.round(Math.random() * 10),
? ? // queryUsers simply returns our users array
? ? queryUsers: () => users,
? },
};
這行新代碼創建了一個解析器函數,當調用該函數時,將返回users數組。
步驟4:測試查詢
這次我們的問題看起來有點不同:
query {
queryUsers {
firstName
lastName
}
}
當我們調用queryUsers查詢時,我們需要指定我們希望API以大括號({})返回哪些字段。上面的代碼返回所有三個字段,但是如果客戶端只需要每個用戶的姓和名,你可以省略電子郵件字段來節省帶寬:
向數組中添加用戶
如果我們的API只能顯示硬編碼的用戶,那么它就沒有多大用處。在本節中,我們還將允許我們的API向數組添加用戶。
步驟1:向我們的typeedefs添加一個Mutation
當您執行除從數據庫讀取(創建、更新、刪除)之外的任何其他操作時,都應該使用GraphQL Mutation。
所有Mutation必須是GraphQL Mutation類型:
const typeDefs = gql`
? type User {
? ? firstName: String!
? ? lastName: String!
? ? email: String!
? }
? type Query {
? ? hello: String!
? ? randomNumber: Int!
? ? queryUsers: [User]!
? }
? # Mutations must be in their own type
? type Mutation {
? ? # We are creating a mutation called "addUser" that takes in 3 arguments
? ? # These arguments will be available to our resolver, which will push the new user to the "users" array
? ? # Notice that this mutation will return a single User, which will be the one that was created
? ? addUser(firstName:String!, lastName:String!, email:String!): User!
? }
`;
我想從這段代碼中記下一些事情:
(1)我們正在創建一個名為addUser的新Mutation
(2)addUser接受三個參數:firstName、lastName和email。所有三個參數都是string類型的,并且是必需的(在括號中指定)
(3)addUser返回一個用戶類型:一個包含新用戶的姓、名和電子郵件的對象。
步驟2:添加addUser解析器
在我們開始編寫解析器之前,讓我們計劃一下它應該完成什么。
首先,當我們運行該解析器時,它將需要從Mutation中獲取firstName、lastName和email參數。然后,它需要將該數據作為一個新對象推送到users數組。最后,我們只返回傳遞到Mutation中的數據。
注意:在使用真實的數據庫時,應該實現try, catch塊來處理可能發生的錯誤。
更新您的解析器以匹配以下內容:
const resolvers = {
? Query: {
? ? hello: () => "Hello world!",
? ? randomNumber: () => Math.round(Math.random() * 10),
? ? queryUsers: () => users,
? },
? // All mutation resolvers must be in the Mutation object; just like our typeDefs
? Mutation: {
? ? // Once again notice the name of the resolver matches what we defined in our typeDefs
? ? // The first argument to any resolver is the parent, which is not important to us here
? ? // The second argument, args, is an object containing all the arguments passed to the resolver
? ? addUser: (parent, args) => {
? ? ? users.push(args); // Push the new user to the users array
? ? ? return args; // Returns the arguments provided, this is the new user we just added
? ? },
? },
};
讓我們看看這個解析器會做什么:
(1)就像查詢一樣,突變必須匹配我們的typeDefs并進入Mutation對象。
(2)每個解析器(不僅僅是Mutation)都可以訪問4個參數,您可以在文檔中了解更多信息。對于這個解析器,我們只需要第二個參數。
(3)第二個參數args將包含新用戶的姓、名和電子郵件。如果您愿意,可以在解析器函數中查看console.log參數args,以查看它包含哪些數據。
(4)因為我們的“數據庫”只是一個對象數組,我們可以簡單地將args對象推入用戶數組。
(5)我們的Mutation需要返回創建的新用戶。我們可以通過返回args對象來做到這一點。
就是這樣!只需要不到10行代碼,我們的服務器現在就可以添加和查詢用戶了!現在,讓我們看看如何調用GraphQL突變。
第四步:呼喚我們的Mutation
調用Mutation非常類似于在GraphQL中調用查詢:
mutation {
? addUser(
? ? firstName: "John",
? ? lastName: "Doe",
? ? email: "john.doe@somemail.com"
? ) {
? ? firstName
? ? lastName
? }
}
提示:我將把上面的GraphQL查詢放在一個新的GraphQL Playground選項卡中。
注意關于上面的查詢的一些事情:
(1)為了表示一個Mutation,我們使用Mutation關鍵字代替查詢。
(2)我們可以通過在圓括號中指定參數來將參數傳遞給GraphQL查詢,就像JavaScript函數一樣。
(3)在這里,我們創建一個名為John Doe的新用戶,電子郵件為john.doe@somemail.com。如果你愿意隨時改變參數。
(4)與queryUsers查詢一樣,我們可以選擇要返回的字段。請記住,此Mutation只返回創建的新用戶。
從Mutation返回的數據應該是這樣的:
如果您再次運行queryUsers查詢,而沒有重啟服務器,您應該看到一個新用戶添加到數組:
注意:重新啟動服務器時,新數據將丟失,因此請確保在實際應用程序中使用數據庫。
太棒了!我們的API現在可以查詢并向數組中添加用戶了!為了挑戰自己,我建議給每個用戶增加幾個字段。比如年齡,生日,喜歡的食物。如果您熟悉MongoDB或Firebase,請嘗試將數據庫與API集成,而不是將數據存儲在數組中。
祝賀您使用Apollo服務器構建了您的第一個GraphQL API !本教程只介紹了GraphQL的功能。還有很多東西需要學習,比如訂閱、片段、指令等等!無論您是在尋找REST API的替代方案,還是在尋找試圖擴展知識的新開發人員,GraphQL都是一個很好的選擇,而且我肯定您會喜歡使用它。
編碼快樂!
https://www.howtographql.com/