/

Apollo全面介紹:GraphQL工具包

Apollo全面介紹:GraphQL工具包

Apollo是一套用於創建GraphQL服務器和使用GraphQL API的工具集合。讓我們詳細探討Apollo,包括Apollo Client和Apollo Server。

Apollo標誌

警告!此教程已過時。Apollo現在使用@apollo/xxx而不是apollo-xxx,請在我更新之前自行研究 :)

Apollo介紹

在過去的幾年中,GraphQL作為構建REST API的一種替代方案,變得非常流行。

GraphQL是一種讓客戶端決定它們想要在網絡上傳輸哪些數據的方法,而不是由服務器發送固定的數據集。

此外,它允許您指定嵌套的資源,從而減少處理REST API時有時所需的來回操作。

Apollo是一個團隊和社區,基於GraphQL進行構建,並提供了幫助您構建項目的不同工具。

Apollo提供的工具主要有3個部分:ClientServerEngine

Apollo Client幫助您使用GraphQL API,支持最流行的前端Web技術,包括React、Vue、Angular、Ember、Meteor等,以及iOS和Android的原生開發。

Apollo Server是GraphQL的服務器部分,與您的後端進行交互,並將響應返回給客戶端的請求。

Apollo Engine是一個托管基礎設施(SAAS),它充當客戶端和服務器之間的中間人,提供緩存、性能報告、負載測量、錯誤跟踪、模式字段使用統計、歷史統計和許多其他好東西。目前免費每月100萬次請求,它是Apollo唯一不開源和免費的部分,並為項目的開源部分提供資金支持。

值得注意的是,這3個工具並沒有以任何方式相互關聯,您可以僅使用Apollo Client與第三方API進行交互,或者僅使用Apollo Server來提供API,而不需要客戶端。

它們都與GraphQL標準規範相容,因此Apollo中沒有專有或不相容的技術。

但將所有這些工具放在一個地方非常方便,這是一個完整的套件,滿足您所有與GraphQL相關的需求。

Apollo致力於易於使用和易於參與貢獻。

Apollo的重點是保持事情簡單。這對於想要流行的技術非常重要,因為某些技術、框架或庫對於99%的中小型公司來說可能過於複雜,只適用於具有非常復雜需求的大公司。

Apollo Client

Apollo Client是用於GraphQL的領先JavaScript客戶端。由社區驅動,設計用於讓您構建與GraphQL數據進行接口的UI組件,無論是在顯示數據時還是在某些操作發生時進行變更。

您不需要更改應用程序中的所有內容才能使用Apollo Client。您可以從一個極小的層開始,一個請求,然後逐漸擴展。

最重要的是,Apollo Client從一開始就設計成簡單、輕巧和靈活。

在本文中,我將詳細介紹如何在React應用程序中使用Apollo Client。

我將使用GitHub的GraphQL API作為服務器。

啟動React應用程式

我使用create-react-app來設置React應用程序,這非常方便,只需添加我們所需的基礎結構即可:

1
npx create-react-app myapp

npx是最新版本的npm中可用的命令。如果您沒有這個命令,請更新npm。

然後使用npm啟動應用程序本地服務器:

1
npm start

這將在localhost:3000上啟動應用程序。

現在打開src/index.js文件:

1
2
3
4
5
6
7
8
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
import App from './App'
import registerServiceWorker from './registerServiceWorker'

ReactDOM.render(<App />, document.getElementById('root'))
registerServiceWorker()

並刪除所有這些內容。

使用Apollo Boost開始

Apollo Boost是開始在新項目中使用Apollo Client的最簡單方法。我們將安裝它,以及react-apollographql

在控制台中運行以下命令:

1
npm install apollo-boost react-apollo graphql

創建ApolloClient對象

您可以從index.js文件中的apollo-client導入ApolloClient:

1
2
3
import { ApolloClient } from 'apollo-client'

const client = new ApolloClient()

Apollo Client默認使用當前主機上的/graphql端點,因此讓我們使用Apollo Link來通過設置GraphQL端點URI來指定與GraphQL服務器的連接詳細信息。

Apollo連結

Apollo Link由apollo-link-http模塊導入的HttpLink對象表示。

Apollo Link為我們提供了一種描述如何獲取GraphQL操作的結果以及想要如何處理響應的方法。

簡而言之,您可以創建多個Apollo Link實例,它們會連續地應用在GraphQL請求上,提供您想要的最終結果。某些鏈接還可以提供重試請求的選項,批量處理等。

我們將向我們的Apollo Client實例添加一個Apollo Link,以使用GitHub的GraphQL端點URI https://api.github.com/graphql

1
2
3
4
5
6
import { ApolloClient } from 'apollo-client'
import { createHttpLink } from 'apollo-link-http'

const client = new ApolloClient({
link: createHttpLink({ uri: 'https://api.github.com/graphql' })
})

緩存

我們還沒有完成。在擁有一個可運行的示例之前,我們還必須告訴ApolloClient使用哪種caching策略InMemoryCache是默認策略,也是一個不錯的起點。

1
2
3
4
5
6
7
8
import { ApolloClient } from 'apollo-client'
import { createHttpLink } from 'apollo-link-http'
import { InMemoryCache } from 'apollo-cache-inmemory'

const client = new ApolloClient({
link: createHttpLink({ uri: 'https://api.github.com/graphql' }),
cache: new InMemoryCache()
})

使用ApolloProvider

現在,我們需要使用ApolloProvider將Apollo Client連接到組件樹中。我們可以在主React文件中使用ApolloProvider

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import React from 'react'
import ReactDOM from 'react-dom'
import { ApolloClient } from 'apollo-client'
import { createHttpLink } from 'apollo-link-http'
import { InMemoryCache } from 'apollo-cache-inmemory'
import { ApolloProvider } from 'react-apollo'

import App from './App'

const client = new ApolloClient({
link: createHttpLink({ uri: 'https://api.github.com/graphql' }),
cache: new InMemoryCache()
})

ReactDOM.render(
<ApolloProvider client={client}>
<App />
</ApolloProvider>,
document.getElementById('root')
)

這足以渲染默認create-react-app屏幕,已初始化Apollo Client:

運行Apollo Client的create-react-app

gql模板標記

我們現在可以使用Apollo Client做一些事情了,我們要從GitHub API獲取一些數據並渲染它。

為此,我們需要在index.js頂部導入gql模板標記:

1
import gql from 'graphql-tag'

使用此模板標記可以創建任何GraphQL查詢,例如:

1
2
3
4
5
const query = gql`
query {
...
}
`

執行GraphQL請求

導入gql是我們工具箱中所需的最後一個項目。

現在,我們可以使用Apollo Client做一些事情了,我們要從GitHub API獲取一些數據並渲染它。

獲取API的訪問令牌

首先要做的是從GitHub獲取一個個人訪問令牌。

GitHub通過提供一個界面,讓您選擇可能需要的任何權限,使其變得很容易:

GitHub界面以創建新的個人訪問令牌

出於這個例子教程的目的,您不需要這些權限,它們用於訪問私有用戶數據,但我們只是查詢公共存儲庫數據。

不過,您仍然需要一個令牌。

您獲得的令牌是一個OAuth 2.0 Bearer token

您可以通過在命令行中運行以下命令輕松測試它:

1
2
3
4
5
$ curl -H "Authorization: bearer ***_YOUR_TOKEN_HERE_***" -X POST -d " \
{ \
\"query\": \"query { viewer { login }}\" \
} \
" https://api.github.com/graphql

(將***_YOUR_TOKEN_HERE_***替換為實際令牌)

如果一切正常,您將獲得結果:

1
{"data":{"viewer":{"login":"***_YOUR_LOGIN_NAME_***"}}}

或者如果出現問題,例如如果您忘記插入令牌:

1
2
3
4
{
"message": "Bad credentials",
"documentation_url": "https://developer.github.com/v4"
}

使用Apollo連結進行身份驗證

現在,我們需要將Authorization標頭與我們的GraphQL請求一起發送,就像我們在上面的curl請求中所做的那樣。

我們可以通過創建一個Apollo Link中間件來使用Apollo Client進行身份驗證。首先安裝apollo-link-context

1
npm install apollo-link-context

該程序允許我們通過設置請求的上下文來添加身份驗證機制。

我們可以通過如下方式在代碼中引用setContext函數使用它:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { setContext } from 'apollo-link-context'

...

const authLink = setContext((_, { headers }) => {
const token = '***YOUR_TOKEN***'

return {
headers: {
...headers,
authorization: `Bearer ${token}`
}
}
})

一旦我們有了這個新的Apollo Link,我們就可以使用concat()方法組合現有的HttpLink,如下所示:

1
const link = authLink.concat(httpLink)

以下是具有我們現在擁有的代碼的src/index.js文件的完整代碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import React from 'react'
import ReactDOM from 'react-dom'
import { ApolloClient } from 'apollo-client'
import { createHttpLink } from 'apollo-link-http'
import { InMemoryCache } from 'apollo-cache-inmemory'
import { ApolloProvider } from 'react-apollo'
import { setContext } from 'apollo-link-context'
import gql from 'graphql-tag'

import App from './App'

const httpLink = createHttpLink({ uri: 'https://api.github.com/graphql' })

const authLink = setContext((_, { headers }) => {
const token = '***YOUR_TOKEN***'

return {
headers: {
...headers,
authorization: `Bearer ${token}`
}
}
})

const link = authLink.concat(httpLink)

const client = new ApolloClient({
link: link,
cache: new InMemoryCache()
})

ReactDOM.render(
<ApolloProvider client={client}>
<App />
</ApolloProvider>,
document.getElementById('root')
)

⚠️ 🚧 警告:請記住,此代碼是供教育目的的示例代碼,它在您的前端代碼中公開了您的GitHub GraphQL API令牌。生產代碼需要在後端保持此令牌的私密性。

現在,我們可以在文件底部執行第一個GraphQL請求,此示例查詢請求獲取超過50,000個星星的前10個最熱門存儲庫的名稱和所有者:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const POPULAR_REPOSITORIES_LIST = gql`
{
search(query: "stars:>50000", type: REPOSITORY, first: 10) {
repositoryCount
edges {
node {
... on Repository {
name
owner {
login
}
stargazers {
totalCount
}
}
}
}
}
`

client.query({ query: POPULAR_REPOSITORIES_LIST }).then(console.log)

成功運行此代碼將在瀏覽器控制台中返回我們的查詢結果:

執行查詢的控制台日誌

在組件中呈現GraphQL查詢結果集

到目前為止,我們已經做得很好了。更酷的是使用graphql結果集來渲染您的組件。

我們讓Apollo Client負責處理所有數據的提取和處理低層級的工作,我們可以專注於顯示數據,通過使用react-apollo提供的graphql組件增強器。

App.js文件中添加以下內容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import React from 'react'
import { graphql } from 'react-apollo'
import { gql } from 'apollo-boost'

const POPULAR_REPOSITORIES_LIST = gql`
{
search(query: "stars:>50000", type: REPOSITORY, first: 10) {
repositoryCount
edges {
node {
... on Repository {
name
owner {
login
}
stargazers {
totalCount
}
}
}
}
}
}
`

const App = graphql(POPULAR_REPOSITORIES_LIST)(props =>
<ul>
{props.data.loading ? '' : props.data.search.edges.map((row, i) =>
<li key={row.node.owner.login + '-' + row.node.name}>
{row.node.owner.login} / {row.node.name}: {' '}
<strong>
{row.node.stargazers.totalCount}
</strong>
</li>
)}
</ul>
)

export default App

這是我們的查詢結果在組件中呈現的結果。

在組件中呈現的查詢結果


Apollo Server

GraphQL服務器的工作是在一個端點上接受傳入的請求,解釋請求並查找滿足客戶端需求的任何數據。

不同的GraphQL服務器實現有數以百計,適用於每種可能的語言。

Apollo Server是JavaScript的GraphQL服務器實現,特別是對於Node.js平台。

它支持許多流行的Node.js框架,包括:

Apollo Server基本上提供了3個功能:

  • 提供一種描述數據的模式
  • 提供解析器的框架,這些解析器是我們為了查找用於滿足請求所需的數據而編寫的函數。
  • 便於處理API的身份驗證

首先,創建一個名為appserver的文件夾,並進入其中,運行npm init --yes 來初始化package.json文件。

然後運行npm install apollo-server graphql

Apollo Playgrounds

如果您喜歡在線遊戲場,大家有兩個很棒的Apollo playground可以使用。

首先,有一個在Glitch上託管的playground,第二個是在CodeSandbox上的playground。

您可以fork/複制這些初始項目,以創建自己的Apollo Server。

Apollo Server Hello World

創建一個index.js文件。

首先,您需要從apollo-server中導入ApolloServergql

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

我們使用gql標籤創建一個模式定義。模式定義是一個模板字面量字符串,其中包含我們的查詢的描述以及與每個字段關聯的類型:

1
2
3
4
5
const typeDefs = gql`
type Query {
hello: String
}
`

解析器是一個將模式中的字段映射到解析器函數的對象,該函數能夠查找數據以回應查詢。

這是一個簡單的解析器,包含hello字段的解析器函數,該函數返回Hello world!字符串:

1
2
3
4
5
6
7
const resolvers = {
Query: {
hello: (root, args, context) => {
return 'Hello world!'
}
}
}

有了這兩個元素,模式定義和解析器,我們初始化一個新的ApolloServer對象:

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

然後調用服務器對象上的listen()方法,並等待Promise解析,這表示服務器已就緒:

1
2
3
server.listen().then(({ url }) => {
console.log(`🚀 Server ready at ${url}`)
})

下面是簡單“Hello World”示例的完整代碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const { ApolloServer, gql } = require('apollo-server');

// 創建模式,使用GraphQL模式語言
const typeDefs = gql`
type Query {
hello: String
}
`

// 為模式字段提供解析器函數
const resolvers = {
Query: {
hello: (root, args, context) => {
return 'Hello world!'
}
}
}

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

server.listen().then(({ url }) => {
console.log(`🚀 Server ready at ${url}`)
})

現在運行node index.js,然後在控制台窗口中運行以下命令:

1
2
3
4
5
$ curl \
-X POST \
-H "Content-Type: application/json" \
--data '{ "query": "{ hello }" }' \
http://localhost:4000/graphql

這應該返回數據:

1
2
3
4
5
{
"data": {
"hello": "Hello world!"
}
}

您可以使用此簡單的App.js示例使客戶端與之接口,您可以從上面的GitHub API示例直接使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import React from 'react'
import { gql } from 'apollo-boost'
import { Query } from 'react-apollo'

const App = () => (
<Query
query={gql`
{
hello
}
`}
>
{({ loading, error, data }) => {
if (loading) return <p>Loading...</p>
if (error) return <p>Error :(</p>

return data.hello
}}
</Query>
)

export default App

您需要更改index.js文件中的httpLink URI如下:

1
const httpLink = createHttpLink({ uri: 'http://localhost:4000/graphql' })