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

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

當談到客戶端和應用程序服務器之間的網絡請求時,REST(表現層狀態轉換的表明)是鏈接兩者最經常使用的選擇之一。在 REST API 的世界中,一切都圍繞着如何把資源做爲可訪問的 URL。而後咱們會進行 CURD 操做(新建、讀取、更新、刪除),這些操做是 HTTP 的基本方法,如 GET、POST、PUT 和 DELETE,來與數據進行交互。html

這是一個典型的 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 開發並積極維護。node

REST 的弊端

GraphQL 是一種用於開發 API 的查詢語言。和 REST(一種架構或者「一種作事方式」)相比,GraphQL 的開發基於一個理念:客戶端每次僅從服務端請求所須要的項目集合。react

在上面的例子中,使用了 REST 或者其餘相似架構。咱們請求 Star Wars 系列電影中 Luke Skywalker 出現過的電影時,咱們獲得了一系列的 電影 或者 homeworld 的名稱,他們還包含了不一樣的 API URL,引導咱們去了解不一樣 JSON 數據集的詳細信息。這確定是一個過分獲取(over fetching)的例子。客戶端爲了去獲取人物 Luke Skywalker 出如今電影中的詳情以及他家鄉星球的名稱,只能去向服務端發起多個請求。android

使用 GraphQL,就能夠將其解析爲單個網絡請求。轉到 API 網址:https://graphql.github.io/swapi-graphql/,查看運行如下查詢(query)看看。ios

注意:在下面的例子中,你能夠沒必要理會 GraphQL API 幕後的工做方式。我將在本教程後面逐步構建你本身的(多是第一個)GraphQL API。git

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

咱們將獲取咱們須要的數據。例如角色的名稱、他們的性別(gender)、家園(homeworld),以及他們出現的電影(films)的標題。運行上述查詢,你將得到如下結果:github

{
	"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,它只須要在網絡上發一個請求就能夠獲得所需結果。從而消除了任何會致使過分獲取或發送多個請求的可能性。web

先決條件

要學習本課程,你只須要在本地計算機上安裝 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,譯者認爲不是很合適)

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

下面是一個更復雜的嵌套查詢示例:

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 框架

你能夠在下面的圖中看到我安裝了所有的依賴,沒有出現任何錯誤。

在你項目的根路徑下,新建一個名字爲 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,以下所示。

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

右側的 schema 卡描述了咱們查詢 hello 的數據類型。這直接來自咱們服務器中定義的 typeDefs

!你剛建立了第一個 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,不出意外,你能夠看到下面的信息:

你也能夠在瀏覽器中訪問 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 按鈕,你將在右側部分看到熟悉的結果,以下所示。

一切正常,由於咱們的查詢類型 allPeople 具備自定義的業務邏輯,可使用 resolver 檢索全部數據(在咱們的例子中,咱們在 resolvers.js 中做爲數據提供的模擬數據)。要獲取單我的物對象,請嘗試運行相似的其餘 query。請記住,必須提供 ID。

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

運行上面的查詢,在結果中,你能夠得到獲得的每一個字段/屬性的值以進行查詢。你的結果將相似於如下內容。

完美!我相信你必定掌握瞭如何建立 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 的官方文檔中找到,這裏

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

結論

若是你一步一步完成教程,那麼祝賀你! 🎉

你已經學習瞭如何使用 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前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索