GraphQL的初學者指南——使用Node.js和Apollo構建您的第一個GraphQL API

來源: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官方網站的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:

The 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查詢返回的數據。

GraphQL的美妙之處在于,您只返回您指定的內容;如果你刪除hello查詢,它將不再顯示在數據中:


GraphQL使用聲明性數據獲取。

五、創建更高級的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

email

}

}


當調用queryUsers時,數據應該與此類似。

當我們調用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

? ? email

? }

}

提示:我將把上面的GraphQL查詢放在一個新的GraphQL Playground選項卡中。

注意關于上面的查詢的一些事情:

(1)為了表示一個Mutation,我們使用Mutation關鍵字代替查詢。

(2)我們可以通過在圓括號中指定參數來將參數傳遞給GraphQL查詢,就像JavaScript函數一樣。

(3)在這里,我們創建一個名為John Doe的新用戶,電子郵件為john.doe@somemail.com。如果你愿意隨時改變參數。

(4)與queryUsers查詢一樣,我們可以選擇要返回的字段。請記住,此Mutation只返回創建的新用戶。

從Mutation返回的數據應該是這樣的:


從addUser突變返回的數據。

如果您再次運行queryUsers查詢,而沒有重啟服務器,您應該看到一個新用戶添加到數組:


“John Doe”已經成功添加到我們的用戶數組中!

注意:重新啟動服務器時,新數據將丟失,因此請確保在實際應用程序中使用數據庫。

太棒了!我們的API現在可以查詢并向數組中添加用戶了!為了挑戰自己,我建議給每個用戶增加幾個字段。比如年齡,生日,喜歡的食物。如果您熟悉MongoDB或Firebase,請嘗試將數據庫與API集成,而不是將數據存儲在數組中。

祝賀您使用Apollo服務器構建了您的第一個GraphQL API !本教程只介紹了GraphQL的功能。還有很多東西需要學習,比如訂閱、片段、指令等等!無論您是在尋找REST API的替代方案,還是在尋找試圖擴展知識的新開發人員,GraphQL都是一個很好的選擇,而且我肯定您會喜歡使用它。

編碼快樂!

https://www.howtographql.com/

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容