加入我们,于10月8日至10日在纽约市,了解最新的GraphQL联盟和API平台工程技巧、趋势和新闻。加入我们参加2024年纽约市的GraphQl峰会
文档
免费开始

REST 链接

在您的 GraphQL 查询中调用 REST API。


概览

  • 由于它是现有的一组 API,可能由第三方管理,因此您没有访问权限来修改后端。

使用

对于更高级或复杂的后端,您可能希望考虑使用 @apollo/server

快速开始

要开始,首先安装 Apollo Client 和我们需要的任何 peerDependencies

npm install --save @apollo/client apollo-link-rest graphql qs

之后,您可以开始设置 Apollo Client 实例:

import { ApolloClient, InMemoryCache } from '@apollo/client';
import { RestLink } from 'apollo-link-rest';
// Set `RestLink` with your endpoint
const restLink = new RestLink({ uri: "https://swapi.dev/api/" });
// Setup your client
const client = new ApolloClient({
cache: new InMemoryCache(),
link: restLink
});

现在,我们可以写出第一个

import { gql } from '@apollo/client';
const query = gql`
query Luke {
person @rest(type: "Person", path: "people/1/") {
name
}
}
`;

然后,您可以使用 Apollo Client 获取数据:

// Invoke the query and log the person's name
client.query({ query }).then(response => {
console.log(response.data.person.name);
});

选项

restLink 构造函数接受一个选项对象,可用于自定义链接的行为。以下列出支持选项:

选项类型描述
uri字符串URI 键是请求触发的字符串端点/域名(当 endpoints 提供默认值时,为 可选
endpoints: /map-of-endpoints/任何可选 端点映射。如果使用此选项,您需要向 @rest(...)指令提供端点
customFetch?任何可选 用于处理 REST 调用的自定义 fetch
头部信息?头部可选 一个对象,表示要将所有请求数据作为头部值发送。有关说明,请参阅此处
凭据?字符串可选 表示fetch调用应使用的凭据政策的字符串。有关说明,请参阅此处
fieldNameNormalizer?: /function/任何可选 一个函数,它接受响应字段名称并将其转换为符合GraphQL的名称。这对于您的REST API返回不能表示为GraphQL的字段是有用的,或者如果您想将JSON中的蛇形命名字段转换为驼峰命名字段。
fieldNameDenormalizer?: /function/任何可选 一个函数,它接受符合GraphQL的字段名称并将其转换为特定的端点名称。
typePatcher: /map-of-functions/任何可选 一个结构,允许您指定REST响应中的嵌套对象时的`__typename`。
defaultSerializer /function/任何可选 一个函数,它将在没有为`@rest`调用定义`bodySerializer`时作为`RestLink`的默认序列化器使用。该函数还将传递当前的`Header`设置,可以在请求发送到`fetch`之前更新。默认方法使用`JSON.stringify`并将`Content-Type`设置为`application/json`。
bodySerializers: /map-of-functions/任何可选 结构,允许定义替代序列化器,可以通过它们的键指定。
responseTransformer?: /function/任何可选 当您的响应数据结构不同时,使用此函数将响应结构化成Apollo期望的格式。

多个端点

如果需要使用多个端点,可以创建您的链接如下

const link = new RestLink({ endpoints: { v1: 'api.com/v1', v2: 'api.com/v2' } });

然后需要指定要使用的端点,在rest中:

const postTitleQuery1 = gql`
query PostTitle {
post @rest(type: "Post", path: "/post", endpoint: "v1") {
id
title
}
}
`;
const postTitleQuery2 = gql`
query PostTitle {
post @rest(type: "[Tag]", path: "/tags", endpoint: "v2") {
id
tags
}
}
`;

如果有默认端点,可以创建您的链接如下

const link = new RestLink({
endpoints: { github: 'github.com' },
uri: 'api.com',
});

如果没有在query中指定端点,将使用默认端点(在uri选项中指定的端点)。

类型名修补

当发送如下query时:

query MyQuery {
planets @rest(type: "PlanetPayload", path: "planets/") {
count
next
results {
name
}
}
}

外部响应对象(data.planets)从@rest(...)指令的type参数中获得__typename: "PlanetPayload"。然而,您需要有一个方法来设置PlanetPayload.results的类型名称。

您可以这样做的一种方式是提供一个typePatcher

const restLink = new RestLink({
uri: '/api',
typePatcher: {
PlanetPayload: (
data: any,
outerType: string,
patchDeeper: RestLink.FunctionalTypePatcher,
): any => {
if (data.results != null) {
data.results =
data.results.map(planet => ({ __typename: "Planet", ...planet }));
}
return data;
},
// ... other nested type patchers
},
})

如果您有一个非常轻量级的REST集成,可以使用@type(name: ...)指令。

query MyQuery {
planets @rest(type: "PlanetPayload", path: "planets/") {
count
next
results @type(name: "Planet") {
name
}
}
}

如果您有一组嵌套对象,这种方法是合适的。这种策略的成本是,每个处理这些对象的查询都需要包含 @type(name: ...),这意味着这种方法可能是相当冗长和容易出错的。

您也可以同时使用这两种方法。

query MyQuery {
planets @rest(type: "PlanetPayload", path: "planets/") {
count
next
results @type(name: "Results") {
name
}
typePatchedResults {
name
}
}
}
const restLink = new RestLink({
uri: '/api',
typePatcher: {
PlanetPayload: (
data: any,
outerType: string,
patchDeeper: RestLink.FunctionalTypePatcher,
): any => {
if (data.typePatchedResults != null) {
data.typePatchedResults =
data.typePatchedResults.map(planet => { __typename: "Planet", ...planet });
}
return data;
},
// ... other nested type patchers
},
})

警告

需要注意的是,目前typePatcher无法针对注解了@type的嵌套对象进行操作。例如,如果您在typePatcher中定义了failingResults,则它将不会被修补:

query MyQuery {
planets @rest(type: "PlanetPayload", path: "planets/") {
count
next
results @type(name: "Planet") {
name
failingResults {
name
}
}
typePatchedResults {
name
}
}
}

为了使这正常工作,您应该尽可能选择一种策略,并坚持下去——要么全部使用typePatcher,要么全部使用@type指令。

响应转换

默认情况下,Apollo 预期记录请求根位置有一个对象,集合请求根位置有一个对象的数组。例如,根据 ID(/users/1)获取用户时,期待以下响应。

{
"id": 1,
"name": "Apollo"
}

当获取用户列表(/users)时,期待以下响应。

[
{
"id": 1,
"name": "Apollo"
},
{
"id": 2,
"name": "Starman"
}
]

如果您的 API 响应结构与 Apollo 预期的不同,您可以在客户端定义一个 responseTransformer。此函数接收响应对象作为第一个 Promise参数,并作为第二个 typeName参数。它应该返回一个 Promise,因为它将负责通过调用 json()text()等来读取响应流。

例如,如果记录不在根级别

{
"meta": {},
"data": [
{
"id": 1,
"name": "Apollo"
},
{
"id": 2,
"name": "Starman"
}
]
}

可以使用以下转换器来支持它

const link = new RestLink({
uri: '/api',
responseTransformer: async response => response.json().then(({data}) => data),
});

纯文本、XML 或其他编码的响应可以通过手动解析并将它们转换为 JSON(使用 Apollo 预期格式的 previously 描述的格式)来处理

const link = new RestLink({
uri: '/xmlApi',
responseTransformer: async response => response.text().then(text => parseXmlResponseToJson(text)),
});

自定义端点响应

客户端级别的 responseTransformer适用于所有响应,跨越所有 URI 和端点。如果您需要为特定端点定义自定义 responseTransformer,可以为此特定端点定义一个选项对象。

const link = new RestLink({
endpoints: {
v1: {
uri: '/v1',
responseTransformer: async response => response.data,
},
v2: {
uri: '/v2',
responseTransformer: async (response, typeName) => response[typeName],
},
},
});

使用对象形式时,需要 uri字段。

自定义抓取

默认情况下,Apollo 使用浏览器 fetch 方法来处理对您的域/端点的 REST 请求。customFetch 选项允许您通过定义一个返回类似 Promise 的对象的处理函数来指定 您自己的 请求处理器:

const link = new RestLink({
endpoints: "/api",
customFetch: (uri, options) => new Promise((resolve, reject) => {
// Your own (asynchronous) request handler
resolve(responseObject)
}),
});

为了快速解决您的 GraphQL 查询,Apollo 会在尽可能早的时候向相关端点发出请求。这通常是可行的,但仍可能导致一次触发大量 REST 请求;特别是对于深层嵌套的查询 (见 @export 指令)

某些端点(如公开 API)可能实施 速率限制,从而导致失败响应和未解决的查询。

例如,customFetch 是管理您应用程序的 fetch 操作的好地方。 以下实现确保每次只发出 2 个请求(并发),直到下一批请求开始至少等待 500ms。

import pThrottle from "p-throttle";
const link = new RestLink({
endpoints: "/api",
customFetch: pThrottle((uri, config) => {
return fetch(uri, config);
},
2, // Max. concurrent Requests
500 // Min. delay between calls
),
});

由于 Apollo 以 Promise 为基础发出请求,我们可以按需解决它们。此示例使用 pThrottle;它是流行的 promise-fun 集合的一部分。

完整选项

以下是自定义 RestLink 的一种方法:

import fetch from 'cross-fetch';
import * as camelCase from 'camelcase';
import * as snake_case from 'snake-case';
const link = new RestLink({
endpoints: { github: 'github.com' },
uri: 'api.com',
customFetch: fetch,
headers: {
"Content-Type": "application/json"
},
credentials: "same-origin",
fieldNameNormalizer: (key: string) => camelCase(key),
fieldNameDenormalizer: (key: string) => snake_case(key),
typePatcher: {
Post: ()=> {
bodySnippet...
}
},
defaultSerializer: (data: any, headers: Headers) => {
const formData = new FormData();
for (let key in data) {
formData.append(key, data[key]);
}
headers.set("Content-Type", "x-www-form-encoded")
return {data: formData, headers};
}
});

RestLink 有一个 接口 LinkChainContext,它用作它将查找的 context 结构。它将决定如何满足特定的 RestLink 请求。(请参阅 @apollo/client/link/context 页面了解为什么您可能需要此内容)。

选项类型描述
凭据?请求凭据覆盖了 RestLink 级别的 credentials 设置。请在此处查看记录的值。
头部信息?头部在此 context-link 中提供的附加头信息。此处记录的值
headersToOverride?字符串数组如果您提供此数组,我们将通过替换根 RestLink 配置中存在的任何匹配头,将您在此链接中提供的头合并。或者,您可以使用 headersMergePolicy 来对合并行为进行更细致的定制。
headersMergePolicy?RestLink.HeadersMergePolicy这是一个函数,用于决定本 contextLink 返回的头如何与 RestLink 级别上定义的头合并。如果您不提供此函数,将简单地将头追加。要使用此选项,您可以提供自己的函数来决定如何处理头。 代码参考
restResponses?Response[]操作完成后将填充此内容,其中包含在操作期间获取的每个 REST url 的响应。如果您需要访问响应头以获取授权令牌,这很有用。

示例

RestLink 使用 headers字段,在@apollo/client/link/context上,您可以组合其他提供附加和动态头的查询

以下是将请求headers添加到上下文并检索操作响应头的一种方法:

const authRestLink = new ApolloLink((operation, forward) => {
operation.setContext(({headers}) => {
const token = localStorage.getItem("token");
return {
headers: {
...headers,
Accept: "application/json",
Authorization: token
}
};
});
return forward(operation).map(result => {
const { restResponses } = operation.getContext();
const authTokenResponse = restResponses.find(res => res.headers.has("Authorization"));
// You might also filter on res.url to find the response of a specific API call
if (authTokenResponse) {
localStorage.setItem("token", authTokenResponse.headers.get("Authorization"));
}
return result;
});
});
const restLink = new RestLink({ uri: "uri" });
const client = new ApolloClient({
cache: new InMemoryCache(),
link: ApolloLink.from([authRestLink, restLink])
});

如果您正在使用多种链式类型,restLink应该位于httpLink之前,因为httpLink将吞噬应通过apollo-link-rest路由的任何调用。

例如

const httpLink = createHttpLink({ uri: "server.com/graphql" });
const restLink = new RestLink({ uri: "api.server.com" });
const client = new ApolloClient({
cache: new InMemoryCache(),
link: ApolloLink.from([authLink, restLink, errorLink, retryLink, httpLink])
// Note: httpLink is terminating so must be last, while retry & error wrap
// the links to their right. State & context links should happen before (to
// the left of) restLink.
});

注意:如果您使用 @apollo/client/link/context 来设置 Headers,则还需要在该链接之前设置该链接。

@rest 指令

这是您设置要获取的端点的位置。rest 指令可以在查询的任何深度中使用。

参数

一个@rest(…)指令接受两个必需参数以及若干个可选参数:

选项类型描述
type字符串此指令将返回的 GraphQL 类型
path字符串REST API的uri路径。这可能是一条路径或一个完整的URL。如果是路径,则将链接创建时或从上下文提供的端点与之连接,以生成完整的URI。另请参阅:pathBuilder
方法?GETPUTPOSTDELETE发送请求的HTTP方法(即 GET、PUT、POST)
端点?字符串在创建RestLink时,如果提供了端点,则用于在(可选的)endpoints表中查找端点的键。
pathBuilder?: /function/字符串如果提供,则此函数可以控制为该请求创建的路径。
bodyKey?: "input"字符串这是在构建PUTPOST请求的REST请求体时使用的变量的名称。如果未提供,则默认为
bodyBuilder?: /function/字符串如果提供,这是调用一个函数的名称,您将其提供给variables,当需要构建请求体时,该函数会被调用。这允许您组合参数或在除了JSON之外的其他格式中编码体。
bodySerializer?: /字符串 | 函数/字符串在将请求体/头传递给fetch调用之前,查找bodySerializers中的函数或为该请求的请求体/头提供的自定义序列化函数的字符串键。默认为JSON.stringify和设置Content-Type: application-json

变量

您可以在嵌套查询内部或您的指令的路径参数中使用查询变量

query PostTitle {
post(id: "1") @rest(type: "Post", path: "/post/{args.id}") {
id
title
}
}

警告在主路径上不会自动进行encodeURIComponent调用。

此外,您还可以控制查询字符串:

query PostTitle {
postSearch(query: "some key words", page_size: 5)
@rest(type: "Post", path: "/search?{args}&{context.language}") {
id
title
}
}

注意事项

  1. 这将转换为/search?query=some%20key%20words&page_size=5&lang=en
  2. querycontext.language / lang=en是从通过@apollo/client/link/context链接添加的Apollo上下文中提取的对象。
  3. 查询字符串参数由npm:qs组装,并对它们进行了encodeURIComponent调用。

可用的来源包括:

选项描述
args这些是直接传递给此字段参数的东西。在上面的例子中,postSearch中包含了querypage_size在参数中。
exportVariables这些是在父上下文中标记为@export(as: ...)的东西。
context这些是apollo-context,因此您可以通过@apollo/client/link/context设置全局变量。
@rest这些包括您传递给@rest()指令的任何其他参数。这在使用pathBuilder时可能更有用,如下文所述。

pathBuilder

如果上述变量替换选项不足以使用,您可以为您的查询提供pathBuilder。这将用于动态构建路径。这是一个高级功能,已在源代码中进行了文档化--它还应该被认为是语法不稳定的,并且我们正在寻找反馈!

bodyKey / bodyBuilder

在进行 POSTPUT HTTP 请求时,通常需要提供请求体。按照 约定,GraphQL 建议您将输入类型命名为 input,因此默认情况下我们将在这里查找您的请求体的 JSON 对象。

bodyKey

如果您需要/想要为它命名不同,则可以传递 bodyKey,我们将查看该变量而不是。

在这个例子中,publish API 接受变量 body作为请求体,而不是作为输入:

mutation PublishPost(
$someApiWithACustomBodyKey: PublishablePostInput!
) {
publishedPost: publish(input: "Foo", body: $someApiWithACustomBodyKey)
@rest(
type: "Post"
path: "/posts/{args.input}/new"
method: "POST"
bodyKey: "body"
) {
id
title
}
}

单元测试

bodyBuilder

如果您需要以不同的方式结构化您的数据,或者需要自定义编码您的请求体(例如,作为表单编码),您可以提供 bodyBuilder 作为替代:

mutation EncryptedPost(
$input: PublishablePostInput!
$encryptor: any
) {
publishedPost: publish(input: $input)
@rest(
type: "Post"
path: "/posts/new"
method: "POST"
bodyBuilder: $encryptor
) {
id
title
}
}

单元测试

bodySerializer

如果您需要以不同的方式序列化您的数据(例如,作为表单编码),而不是依赖于默认的 JSON 序列化,您提供一个 bodySerializerbodySerializer可以是形式的函数 (data: any, headers: Headers) => {body: any, header: Headers}或字符串键。当使用字符串键时,RestLink将使用初始化期间可以传入的bodySerializers对象中的相应序列化器。

mutation EncryptedForm(
$input: PublishablePostInput!,
$formSerializer: any
) {
publishedPost: publish(input: $input)
@rest(
type: "Post",
path: "/posts/new",
method: "POST",
bodySerializer: $formSerializer
) {
id
title
}
publishRSS(input: $input)
@rest(
type: "Post",
path: "/feed",
method: "POST",
bodySerializer: "xml"
)
}

例如,formSerializer可以定义如下

const formSerializer = (data: any, headers: Headers) => {
const formData = new FormData();
for (let key in data) {
if (data.hasOwnProperty(key)) {
formData.append(key, data[key]);
}
}
headers.set('Content-Type', 'application/x-www-form-urlencoded');
return {body: formData, headers};
}

"xml"将在 RestLink中直接定义。

const restLink = new RestLink({
...otherOptions,
bodySerializers: {
xml: xmlSerializer
}
})

@export 指令

export 指令重新导出一个 field以供后来(嵌套) query使用。这部分与服务器上支持的语义相同,但在 RestLink中使用时,您可以使用导出的 进行进一步调用(例如,从嵌套 的水龙头请求)。

注意:如果您经常使用 @export,您可能更喜欢查看 @apollo/server

参数

  • as: string: 创建此作为 variable的名称,以便用于以下

示例

一个用例示例是获取用户列表,然后访问不同端点以使用在 REST 查询参数中导出的 field获取更多数据。

const QUERY = gql`
query RestData($email: String!) {
users @rest(path: '/users/email?{args.email}', method: 'GET', type: 'User') {
id @export(as: "id")
firstName
lastName
friends @rest(path: '/friends/{exportVariables.id}', type: '[User]') {
firstName
lastName
}
}
}
`;

突变

你还可以使用 apollo-link-rest 编写 ,例如:

mutation DeletePost($id: ID!) {
deletePostResponse(id: $id)
@rest(type: "Post", path: "/posts/{args.id}", method: "DELETE") {
NoResponse
}
}

故障排除

这里是几个常见的 apollo-link-rest 问题及解决方案。

  • 缺少字段 __typename in ... -- 如果你看到这个,可能是你没有提供 type:@rest(...) 指令。另外,你需要设置一个 typePatcher
  • Headers is undefined -- 如果你看到类似的信息,表明你正在浏览或不支持完整 Headers API 的其他 JavaScript 环境。

示例应用

为了帮助你开始,这里有一些示例应用

  • 简单: 一个非常简单的应用,包含一个反射设置部分的 查询
  • 高级: 一个更复杂的应用,展示了如何使用导出 指令

贡献

请在github上加入我们 apollographql/apollo-link-rest,并在 Apollo GraphQL 社区论坛中。

上一页
移除 Typename
下一页
重试
评分文章评分在GitHub上编辑编辑论坛Discord

©2024Apollo Graph Inc.,也称为Apollo GraphQL。

隐私政策

公司