@apollo/datasource-rest

中文文档
6.4.1发布于 2024/12/07MITTypeScriptCommonJS近 30 天下载 61.1万

阿波罗服务器 v4 的 REST 数据源

中文使用文档
文档目录7 节
  1. 文档
  2. 使用量
  3. API 参考
  4. HTTP 方法
  5. 拦截获取
  6. 处理答复
  7. 与阿波罗服务器整合

阿波罗 REST 数据源

此套件导出 a( :RESTDataSource) 用于从 REST API 中取取取数据并通过 阿波罗服务器内的 GraphQL 将其曝光的类别.

RESTData Source 将 DOM 样式的获取 API 的操作包成 node-fetch 并增加以下特征:

  • 两层的缓存:
    • 内元“ 请求分解” 功能, 默认避免并行发送相同的 GET( 或 HEAD) 请求 .
    • “ HTTP 缓存” 在( 可能共享) 中提供浏览器风格缓存 KeyValueCache 它遵守标准的 HTTP 缓存头.
  • 方便的特性,例如能够将一个非序列化对象指定为 JSON 请求正文,以及可以简单指定 URL 搜索参数
  • 处理错误

文档

查看 RESTData 源代码的阿波罗服务器文档 更高级别的细节和实例.

使用量

要开始,请安装 @apollo/datasource-rest 软件包 :

npm install @apollo/datasource-rest

要定义数据源,请扩展 RESTDataSource 分类并执行解决者所需的数据获取方法。 然后可以通过阿波罗服务器提供数据源 context 执行时的对象.

你实施这些方法,可称为在《公约》中包含的便利方法 RESTDataSource 类以执行 HTTP 请求,同时使其易于建立查询参数,解析 JSON 结果并处理出错.

const { RESTDataSource } = require('@apollo/datasource-rest');

class MoviesAPI extends RESTDataSource {
  override baseURL = 'https://movies-api.example.com/';

  async getMovie(id) {
    return this.get(`movies/${encodeURIComponent(id)}`);
  }

  async getMostViewedMovies(limit = 10) {
    const data = await this.get('movies', {
      params: {
        per_page: limit.toString(), // all params entries should be strings
        order_by: 'most_viewed',
      },
    });
    return data.results;
  }
}

API 参考

RESTDataSource 被设计为子分类,以便创建一个API,供服务器的其余部分使用。 它的许多方法都受到保护。 其中包括HTTP取取方法(fetch, (中文(简体) ), get, (中文(简体) ), put, (中文(简体) ), post, (中文(简体) ), patch, (中文(简体) ), delete,以及 head您的 API 可以调用, 以及其他可以被推翻的方法来自定义行为 .

此 README 列出所有受保护的方法 。 实践上,如果你想用压倒一切的方法定制行为,读取 源代码 这是最好的选择.

构造器

这个 RESTDataSource 带入一个 DataSourceConfig 它允许压倒某些默认行为.

配置覆盖

  • cache - 自定义 KeyValueCache 执行
  • fetch - 自定义 Fetcher 执行
  • logger - 自定义 Logger 将取代内部所有伐木活动的 RESTDataSource

要覆盖 RESTData 源代码,请参见以下示例代码:

const dataSource = new (class extends RESTDataSource {})({
  cache: customCache,
  fetch: customFetcher,
  logger: customLogger,
});

属性

baseURL

用于所有 REST 呼叫的可选值 。 如果设定在您的类执行中, 此基础 URL 将作为所有调用的前缀 。 如果不设定,则转至REST呼叫的值正是所使用的值. 另见 resolveURL。 。 。 .

class MoviesAPI extends RESTDataSource {
  override baseURL = 'https://movies-api.example.com/';

  // GET
  async getMovie(id) {
    return this.get(
      `movies/${encodeURIComponent(id)}` // path
    );
  }
}

RESTDataSource 将字符串解释为诸如 this.get() 与浏览器解释网页链接的方式完全相同,其地址与 this.baseURL。如果 this.baseURL 有非空路径组件 :

  • 如果字符串传递到诸如 this.get() 以斜线开头,然后相对于 东道主 ,而不是完整的基础 URL。 也就是说,如果 this.baseURL 实值 https://foo.com/a/b/c/,则 this.get('d') 决定: https://foo.com/a/b/c/d不过 this.get('/d') 决定: https://foo.com/d。 。 。 .
  • 如果碱基 URL 有路径元素,而不以斜线结束,那么给定的路径会替换路径中最后一个元素. 也就是说,如果 baseURL 实值 https://foo.com/a/b/c, (中文(简体) ), this.get('d') 决定: https://foo.com/a/b/d

在实践中,这意味着你通常应该设置 this.baseURL 到您想要访问的所有 URL 的常见前缀 包括一条后盾,您应该通过路径 没有领先的斜线 方法,例如: this.get()。 。 。 .

如果资源路径开始于一个像 URL 的东西,因为它包含一个冒号,而您希望它在其路径之后被添加到完整的基础 URL(所以您不能通过它 ) this.get('/foo:bar')),你可以通过一条从 ./一样 this.get('./foo:bar')。 。 。 .

httpCache

这是一个在 HTTP 取取中添加 HTTP- header- 敏感缓存的内部对象 。 其精确的API是本套件的内部,可能会在版本之间变化.

B. 压倒一切的方法

cacheKeyFor

默认, RESTDatasource 使用该 cacheKey 选项,从请求作为缓存密钥,或请求方法和完整请求 URL 在将请求信息保存到 KeyValueCache。覆盖此方法以删除查询参数或计算自定义缓存密钥.

例如,您可以用它来使用头字段或HTTP方法作为缓存密钥的一部分. 尽管我们确实验证了信头字段,并且当它们不匹配时不服务于缓存的响应,但新的响应会覆盖不同信头字段的旧的. (对于HTTP方法,这可能是一件积极的事情,因为你可能想要一个 POST /foo 请求停止先前缓存 GET /foo ) 从被送回

requestDeduplicationPolicyFor

默认, RESTDataSource 全部去复制 并行 离开 GET (或 HEAD请求 在一个内存缓存中,与 KeyValueCache 用于HTTP响应缓存. 它假设两个 GET (或两个) HEAD) 并行向同一URL提出的请求可以共享相同的响应. 当请求返回时,其回复会同时发送给每个请求同一URL的呼叫者,然后从缓存中移除.

如果使用相同的缓存密钥( 默认方法 + URL) 提出请求, 但使用 HTTP 方法除外 GET 或者说 HEAD,在飞行中请求的分解无效:下一个平行 GET (或 HEAD)请求相同的URL将会提出新的请求.

您可以以多个方式配置此行为 :

  • 您可以更改哪些请求是非重复的,哪些不是.
  • 看得出来 RESTDataSource 对完成之后开始的新请求,而不仅仅是重叠请求,取消重复请求。 (这是记录不良的行为) RESTDataSource (在v 5.0.0
  • 您可以独立控制“ 引导键” KeyValueCache 缓存密钥 .

你这样做,通过压倒 requestDeduplicationPolicyFor 在你的课堂上。 此方法采用 URL 和请求, 并返回带有三种形式之一的策略对象 :

  • {policy: 'deduplicate-during-request-lifetime', deduplicationKey: string}: 这是默认行为 GET 请求。 如果同一分解密钥的请求正在进行中,则分享其结果。 否则,启动一个请求,允许其他请求在运行时针对它复制,并在请求成功返回后忘记它.
  • {policy: 'deduplicate-until-invalidated', deduplicationKey: string}: 这是默认行为 GET 在v5之前的版本中请求。 如果同一分解密钥的请求正在进行中,则分享其结果。 否则,启动请求并允许其他请求在运行时对它重复复制. 今后所有政策要求 deduplicate-during-request-lifetime 或者说 deduplicate-until-invalidated 同样的 deduplicationKey 将分享同样的结果,直到从政策开始请求 do-not-deduplicate 和匹配条目 invalidateDeduplicationKeys。 。 。 .
  • { policy: 'do-not-deduplicate'; invalidateDeduplicationKeys?: string[] }: 这是非- 的默认行为GET 请求。 总是运行一个实际的HTTP请求,不允许其他请求针对它去重复. 此外,立即取消任何列出的密钥:新要求 deduplicationKey 将不会匹配请求缓存中目前存在的任何请求.

此方法的默认实施是:

protected requestDeduplicationPolicyFor(
  url: URL,
  request: RequestOptions,
): RequestDeduplicationPolicy {
  const method = request.method ?? 'GET';
  // Start with the cache key that is used for the shared header-sensitive
  // cache. Note that its default implementation does not include the HTTP
  // method, so if a subclass overrides this and allows non-GET/HEADs to be
  // de-duplicated it will be important for it to include (at least!) the
  // method in the deduplication key, so we're explicitly adding GET/HEAD here.
  const cacheKey = this.cacheKeyFor(url, request);
  if (['GET', 'HEAD'].includes(method)) {
    return {
      policy: 'deduplicate-during-request-lifetime',
      deduplicationKey: `${method} ${cacheKey}`,
    };
  } else {
    return {
      policy: 'do-not-deduplicate',
      // Always invalidate GETs and HEADs when a different method is seen on the same
      // cache key (ie, URL), as per standard HTTP semantics. (We don't have
      // to invalidate the key with this HTTP method because we never write
      // it.)
      invalidateDeduplicationKeys: [
        this.cacheKeyFor(url, { ...request, method: 'GET' }),
        this.cacheKeyFor(url, { ...request, method: 'HEAD' }),
      ],
    };
  }

要完全禁用复制, 只要总是返回 do-not-duplicate. (这不影响 HTTP 头敏感缓存 。)

class MoviesAPI extends RESTDataSource {
  protected override requestDeduplicationPolicyFor() {
    return { policy: 'do-not-deduplicate' } as const;
  }
}
willSendRequest

处理每个请求开始时都会引用这种方法. 这叫与 path 财务报告和审定财务报表 request 提供给 fetch,保证不空 headers 财务报告和审定财务报表 params 对象。 如果有的话 Promise 它将等到承诺完成后才能继续执行请求。 见 正在截取 用于使用示例的章节.

resolveURL

在某些情况下,您会希望根据环境或其他背景值设置URL,而不是简单地针对 this.baseURL。要做到这一点,你可以覆盖 resolveURL数字 :

import type { KeyValueCache } from '@apollo/utils.keyvaluecache';

class PersonalizationAPI extends RESTDataSource {
  override async resolveURL(path: string, _request: AugmentedRequest) {
    if (!this.baseURL) {
      const addresses = await resolveSrv(path.split("/")[1] + ".service.consul");
      this.baseURL = addresses[0];
    }
    return super.resolveURL(path);
  }
}
cacheOptionsFor

允许设置 CacheOptions 用于在 HTTPCache。这与只请求缓存分开。 您可以用它来设定 TTL 以秒计的值 。 如果你回来了 {ttl: 0},该响应将不会被存储。 如果您返回一个正数,则 ttl 返回 2xx 状态代码,然后是响应 将 被缓存, 无论 HTTP 标题 : 确保这是您想要的 ! (目前没有办法说"只有缓存响应应该按照HTTP头来缓存,而是将TTL修改为特定的东西". ). 请注意,如果您不指定 ttl 这里,只是 GET 请求被缓存 .

您也可以指定 cacheOptions 任何呼吁中的“要求” get(), (中文(简体) ), post()等 (中文(简体) ). 具体注意 head() 电话根本没有缓存,所以不会对 HEAD 请求。 这可以是对象,例如: {ttl: 1},或返回对象的函数。 若为 cacheOptions 提供, cacheOptionsFor 不得称号(即, this.cacheOptionsFor 是有效的默认值 cacheOptions) (中文(简体) ).

这个 cacheOptions 函数和 cacheOptionsFor 方法可以是协同的.

override cacheOptionsFor() {
  return {
    ttl: 1
  }
}
didEncounterError

注:在之前版本的RESTData Source( < v5)中,这个钩子预计会抛出它收到的错误(默认执行确实如此). 不再需要这样做;如下文所述,在引用后将立即投出错误 didEncounterError。 。 。 .

您可以执行此钩, 以检查( 或修改) 在取取回、 剖析尸体时所抛出的错误( )parseBody()),或由 throwIfResponseIsError() 钩. 此钩子收到的错误将在此钩子被引用后立即被抛出 .

您也可以在这里完全抛出不同的错误 。 注意,默认情况下,错误是 GraphQLErrors(来自 errorFromResponse) (中文(简体) ).

parseBody

这种方法用HTTP响应调用,应当将正文读出并分解成适当的格式. 默认情况下,它会检查是否 Content-Type 页眉起于 application/json 或结束于 +json (只是将标题看成字符串而不使用 Content-Type 解析器)并返回 response.json() 如果确实如此或 response.text() 如果它没有。 如果你想用不同的方式读取尸体,请覆盖它。 这种方法应完全读取响应;如果不读取,可能导致HTTP缓存内存出漏. 如果您超过此选项, 您可能想要覆盖 cloneParsedBody 还有.

cloneParsedBody

这种方法用于克隆一个正体(供请求去重复特性使用,使多位调用者获得不同的回取值,可以单独变异). 狦 parseBody 返回基本 JSON 对象以外的值,您可能也想要覆盖此方法。 如果您使用此类的代码与从复制请求共享状态返回的值一致, 您也可以修改此方法以返回其参数而不克隆 .

shouldJSONSerializeBody

默认情况下,此方法返回 true 如果请求机构是:

  • 普通对象或数组
  • 带有一个 toJSON 方法(这不是 Buffer 或命名的类实例 FormData(中文(简体) )

您可以覆盖这种方法, 以便序列化其他对象, 如自定义类如 JSON .

throwIfResponseIsError

解析后,此方法检查一个条件(默认情况下,如果 HTTP 状态是 4xx 或 5xxx ),并扔出一个错误 errorFromResponse 如果条件得到满足.

errorFromResponse

基于响应创建错误.

catchCacheWritePromiseErrors

此类写入背景中共享的 HTTP- header- 敏感缓存( e, 该写入不是 HTTP 抓取的一部分) 。 它通过 Promise 与缓存相关的信息写入此方法。 默认情况下,此方法将添加 a catch 处理器到 Promise 将任何错误写入 console.error。如果您信任所有呼叫者,您可以使用它来进行不同的错误处理,或者不进行错误处理 fetch 方法和等待 httpCache.cacheWritePromise。 。 。 .

trace

这种方法将单个请求的整个处理包裹起来; 如果 NODE_ENV 环境变量等于 development,它记录请求方法、URL和持续时间。 您可以以不同的方式覆盖它以提供可观察性 .

HTTP 方法

这个 get 方法 RESTDataSource 制作一个 HTTP GET 请求并返回其解析后的身体。 同样,也有一些内在方法允许 POST, (中文(简体) ), PUT, (中文(简体) ), PATCH, (中文(简体) ), DELETE,以及 HEAD 请求。 (本节) head 方法返回全数 FetcherResponse 而不是身体 因为 HEAD 答复没有机构。 )

class MoviesAPI extends RESTDataSource {
  override baseURL = 'https://movies-api.example.com/';

  // an example making an HTTP POST request
  async postMovie(movie) {
    return this.post(
      `movies`, // path
      { body: movie }, // request body
    );
  }

  // an example making an HTTP PUT request
  async newMovie(movie) {
    return this.put(
      `movies`, // path
      { body: movie }, // request body
    );
  }

  // an example making an HTTP PATCH request
  async updateMovie(movie) {
    return this.patch(
      `movies`, // path
      { body: { id: movie.id, movie } }, // request body
    );
  }

  // an example making an HTTP DELETE request
  async deleteMovie(movie) {
    return this.delete(
      `movies/${encodeURIComponent(movie.id)}`, // path
    );
  }
}

HTTP 帮助器的所有功能( )get, (中文(简体) ), put, (中文(简体) ), post, (中文(简体) ), patch, (中文(简体) ), delete,以及 head)接受用于设置 body, (中文(简体) ), headers, (中文(简体) ), params, (中文(简体) ), cacheKey,以及 cacheOptions (和其他获取 API 选项).

或者,你可以使用 fetch 方法。 这种方法的返回值是一个 DataSourceFetchResult,包含 parsedBody, (中文(简体) ), response,以及其他一些关于操作如何与缓存相互作用的元数据字段.

拦截获取

数据源允许您截取获取以设置信头,查询参数,或者对发出的请求进行其他修改. 这通常用于授权或适用于所有请求的其他共同关切。 这个 constructor 当类像这样被即时化时,可以被推翻以要求额外的上下文信息:

class PersonalizationAPI extends RESTDataSource {
  private token: string;

  constructor(token: string) {
    super();
    this.token = token;
  }

  willSendRequest(path, request) {
    // set an authorization header
    request.headers['authorization'] = this.token;
    // or set a query parameter
    request.params.set('api_key', this.token);
  }
}

如果您正在使用 TypeScript, 您可以使用 AugmentedRequest 类型 willSendRequest 签名 :

import { RESTDataSource, AugmentedRequest } from '@apollo/datasource-rest';

class PersonalizationAPI extends RESTDataSource {
  override baseURL = 'https://personalization-api.example.com/';

  private token: string;
  constructor(token: string) {
    super();
    this.token = token;
  }

  override willSendRequest(_path: string, request: AugmentedRequest) {
    request.headers['authorization'] = this.token;
  }
}

处理答复

寻找 didReceiveResponse? 吗? 这段也许对你有意思.

在回复之前你可能需要读取或突变反应. 例如,您可能需要为每个请求登录一个特定的标题 。 要做到这一点,你可以推翻公众 fetch 这样的方法 :

  class MyDataSource extends RESTDataSource {
    override async fetch<TResult>(
      path: string,
      incomingRequest: DataSourceRequest = {}
    ) {
      const result = await super.fetch(path, incomingRequest);
      const header = result.response.headers.get('my-custom-header');
      if (header) {
        console.log(`Found header: ${header}`);
      }
      return result;
    }
  }

此示例会影响默认 fetch 由父母执行super) (中文(简体) ). 我们把我们的台阶附在承诺链上,读取标题,并还原最初的结果,即: super.fetch 承诺{ parsedBody, response }) (中文(简体) ).

与阿波罗服务器整合

为了让解析者访问数据源,您创建并返回您 context 函数。 (下例使用阿波罗服务器 4 API

interface MyContext {
  movies: MoviesAPI;
  personalization: PersonalizationAPI;
}

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

// The context function you provide to your integration should handle constructing your data sources on every request.
const url = await startStandaloneServer(server, {
  async context({ req }) { 
    return {
      moviesAPI: new MoviesAPI(),
      personalizationAPI: new PersonalizationAPI(req.headers['authorization']),
    };
  },
});

从我们的解析器,我们可以从上下文获取数据源,并返回结果:

const resolvers = {
  Query: {
    movie: async (_source, { id }, { moviesAPI }) => {
      return moviesAPI.getMovie(id);
    },
    mostViewedMovies: async (_source, _args, { moviesAPI }) => {
      return moviesAPI.getMostViewedMovies();
    },
    favorites: async (_source, _args, { personalizationAPI }) => {
      return personalizationAPI.getFavorites();
    },
  },
};

版本与发布记录

@apollo/datasource-rest 共发布 20 个版本,首次发布于 2022/08/20;过去 12 个月发布了 0 个版本。

版本发布日期距今
6.4.1latest2024/12/071.9 年前
6.4.02024/12/051.9 年前
6.3.02024/06/112 年前
6.2.22023/10/193 年前
6.2.12023/09/263 年前
6.2.02023/09/193 年前

关于 @apollo/datasource-rest 的常见问题

@apollo/datasource-rest 是什么?

阿波罗服务器 v4 的 REST 数据源

如何安装 @apollo/datasource-rest?

在项目目录运行 npm install @apollo/datasource-rest,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:graphql(^16.5.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@apollo/datasource-rest 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@apollo/datasource-rest 支持 ESM 还是 CommonJS?

以 CommonJS 形式发布,可以直接 require();在 ES Module 项目中也可以用 import 引入(Node.js 会把 module.exports 作为默认导出)。

@apollo/datasource-rest 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=16.14。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@apollo/datasource-rest 可以商用吗?

@apollo/datasource-rest 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@apollo/datasource-rest 的下载量有多少?

根据 npm 官方下载统计,@apollo/datasource-rest 在 2026/10/07 – 2026/11/05 这 30 天内被下载 611,275 次,日均约 20,376 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@apollo/datasource-rest 还在维护吗?

最新版本 6.4.1 发布于 2024/12/07(1.9 年前);至今共发布 20 个版本,其中过去 12 个月发布了 0 个;首次发布于 2022/08/20。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。