[譯] 使用 NodeJS 建立一個 GraphQL 伺服器

0x7e2發表於2018-11-30

Hello World!在這個 GraphQL 的教程中,你可以學到如何使用 Apollo Server 庫 2.0 版本來構建一個基於 NodeJS 和 Experss 的 GraphQL 伺服器。

[譯] 使用 NodeJS 建立一個 GraphQL 伺服器

當談到客戶端和應用程式伺服器之間的網路請求時,REST(表現層狀態轉換的代表)是連線二者最常用的選擇之一。在 REST API 的世界中,一切都圍繞著如何把資源作為可訪問的 URL。然後我們會進行 CURD 操作(新建、讀取、更新、刪除),這些操作是 HTTP 的基本方法,如 GET、POST、PUT 和 DELETE,來與資料進行互動。

這是一個典型的 REST 請求的例子:

// 請求示例
https://swapi.co/api/people/

// 上面請求的 JSON 格式響應
{
	"results": [
		{
			"name": "Luke Skywalker",
			"gender": "male",
			"homeworld": "https://swapi.co/api/planets/1/",
			"films": [
				"https://swapi.co/api/films/2/",
				"https://swapi.co/api/films/6/",
				"https://swapi.co/api/films/3/",
				"https://swapi.co/api/films/1/",
				"https://swapi.co/api/films/7/"
			],
    }
		{
			"name": "C-3PO",
			"gender": "n/a",
			"homeworld": "https://swapi.co/api/planets/1/",
			"films": [
				"https://swapi.co/api/films/2/",
				"https://swapi.co/api/films/5/",
				"https://swapi.co/api/films/4/",
				"https://swapi.co/api/films/6/",
				"https://swapi.co/api/films/3/",
				"https://swapi.co/api/films/1/"
			],
		}
  ]
}
複製程式碼

REST API 的響應格式未必會是 JSON,但是這是目前大多數 API 的首選方法。除了 REST,還出現了另一種處理網路請求的方法:GraphQL。它於 2015 年開源,正在改變著開發人員在伺服器端編寫API以及在客戶端處理API的方式。並由 Facebook 開發並積極維護。

REST 的弊端

GraphQL 是一種用於開發 API 的查詢語言。和 REST(一種架構或者“一種做事方式”)相比,GraphQL 的開發基於一個理念:客戶端每次僅從服務端請求所需要的專案集合。

在上面的例子中,使用了 REST 或者其他類似架構。我們請求 Star Wars 系列電影中 Luke Skywalker 出現過的電影時,我們得到了一系列的 電影 或者 homeworld 的名稱,他們還包含了不同的 API URL,引導我們去了解不同 JSON 資料集的詳細資訊。這肯定是一個過度獲取(over fetching)的例子。客戶端為了去獲取人物 Luke Skywalker 出現在電影中的詳情以及他家鄉星球的名稱,只能去向服務端發起多個請求。

使用 GraphQL,就可以將其解析為單個網路請求。轉到 API 網址:https://graphql.github.io/swapi-graphql/,檢視執行以下查詢(query)看看。

注意:在下面的例子中,你可以不必理會 GraphQL API 幕後的工作方式。我將在本教程後面逐步構建你自己的(可能是第一個)GraphQL API。

{
	allPeople {
		edges {
			node {
				name
				gender
				homeworld {
					name
				}
				filmConnection {
					edges {
						node {
							title
						}
					}
				}
			}
		}
	}
}
複製程式碼

我們將獲取我們需要的資料。例如角色的名稱、他們的性別(gender)、家園(homeworld),以及他們出現的電影(films)的標題。執行上述查詢,你將獲得以下結果:

{
	"data": {
		"allPeople": {
			"edges": [
				{
					"node": {
						"name": "Luke Skywalker",
						"gender": "male",
						"homeworld": {
							"name": "Tatooine"
						},
						"filmConnection": {
							"edges": [
								{
									"node": {
										"title": "A New Hope"
									}
								},
								{
									"node": {
										"title": "The Empire Strikes Back"
									}
								},
								{
									"node": {
										"title": "Return of the Jedi"
									}
								},
								{
									"node": {
										"title": "Revenge of the Sith"
									}
								},
								{
									"node": {
										"title": "The Force Awakens"
									}
								}
							]
						}
					}
				},
				{
					"node": {
						"name": "C-3PO",
						"gender": "n/a",
						"homeworld": {
							"name": "Tatooine"
						},
						"filmConnection": {
							"edges": [
								{
									"node": {
										"title": "A New Hope"
									}
								},
								{
									"node": {
										"title": "The Empire Strikes Back"
									}
								},
								{
									"node": {
										"title": "Return of the Jedi"
									}
								},
								{
									"node": {
										"title": "The Phantom Menace"
									}
								},
								{
									"node": {
										"title": "Attack of the Clones"
									}
								},
								{
									"node": {
										"title": "Revenge of the Sith"
									}
								}
							]
						}
					}
				}
			]
		}
	}
}
複製程式碼

如果應用程式的客戶端正在觸發上述 GraphQL URL,它只需要在網路上發一個請求就可以得到所需結果。從而消除了任何會導致過度獲取或傳送多個請求的可能性。

先決條件

要學習本課程,你只需要在本地計算機上安裝 nodejsnpm 即可。

GraphQL 簡述

簡而言之,GraphQL 是一種用於闡述如何請求 data 的語法,通常用於從客戶端檢索資料(也稱為 query)或者對其進行更改(也稱為 mutation)。

GraphQL 幾乎沒有什麼定義特徵:

  • 它允許客戶端準確指定所需的資料。這也稱為宣告性資料提取。
  • 對網路層沒有特殊要求
  • 更容易組合來自多個源的資料
  • 在以 schema 和 query 的形式宣告資料結構時,它使用強型別系統。這有助於在傳送網路請求之前校驗查詢。

GraphQL API 的構建模組

GraphQL API 有四個構建模組:

  • schema
  • query
  • mutations
  • resolvers

Schema 以物件的形式在伺服器上定義。每個物件對應於資料型別,以便於去查詢他們。例如:

type User {
	id: ID!
	name: String
	age: Int
}
複製程式碼

上面的 schema 定義了一個使用者物件的樣子。其中必需的欄位 id! 符號標識。還包含其他欄位,例如 string 型別的 nameinteger 型別的 age。這也會在查詢資料的時候對 schema 進行驗證。

Queries 是你用來向 GraphQL API 發出請求的方法。例如,在我們上面的示例中,就像我們獲取 Star Wars 相關的資料時那樣。讓我們簡化一下,如果在 GraphQL 中查詢,就是在查詢物件的特定欄位。例如,使用上面相同的 API,我們能獲取 Star Wars 中所有角色的名稱。下面你可以看到差異,在圖片的左側是查詢,右側是結果。(譯者注:原文是 on the right-hand side is the image,譯者認為不是很合適)

[譯] 使用 NodeJS 建立一個 GraphQL 伺服器

使用 GraphQL 查詢的好處是它們可以巢狀到你想要的深度。這在 REST API 中很難做到。(在 REST API 中)操作變得複雜得多。

下面是一個更復雜的巢狀查詢示例:

[譯] 使用 NodeJS 建立一個 GraphQL 伺服器

Mutations: 在 REST 架構中,要修改資料,我們要麼使用 POST 來新增資料,要麼使用 PUT 來更新現有欄位的資料。在 GraphQL 中,整體的概念是類似的。你可以傳送一個 query 來在服務端執行寫入操作。但是。這種形式的查詢稱為 Mutation。

Resolvers 是 schema 和 data 之間的紐帶。它們提供可用於通過不同操作與資料庫互動的功能。

在這個教程中,你將學習用我們剛剛學到的構件,來使用 Nodejs 構建 GraphQL 伺服器。

Hello World!使用 GraphQL

現在我們來寫我們第一個 GraphQL 伺服器。本教程中,我們將使用 Apollo Server。我們需要為 Apollo Server 安裝三個包才能使用現有的 Express 應用程式作為中介軟體。Apollo Server 的優點在於它可以與 Node.js 的幾個流行框架一起使用:Express、KoaHapi。Apollo 本身和庫無關,因此在客戶端和伺服器應用程式中,它可以和許多第三方庫連線。

開啟你的終端安裝以下依賴:

# 首先新建一個空資料夾
mkdir apollo-express-demo

# 然後初始化
npm init -y

# 安裝需要的依賴
npm install --save graphql apollo-server-express express
複製程式碼

讓我們簡要了解下這些依賴的作用。

  • graphql 是一個支援庫,並且在我們這裡是一個必要的模組
  • 新增到現有應用程式中的 apollp-server-express 是相應的 HTTP 伺服器支援包
  • express 是 Nodejs 的 web 框架

你可以在下面的圖中看到我安裝了全部的依賴,沒有出現任何錯誤。

[譯] 使用 NodeJS 建立一個 GraphQL 伺服器

在你專案的根路徑下,新建一個名字為 index.js,包含以下程式碼的檔案。

const express = require('express');
const { ApolloServer, gql } = require('apollo-server-express');

const typeDefs = gql`
	type Query {
		hello: String
	}
`;

const resolvers = {
	Query: {
		hello: () => 'Hello world!'
	}
};

const server = new ApolloServer({ typeDefs, resolvers });

const app = express();
server.applyMiddleware({ app });

app.listen({ port: 4000 }, () =>
	console.log(`? Server ready at http://localhost:4000${server.graphqlPath}`)
);
複製程式碼

這是我們伺服器檔案的起點。開始我們僅僅只需要 express 模組。gql 是一個模板文字標記,用於將 GraphQL schema 編寫為型別。schema 由型別定義組成,並且強制包含一個用於讀取資料的 Query 型別,用於讀取資料。它還可以包含表示其他資料欄位的欄位和巢狀欄位。在我們上面的例子中,我們定義了 typeDefs 來編寫 graphQL 的 schema。

然後 resolvers 映入眼簾。Resolver 用於從 schema 中返回欄位的資料。在我們的示例中,我們定義了一個 resolver,它將函式 hello() 對映到我們的 schema 上的實現。接下來,我們建立一個 server,它使用 ApolloServer 類來例項化並啟動伺服器。由於我們使用了 Express,所以我們需要整合 ApolloServer 類。通過 applyMiddleware() 作為 app 來傳遞它,來新增 Apollo Server 的中介軟體。這裡的 app 是 Express 的一個例項,代表了現有的應用程式。

最後,我們使用 Express 模組提供的 app.listen() 來引導伺服器。要執行伺服器,只需要開啟 terminal 並執行命令 node index.js。現在,從瀏覽器視窗訪問 url:http://localhost:4000/graphql 來看看它的操作。

Apollo Server 為你設定了 GraphQL Playground,供你快速開始執行 query,探索 schema,如下所示。

[譯] 使用 NodeJS 建立一個 GraphQL 伺服器

要執行一個 query,在左側編輯空白部分,輸入以下 query。然後按中間的 ▶ (play)按鈕。

[譯] 使用 NodeJS 建立一個 GraphQL 伺服器

右側的 schema 卡描述了我們查詢 hello 的資料型別。這直接來自我們伺服器中定義的 typeDefs

[譯] 使用 NodeJS 建立一個 GraphQL 伺服器

!你剛建立了第一個 GraphQL 伺服器。現在讓我們擴充下我們對現實世界的認知。

使用 GraphQL 構建 API

目前為止我們整理了所有必要的模組以及隨附的必要術語。在這一節,我們將用 Apollo Server 為我們的演示去建立一個小的 Star Wars API。你可能已經猜到了 Apollo server 是一個庫,可以幫助你使用 Nodejs 將 GraphQL schema 連線到 HTTP server。它不侷限於特定的 Node 框架。例如上一節中我們使用了 ExpressJS。Apollo Server 支援 Koa,Restify,Hapi 和 Lambda。對於我們的 API,我們繼續使用 Express。

使用 Babel 進行編譯

如果想從頭開始,請繼續。從 Hello World! With GraphQL 一節安裝所有的庫。這是我們在前面一節中安裝的所有依賴:

"dependencies": {
		"apollo-server-express": "^2.1.0",
		"express": "^4.16.4",
		"graphql": "^14.0.2"
	}
複製程式碼

我將使用相同的專案和相同的檔案 index.js 去引導伺服器啟動。但是在我們構建我們的 API 之前,我想告訴你如何在我們的演示專案中使用 ES6 modules。對於使用像 React 和 Angular 這樣的前端庫,他們已經支援了 ES6 特性。例如 importexport default 這樣的語句。Nodejs 版本 8.x.x 解決了這個問題。我們所需要的只是一個轉換器(transpiler)讓我們使用 ES6 特性編寫 JavaScript。你完全可以跳過這個步驟使用舊的 require() 語句。

那麼什麼是轉換器呢?

轉換器(Transpiler)也被稱作‘源到源的編譯器’,從一種程式語言寫的原始碼中讀取程式碼轉換成另一種語言的等效程式碼。

在 Nodejs 的情況下,我們不會切換程式語言,而是要使用哪些我目前使用的 LTS 版本的 Node 不支援的語言的新特性。我將安裝 Babel 編譯器,並通過接下來的配置過程在我們的專案中啟用它。

首先,你需要安裝一些依賴,記得使用 -D 引數。因為我們只會在開發環境中用到這些依賴。

npm install -D babel-cli babel-preset-env babel-watch
複製程式碼

只要你成功安裝了他們,在專案的根目錄下新增一個 .babelrc 檔案並且新增以下配置:

{
	"presets": [env]
}
複製程式碼

配置流程的最後一步是在 package.json 中新增一個 dev 指令碼(script)。一旦(專案檔案)發生變化,babel 編譯器將自動執行。這由 babel-watch 完成。同時它也負責重新啟動 Nodejs 網路伺服器。

"scripts": {
	"dev": "babel-watch index.js"
}
複製程式碼

要檢視它的操作,請將以下程式碼新增到 index.js 中,看看是否一切正常。

import express from 'express';

const app = express();

app.get('/', (req, res) => res.send('Babel Working!'));

app.listen({ port: 4000 }, () =>
	console.log(`? Server ready at http://localhost:4000`)
);
複製程式碼

在終端中輸入 npm run dev,不出意外,你可以看到下面的資訊:

[譯] 使用 NodeJS 建立一個 GraphQL 伺服器

你也可以在瀏覽器中訪問 http://localhost:4000/ 去看看其操作。

新增 Schema

我們需要一個 schema 來啟動我們的 GraphQL API。讓我們在 api 目錄下建立一個名字為 api/schema.js 的新檔案。新增以下 schema。

import { gql } from 'apollo-server-express';

const typeDefs = gql`
	type Person {
		id: Int
		name: String
		gender: String
		homeworld: String
	}
	type Query {
		allPeople: [Person]
		person(id: Int!): Person
	}
`;

export default typeDefs;
複製程式碼

我們的 schema 一共包含兩個 query。第一個是 allPeople,通過它我們可以列出到 API 中的所有的人物。第二個查詢 person 是使用他們的 id 檢索一個人。這兩種查詢型別都依賴於一個名為 Person 物件的自定義型別,該物件包含四個屬性。

新增 Resolver

我們已經瞭解了 resolver 的重要性。它基於一種簡單的機制,去關聯 schema 和 data。Resolver 是包含 query 或者 mutation 背後的邏輯和函式。然後使用它們來檢索資料並在相關請求上返回。

如果在使用 Express 之前構建了伺服器,則可以將 resolver 視為控制器,其中每一個控制器都是針對特定路由構建。由於我們不在伺服器後面使用資料庫,因此我們必須提供一些虛擬資料來模擬我們的 API。

建立一個名為 resolvers.js 的新檔案並新增下面的檔案。

const defaultData = [
	{
		id: 1,
		name: 'Luke SkyWaler',
		gender: 'male',
		homeworld: 'Tattoine'
	},
	{
		id: 2,
		name: 'C-3PO',
		gender: 'bot',
		homeworld: 'Tattoine'
	}
];

const resolvers = {
	Query: {
		allPeople: () => {
			return defaultData;
		},
		person: (root, { id }) => {
			return defaultData.filter(character => {
				return (character.id = id);
			})[0];
		}
	}
};

export default resolvers;
複製程式碼

首先,我們定義 defaultData 陣列,其中包含 Star Wars 中兩個人物的詳細資訊。根據我們的 schema,陣列中的這兩個物件都有四個屬性。接下來是我們的 resolvers 物件,它包含兩個函式。這裡可以使用 allPeople() 來檢索 defaultData 陣列中的所有資料。person() 箭頭函式使用引數 id 來檢索具有請求 ID 的 person 物件。這個已經在我們的查詢中定義了。

你必須匯出 resolver 和 schema 物件才能將它們與 Apollo Server 中介軟體一起使用。

實現伺服器

現在我們定義了 schema 和 resolver,我們將要在 index.js 檔案裡邊實現伺服器。首先從 apollo-server-express 匯入 Apollo-Server。我們還需要從 api/ 資料夾匯入我們的 schema 和 resolvers 物件。然後,使用 Apollo Server Express 庫中的 GraphQL 中介軟體例項化 GraphQL API。

import express from 'express';
import { ApolloServer } from 'apollo-server-express';

import typeDefs from './api/schema';
import resolvers from './api/resolvers';

const app = express();

const PORT = 4000;

const SERVER = new ApolloServer({
	typeDefs,
	resolvers
});

SERVER.applyMiddleware({ app });

app.listen(PORT, () =>
	console.log(`? GraphQL playground is running at http://localhost:4000`)
);
複製程式碼

最後,我們使用 app.listen() 來引導我們的 Express 伺服器。你現在可以從終端執行命令 npm run dev 來執行伺服器。伺服器節點啟動後,將提示成功訊息,指示伺服器已經啟動。

現在要測試我們的 GraphQL API,在瀏覽器視窗中跳轉 http://localhost:4000/graphql URL 並執行以下 query。

{
  allPeople {
    id
    name
    gender
    homeworld
  }
}
複製程式碼

點選 play 按鈕,你將在右側部分看到熟悉的結果,如下所示。

[譯] 使用 NodeJS 建立一個 GraphQL 伺服器

一切正常,因為我們的查詢型別 allPeople 具有自定義的業務邏輯,可以使用 resolver 檢索所有資料(在我們的例子中,我們在 resolvers.js 中作為資料提供的模擬資料)。要獲取單個人物物件,請嘗試執行類似的其他 query。請記住,必須提供 ID。

{
	person(id: 1) {
		name
		homeworld
	}
}
複製程式碼

執行上面的查詢,在結果中,你可以獲得得到的每個欄位/屬性的值以進行查詢。你的結果將類似於以下內容。

[譯] 使用 NodeJS 建立一個 GraphQL 伺服器

完美!我相信你一定掌握瞭如何建立 GraphQL query 並執行它。Apollo Server 庫功能很強大。它讓我們能夠編輯 playground。假設我們要編輯 playground 的主題?我們要做的就是在建立 ApolloServer 例項時提供一個選項,在我們的例子中是 SERVER

const SERVER = new ApolloServer({
	typeDefs,
	resolvers,
	playground: {
		settings: {
			'editor.theme': 'light'
		}
	}
});
複製程式碼

playground 屬性有很多功能,例如定義 playground 的預設端點(endpoint)以更改主題。你甚至可以在生產模式啟用 playground。更多配置項可以在Apollo Server 的官方文件中找到,這裡

更改主題後我們獲取下面的結果。

[譯] 使用 NodeJS 建立一個 GraphQL 伺服器

結論

如果你一步一步完成教程,那麼祝賀你! ?

你已經學習瞭如何使用 Apollo 庫配置 Express 伺服器來設定您自己的 GraphQL API。Apollo Server 是一個開源專案,是為全棧應用程式建立 GraphQL API 的最穩定的解決方案之一。他還支援客戶端開箱即用的 React、Vue、Angular、Meteor 和 Ember 以及使用 Swift 和 Java 的 Native 移動開發。有關這方面的更多資訊可以在這裡找到。

在此 Github 倉庫中檢視教程的完整程式碼 ?

啟動一個新的 Node.js 專案,或者尋找一個 Node 開發者?

Crowdbotics 幫助企業利用 Node 構建酷炫的東西(除此之外)。如果你有一個 Node 專案,你需要其他開發者資源,請給我們留言。Crowbotics 可以幫助您估算給定產品的功能規格的構建時間,並根據您的需要提供專門的 Node 開發者。如果你使用 Node 構建,檢視 Crowdbotics

感謝 William Wickey 提供編輯方面的幫助。

如果發現譯文存在錯誤或其他需要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可獲得相應獎勵積分。文章開頭的 本文永久連結 即為本文在 GitHub 上的 MarkDown 連結。


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章