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

迁移到 Apollo Server 4


📣 Apollo Server 4 已 公开发布

Apollo Server 4提供以下功能:

  • 新的
  • 可作为ECMAScript或CJS模块使用的包。

新的@apollo/server

Apache Server 3 以一组 固定包形式发布,用于与不同的Web框架和环境集成。主要的"一切皆包含" apollo-server 包通过提供一个最小可定制的 来减少设置时间。

Apollo Server 3中,apollo-server-core 包定义了一个 ApolloServer "基础"类,各个集成包(apollo-server-expressapollo-server-lambda,等等)通过略有不同的API进行子类化。这种封装结构意味着新的集成包版本将与Apollo Server本身体现在统一版本,这使得支持框架的主要版本和添加特定的集成更改具有挑战性。此外,Apollo Server 3不提供为额外的框架添加新集成的方法。

Apollo Server 4通过提供一个稳定的Web框架集成API,采取了不同的集成方法,其中包括明确支持 框架的生命周期。

新的 @apollo/server 包包括:

Apollo Server 4中没有任何特定的集成子类。而是有一个单一的 ApolloServer 类和一个所有集成都使用的单一API。

Apollo Server 3中,Apollo Server核心团队负责维护每个集成包。随着Apollo Server 4的推出,AS核心团队不再直接维护大多数集成包。相反,我们与更广泛的开源社区共同维护Apollo Server集成,使那些经常使用不同Web框架的人能够为他们的框架集成做出最佳选择。

如果您正从Apollo Server 3迁移到Apollo Server 4,请使用以下流程图查看您的迁移路径:

N
Yes
N
Yes
Am I using the apollo-server package?
Am I using the apollo-server-express package?
Use the startStandaloneServer function
See if a community-supported integration exists
Use the expressMiddleware function

@apollo/server 包导出这些函数,并与 ApolloServer 类一起导出。

如果您使用的是其他 Apollo Server 3 框架集成包(如 apollo-server-koaapollo-server-lambda),请查看我们的 集成列表,以查看是否存在针对您选择的框架的社区维护的集成包。

如果您最喜欢的框架还没有 Apollo Server 集成 方案,请通过 构建新的集成 来帮助更广泛的社区!您还可以 加入讨论以维护我们现有的集成

以下是使用框架集成的一些高层次更改

  • 您可以直接将您的 context 初始化函数传递给您的框架集成函数(例如,expressMiddlewarestartStandaloneServer),而不是传递给 ApolloServer 构造函数。
  • 您需要负责使用您框架集成函数的标准功能来 设置 HTTP 请求体解析和 CORS
  • 如果要您的服务器监听特定的 URL 路径,请将此路径直接传递给您的框架 ,而不是使用 path 选项。如果没有指定 URL 路径,Apollo Server 3 的默认值是 /graphql,因此为了保留现有行为,您应明确指定该路径。

以下部分显示了如何使用 apollo-serverapollo-server-express 的服务器更新到 Apollo Server 4。

📝 注意

以下示例中,我们使用顶级await调用来异步启动我们的服务器。如果你想查看如何设置,请查看有关入门教程的详细信息。

apollo-server 迁移

在 Apollo Server 3 中,Apollo Server 包是一个“包含电池”的包,它封装了 apollo-server-express,提供了具有最小 HTTP 级别定制的 HTTP 服务器。

如果你在 Apollo Server 3 中使用了“包含电池”的 apollo-server 包,请使用 Apollo Server 4 中的 startStandaloneServer 函数。

以下是 Apollo Server 3 代码:

apollo-server-3-standalone.ts
// npm install apollo-server graphql
import { ApolloServer } from 'apollo-server';
import { typeDefs, resolvers } from './schema';
interface MyContext {
token?: String;
}
const server = new ApolloServer({
typeDefs,
resolvers,
context: async ({ req }) => ({ token: req.headers.token }),
});
const { url } = await server.listen(4000);
console.log(`🚀 Server ready at ${url}`);
apollo-server-3-standalone.js
// npm install apollo-server graphql
import { ApolloServer } from 'apollo-server';
import { typeDefs, resolvers } from './schema';
const server = new ApolloServer({
typeDefs,
resolvers,
context: async ({ req }) => ({ token: req.headers.token }),
});
const { url } = await server.listen(4000);
console.log(`🚀 Server ready at ${url}`);

在 Apollo Server 4 中看起来像这样:

apollo-server-4-standalone.ts
// npm install @apollo/server graphql
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { typeDefs, resolvers } from './schema';
interface MyContext {
token?: String;
}
const server = new ApolloServer<MyContext>({ typeDefs, resolvers });
const { url } = await startStandaloneServer(server, {
context: async ({ req }) => ({ token: req.headers.token }),
listen: { port: 4000 },
});
console.log(`🚀 Server ready at ${url}`);
apollo-server-4-standalone.js
// npm install @apollo/server graphql
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { typeDefs, resolvers } from './schema';
const server = new ApolloServer({ typeDefs, resolvers });
const { url } = await startStandaloneServer(server, {
context: async ({ req }) => ({ token: req.headers.token }),
listen: { port: 4000 },
});
console.log(`🚀 Server ready at ${url}`);

startStandaloneServer函数接受两个;第一个是一个ApolloServer实例,该实例开始监听传入的请求。第二个是配置服务器选项的对象,它最显著地接受以下属性:

名称
类型
描述
context

Function

一个可选的context初始化函数。该context函数接收reqres选项(更多信息请见下方)。

在Apollo Server 3中,您将context函数传递给构造函数。在Apollo Server 4中,您将context函数传递给startStandaloneServer

listen

Object

一个可选的listen配置选项。listen选项接受一个对象,其属性与net.Server.listen选项对象相同

例如,在Apollo Server 3中,如果您使用server.listen(4321),现在您将listen: { port: 4321 }传递到Apollo Server 4中的startStandaloneServer函数中。如果您没有传递任何参数到Apollo Server 3的server.listen()方法;您不需要指定此listen选项。

StartStandaloneServer函数不能配置您的服务器CORS行为。如果您之前在Apollo Server 3中使用过cors构造函数选项来自定义您的CORS设置,请在Apollo Server 4中使用expressMiddleware函数

同样,如果您在Apollo Server 3中使用过stopGracePeriodMillis构造函数选项,请使用expressMiddleware函数并将stopGracePeriodMillis指定给ApolloServerPluginDrainHttpServer插件。

apollo-server-express迁移

如果您在Apollo Server 3中使用过apollo-server-express包,则请在Apollo Server 4中使用expressMiddleware函数(即,改用server.applyMiddlewareserver.getMiddleware)。

要从Apollo Server 3的Apollo Server 3的apollo-server-express包迁移到使用expressMiddleware函数,请按照以下步骤操作:

  1. 安装@apollo/servercors包。
  2. @apollo/server导入符号(即,而不是从apollo-server-expressapollo-server-core导入)。
  3. cors添加到您的服务器设置中。
  4. 移除Apollo Server 3的apollo-server-expressapollo-server-core包。
  5. 如果您正在使用apollo-server-express'的默认/graphqlURL路径(即,未指定另一个包含路径选项的URL),则可以将expressMiddleware挂载到/graphql以保持行为。要使用另一个URL路径,使用app.use在指定的路径上挂载您的服务器。

以下是 Apollo Server 3 代码:

apollo-server-3.ts
// npm install apollo-server-express apollo-server-core express graphql
import { ApolloServer } from 'apollo-server-express';
import { ApolloServerPluginDrainHttpServer } from 'apollo-server-core';
import express from 'express';
import http from 'http';
import { typeDefs, resolvers } from './schema';
interface MyContext {
token?: String;
}
const app = express();
const httpServer = http.createServer(app);
const server = new ApolloServer({
typeDefs,
resolvers,
context: async ({ req }) => ({ token: req.headers.token }),
plugins: [ApolloServerPluginDrainHttpServer({ httpServer })],
});
await server.start();
server.applyMiddleware({ app });
await new Promise<void>((resolve) => httpServer.listen({ port: 4000 }, resolve));
console.log(`🚀 Server ready at https://127.0.0.1:4000${server.graphqlPath}`);
apollo-server-3.js
// npm install apollo-server-express apollo-server-core express graphql
import { ApolloServer } from 'apollo-server-express';
import { ApolloServerPluginDrainHttpServer } from 'apollo-server-core';
import express from 'express';
import http from 'http';
import { typeDefs, resolvers } from './schema';
const app = express();
const httpServer = http.createServer(app);
const server = new ApolloServer({
typeDefs,
resolvers,
context: async ({ req }) => ({ token: req.headers.token }),
plugins: [ApolloServerPluginDrainHttpServer({ httpServer })],
});
await server.start();
server.applyMiddleware({ app });
await new Promise((resolve) => httpServer.listen({ port: 4000 }, resolve));
console.log(`🚀 Server ready at https://127.0.0.1:4000${server.graphqlPath}`);

在 Apollo Server 4 中看起来像这样:

apollo-server-4.ts
// npm install @apollo/server express graphql cors
import { ApolloServer } from '@apollo/server';
import { expressMiddleware } from '@apollo/server/express4';
import { ApolloServerPluginDrainHttpServer } from '@apollo/server/plugin/drainHttpServer';
import express from 'express';
import http from 'http';
import cors from 'cors';
import { typeDefs, resolvers } from './schema';
interface MyContext {
token?: String;
}
const app = express();
const httpServer = http.createServer(app);
const server = new ApolloServer<MyContext>({
typeDefs,
resolvers,
plugins: [ApolloServerPluginDrainHttpServer({ httpServer })],
});
await server.start();
app.use(
'/graphql',
cors<cors.CorsRequest>(),
express.json(),
expressMiddleware(server, {
context: async ({ req }) => ({ token: req.headers.token }),
}),
);
await new Promise<void>((resolve) => httpServer.listen({ port: 4000 }, resolve));
console.log(`🚀 Server ready at https://127.0.0.1:4000/graphql`);
apollo-server-4.js
// npm install @apollo/server express graphql cors
import { ApolloServer } from '@apollo/server';
import { expressMiddleware } from '@apollo/server/express4';
import { ApolloServerPluginDrainHttpServer } from '@apollo/server/plugin/drainHttpServer';
import express from 'express';
import http from 'http';
import cors from 'cors';
import { typeDefs, resolvers } from './schema';
const app = express();
const httpServer = http.createServer(app);
const server = new ApolloServer({
typeDefs,
resolvers,
plugins: [ApolloServerPluginDrainHttpServer({ httpServer })],
});
await server.start();
app.use(
'/graphql',
cors(),
express.json(),
expressMiddleware(server, {
context: async ({ req }) => ({ token: req.headers.token }),
}),
);
await new Promise((resolve) => httpServer.listen({ port: 4000 }, resolve));
console.log(`🚀 Server ready at https://127.0.0.1:4000/graphql`);

已删除的集成

Apollo Server 4对Web框架集成采取了根本不同的方法。通过提供具有稳定HTTP抽象的良好定义的API,Apollo Server 4使贡献者能够首次构建和维护集成。

为此,Apollo Server核心团队不再维护以下集成包在Apollo Server 4中:

相反,我们现在与更广泛的社区合作,维护以下 Apollo 服务器的开源集成:Apollo Server:

如果您的框架没有集成,您总是可以构建自己的

Apollo Server 3 中,apollo-server-express 包支持 Express 以及其较旧的先行者 Connect。在 Apollo Server 4 中,expressMiddleware 已不再支持 Connect。感兴趣的开发者可以构建特定于 Connect 的中间件,如果有人这样做,欢迎提交 PR 至此迁移指南!

已合并到 @apollo/server 的软件包

如上所示,Apollo Server 4 将 apollo-serverapollo-server-expressapollo-server-core 包的功能合并到一个新的 @apollo/server 包中。

但是:还有更多!@apollo/server 包还结合了以下包:

插件位于深层次导入中

在 Apollo Server 3 中,apollo-server-core 包中导出了内建的插件,如 ApolloServerUsageReporting,在顶层。要使用这些插件,您必须安装 两个包:一个为 apollo-server-core 包,另一个为您用于导入 ApolloServer 的包(例如 apollo-serverapollo-server-express)。

在 Apollo Server 4 中,这些内建的插件是主包 @apollo/server 的一部分,该包还导入了 ApolloServer 类。该 @apollo/server 包通过深层次导出导出这些内置插件。这意味着您需要为每个内置插件使用深层次导入,这样您就可以仅评估您在应用程序中使用的插件,并使打包器更容易删除未使用的代码。

有一个例外:ApolloServerPluginLandingPageGraphQLPlayground 插件现在有自己的包 @apollo/server-plugin-landing-page-graphql-playground,您可以单独安装。

此插件安装了 GitHub 上的未维护项目 unmaintained Playground 项目作为登录页面,并为与 Apollo Server 2 兼容提供。该包将在 Apollo Server 4 发布后将不再受支持。我们强烈建议您切换到 Apollo Server 4 的默认登录页面,该页面安装了活跃维护的 Apollo Sandbox

Apollo Server 导出以下插件:

插件导入路径
ApolloServerPluginCacheControl@apollo/server/plugin/cacheControl
ApolloServerPluginCacheControlDisabled@apollo/server/plugin/disabled
ApolloServerPluginDrainHttpServer@apollo/server/plugin/drainHttpServer
ApolloServerPluginInlineTrace@apollo/server/plugin/inlineTrace
ApolloServerPluginInlineTraceDisabled@apollo/server/plugin/disabled
ApolloServerPluginLandingPageDisabled@apollo/server/plugin/disabled
ApolloServerPluginLandingPageLocalDefault@apollo/server/plugin/landingPage/default
ApolloServerPluginLandingPageProductionDefault@apollo/server/plugin/landingPage/default
ApolloServerPluginSchemaReporting@apollo/server/plugin/schemaReporting
ApolloServerPluginUsageReporting@apollo/server/plugin/usageReporting
ApolloServerPluginUsageReportingDisabled@apollo/server/plugin/disabled

例如,将以下 Apollo Server 3 代码替换为:

import { ApolloServerPluginUsageReporting } from 'apollo-server-core';

以下 Apollo Server 4 代码:

import { ApolloServerPluginUsageReporting } from '@apollo/server/plugin/usageReporting';

您还可以从与插件相同的深层次导入中导入每个插件的关联 TypeScript 类型(例如,ApolloServerPluginUsageReportingOptions)。

一旦您已更新导入,您就可以从项目中删除对 apollo-server-core 的依赖。

已知的回归问题

合适的400状态码

Apollo Server v4在处理无效的变量对象时返回200状态码,而v3以合适的400状态码响应。这个回归问题是在PR #6502中引入的,并已在Issue #7462中引起我们的注意。

具体来说,这个问题影响到了 Input variable coercion 失败的情况。变量的错误类型(例如,使用String而不是Int)或者意外的null会导致变量解析失败。此外,在输入对象中的缺失或不正确的以及在验证期间抛出错误的自定义也将导致变量解析失败。有关变量解析的更多详细信息,请参阅GraphQL规范中的“输入解析”部分。

我们建议采取措施避免这一回归,除非您已经修改了应用程序以绕过它。要实现这一点,请将status400ForVariableCoercionErrors: true选项添加到您的ApolloServer构造函数中:

new ApolloServer({
// ...
status400ForVariableCoercionErrors: true,
});
new ApolloServer({
// ...
status400ForVariableCoercionErrors: true,
});

在Apollo Server v5中,此选项将不再需要(且将被忽略)。

提升依赖关系

Node.js

Apollo Server 4支持Node.js 14.16.0及以上版本。(Apollo Server 3支持Node.js 12。)这包括所有LTS和发布时的所有当前主版本

如果您正在使用Node.js 12,请在升级到Apollo Server 4之前升级您的运行时环境。

(Apollo Server 4特别需要v14.16.0版本,而不仅仅是v14.0.0版本,因为这是支持我们最低支持的版本graphql的Node.js 14的最小版本,如下一节所述。)

graphql

Apollo Server 需要依赖 graphql(核心的 JavaScript 实现 GraphQL)。Apollo Server 4 支持 graphql v16.6.0 及更高版本,但我们 强烈推荐使用至少 v16.7.0,因为 graphql 中存在一个可能导致服务器崩溃的错误。

如果您正在使用较旧的 graphql 版本,请在升级到 Apollo Server 4 之前将其升级到支持的版本。

请注意,升级 graphql 可能需要您将项目中安装的其他库升级到新版本。例如,如果您与 Apollo Server 使用 Apollo Gateway,应在升级到 Apollo Server 4 之前,将 Apollo Gateway 升级至至少 v0.50.1 或任何 v2.x 版本以完全支持 graphql v16

TypeScript

如果您在使用 TypeScript 的 Apollo Server,必须使用 TypeScript v4.7.0 及更高版本。

关于背景,Apollo Server 使用了 v4.7 中引入的 功能。我们希望为旧版本的 TypeScript 开发 "降级" 版本的 @apollo/server's 类型定义,但发现 TypeScript 的 typesVersions 功能 有点难以使用。

如果您需要支持旧版本的 TypeScript 且愿意帮助我们让 typesVersions 正常工作,我们非常欢迎提交 pull request!

删除了构造函数选项

以下 ApolloServer 构造函数选项已被删除,转而支持其他功能或配置方法。

数据源

📣 请参阅我们新的 获取数据文章,了解更多关于在 Apollo Server 4 中数据源的概念是如何变化的。

在 Apollo Server 3 中,顶层的 dataSources 构造函数选项实际上为您应用的上下文函数添加了一个后处理步骤,创建了 DataSource子类并将它们添加到您的 context 对象的 dataSources 字段上。这意味着 context函数返回的类型与您的 context类型和插件接收的类型不同。此外,这种设计模糊了在每次请求中创建一次(即与其他上下文对象一样)DataSource对象的知识。

Apollo Server 4 移除了 dataSources 构造函数选项。现在您可以像处理其他 context 对象的部分一样处理 DataSources

在 Apollo Server 3 中,在构造每个 DataSource子类之后,Apollo Server 立即对每个新的 DataSource调用 dataSource.initialize({ cache, context })函数。如果您需要在 Apollo Server 4 中复制这种行为,您可以将 cachecontext 参数传递给每个 DataSource 构造函数。在 Apollo Server 4 中,您可以在 ApolloServer 上找到一个新的只读字段 cache

例如,下面我们使用 RESTDataSource 类以创建一个 DataSource,使用 Apollo Server 3:

Apollo Server 3
import { RESTDataSource, RequestOptions } from 'apollo-datasource-rest';
import { ApolloServer } from 'apollo-server';
class MoviesAPI extends RESTDataSource {
override baseURL = 'https://movies-api.example.com/';
override willSendRequest(request: RequestOptions) {
request.headers.set('Authorization', this.context.token);
}
async getMovie(id: string): Promise<Movie> {
return this.get<Movie>(`movies/${encodeURIComponent(id)}`);
}
async updateMovie(movie: Movie): Promise<Movie> {
return this.patch(
'movies',
// Syntax for passing a request body
{ id: movie.id, movie },
);
}
}
interface ContextValue {
token: string;
dataSources: {
moviesAPI: MoviesAPI;
};
}
const server = new ApolloServer({
typeDefs,
resolvers,
context: ({ req: ExpressRequest }): Omit<ContextValue, 'dataSources'> => {
return {
token: getTokenFromRequest(req),
};
},
dataSources: (): ContextValue['dataSources'] => {
return {
moviesAPI: new MoviesAPI(),
};
},
});
await server.listen();
Apollo Server 3
import { RESTDataSource } from 'apollo-datasource-rest';
import { ApolloServer } from 'apollo-server';
class MoviesAPI extends RESTDataSource {
baseURL = 'https://movies-api.example.com/';
willSendRequest(request) {
request.headers.set('Authorization', this.context.token);
}
async getMovie(id) {
return this.get(`movies/${encodeURIComponent(id)}`);
}
async updateMovie(movie) {
return this.patch(
'movies',
// Syntax for passing a request body
{ id: movie.id, movie },
);
}
}
const server = new ApolloServer({
typeDefs,
resolvers,
context: ({ req: ExpressRequest }) => {
return {
token: getTokenFromRequest(req),
};
},
dataSources: () => {
return {
moviesAPI: new MoviesAPI(),
};
},
});
await server.listen();

以下是使用 Apollo Server 4 编写相同代码的方法。

Apollo Server 4
import { RESTDataSource, AugmentedRequest } from '@apollo/datasource-rest';
// KeyValueCache is the type of Apollo server's default cache
import type { KeyValueCache } from '@apollo/utils.keyvaluecache';
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
class MoviesAPI extends RESTDataSource {
override baseURL = 'https://movies-api.example.com/';
private token: string;
constructor(options: { token: string; cache: KeyValueCache }) {
super(options); // this sends our server's `cache` through
this.token = options.token;
}
override willSendRequest(path: string, request: AugmentedRequest) {
request.headers.authorization = this.token;
}
async getMovie(id: string): Promise<Movie> {
return this.get<Movie>(`movies/${encodeURIComponent(id)}`);
}
async updateMovie(movie: Movie): Promise<Movie> {
return this.patch(
'movies',
// Note the way we pass request bodies has also changed!
{ body: { id: movie.id, movie } },
);
}
}
interface ContextValue {
token: string;
dataSources: {
moviesAPI: MoviesAPI;
};
}
const server = new ApolloServer<ContextValue>({
typeDefs,
resolvers,
});
const { url } = await startStandaloneServer(server, {
context: async ({ req }) => {
const token = getTokenFromRequest(req);
const { cache } = server;
return {
token,
dataSources: {
moviesAPI: new MoviesAPI({ cache, token }),
},
};
},
});
console.log(`🚀 Server ready at ${url}`);
Apollo Server 4
import { RESTDataSource } from '@apollo/datasource-rest';
// KeyValueCache is the type of Apollo server's default cache
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
class MoviesAPI extends RESTDataSource {
baseURL = 'https://movies-api.example.com/';
constructor(options) {
super(options); // this sends our server's `cache` through
this.token = options.token;
}
willSendRequest(path, request) {
request.headers.authorization = this.token;
}
async getMovie(id) {
return this.get(`movies/${encodeURIComponent(id)}`);
}
async updateMovie(movie) {
return this.patch(
'movies',
// Note the way we pass request bodies has also changed!
{ body: { id: movie.id, movie } },
);
}
}
const server = new ApolloServer({
typeDefs,
resolvers,
});
const { url } = await startStandaloneServer(server, {
context: async ({ req }) => {
const token = getTokenFromRequest(req);
const { cache } = server;
return {
token,
dataSources: {
moviesAPI: new MoviesAPI({ cache, token }),
},
};
},
});
console.log(`🚀 Server ready at ${url}`);

在 Apollo Server 4 中,我们将 apollo-datasource-rest 移至新的 @apollo/datasource-rest 包中。这两个包之间的功能大部分是相同的。然而,在如何传递请求的 headersparamscacheOptionsbody 方面存在一些小的语法差异。有关更详细的信息,请参阅 从 REST 获取

如果您想在您的 DataSource 中访问整个上下文值,您可以通过将上下文值作为一个 class(使其能够在其构造函数中通过 this 引用自身)来实现:

import { RESTDataSource, WillSendRequestOptions } from '@apollo/datasource-rest';
import { KeyValueCache } from '@apollo/utils.keyvaluecache';
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { IncomingMessage } from 'http';
class MoviesAPI extends RESTDataSource {
override baseURL = 'https://movies-api.example.com/';
private contextValue: ContextValue;
constructor(options: { contextValue: ContextValue; cache: KeyValueCache }) {
super(options); // this should send `cache` through
this.contextValue = options.contextValue;
}
override willSendRequest(path: string, request: WillSendRequestOptions) {
request.headers['authorization'] = this.contextValue.token;
}
async getMovie(id): Promise<Movie> {
return this.get<Movie>(`movies/${encodeURIComponent(id)}`);
}
}
class ContextValue {
public token: string;
public dataSources: {
moviesAPI: MoviesAPI;
};
constructor({ req, server }: { req: IncomingMessage; server: ApolloServer<ContextValue> }) {
this.token = getTokenFromRequest(req);
const { cache } = server;
this.dataSources = {
moviesAPI: new MoviesAPI({ cache, contextValue: this }),
};
}
}
const server = new ApolloServer<ContextValue>({
typeDefs,
resolvers,
});
await startStandaloneServer(server, {
context: async ({ req }) => new ContextValue({ req, server }),
});
import { RESTDataSource } from '@apollo/datasource-rest';
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
class MoviesAPI extends RESTDataSource {
baseURL = 'https://movies-api.example.com/';
constructor(options) {
super(options); // this should send `cache` through
this.contextValue = options.contextValue;
}
willSendRequest(path, request) {
request.headers['authorization'] = this.contextValue.token;
}
async getMovie(id) {
return this.get(`movies/${encodeURIComponent(id)}`);
}
}
class ContextValue {
constructor({ req, server }) {
this.token = getTokenFromRequest(req);
const { cache } = server;
this.dataSources = {
moviesAPI: new MoviesAPI({ cache, contextValue: this }),
};
}
}
const server = new ApolloServer({
typeDefs,
resolvers,
});
await startStandaloneServer(server, {
context: async ({ req }) => new ContextValue({ req, server }),
});

如果您希望在不对 Apollo Server 4 的数据源进行更改的情况下快速迁移到 不更改,下面的代码片段使用自定义插件重现了 Apollo Server 3 的 dataSources 行为。

我们将此作为短期修复方案,并鼓励您 创建适合每个源的自定义数据源类

模块

在 Apollo Server 3 中,存在几种 方法 来为您的 ApolloServer 实例提供模式。最常见的方法之一是提供 typeDefsresolvers 选项(每个选项都可以是一个数组)。另一种方法是用包含 typeDefsresolvers 键的对象数组来使用 modules 选项。在内部,这两种选项使用完全不同的逻辑来完成相同的事情。

为了简化其 API,Apollo Server 4 移除了 modules 构造函数选项。您可以替换任何之前的 modules 使用以下语法:

new ApolloServer({
typeDefs: modules.map({ typeDefs } => typeDefs),
resolvers: modules.map({ resolvers } => resolvers),
})

此外,对应的 GraphQLSchemaModule TypeScript 类型也不再导出。

mocksmockEntireSchema

在 Apollo Server 3 中,mocksmockEntireSchema 构造函数选项允许 Apollo Server 根据您的服务器模式返回模拟数据,用于 GraphQL 。在内部,Apollo Server 3 的模拟功能通过一个过时的 @graphql-tools/mocks 库提供。

Apollo Server 4 去除了 mocksmockEntireSchema 构造函数选项。您可以将 @graphql-tools/mock 包直接集成到您的应用中,以获取最新的模拟功能。有关配置模拟的更多信息,请参阅 @graphql-tools/mocks 文档

以下示例比较了 Apollo Server 3 中左侧的 mocksmockEntireSchema 构造函数选项,以及右侧使用 @graphql-tools/mock 的替代方案。您还可以在不影响行为的情况下逐步将这些更改应用到 Apollo Server 3 中。

Apollo Server 3
new ApolloServer({
mocks: true,
});
Apollo Server 4
import { addMocksToSchema } from '@graphql-tools/mock';
import { makeExecutableSchema } from '@graphql-tools/schema';
new ApolloServer({
schema: addMocksToSchema({
schema: makeExecutableSchema({ typeDefs, resolvers }),
}),
});
Apollo Server 3
const mocks = {
Int: () => 6,
};
new ApolloServer({
mocks,
});
Apollo Server 4
import { addMocksToSchema } from '@graphql-tools/mock';
import { makeExecutableSchema } from '@graphql-tools/schema';
const mocks = {
Int: () => 6,
};
new ApolloServer({
schema: addMocksToSchema({
schema: makeExecutableSchema({ typeDefs, resolvers }),
mocks,
}),
});
Apollo Server 3
const mocks = {
Int: () => 6,
};
new ApolloServer({
mocks,
mockEntireSchema: false,
});
Apollo Server 4
import { addMocksToSchema } from '@graphql-tools/mock';
import { makeExecutableSchema } from '@graphql-tools/schema';
const mocks = {
Int: () => 6,
};
new ApolloServer({
schema: addMocksToSchema({
schema: makeExecutableSchema({ typeDefs, resolvers }),
mocks,
preserveResolvers: true,
}),
});

调试

在 Apollo Server 3 中,debug 构造函数选项(默认为 true,除非 NODE_ENV 环境变量设置为 productiontest),它控制 Apollo Server 的多个不相关的方面:

  • 如果 debugtrue,则包含带有堆栈跟踪的 GraphQL 错误响应。
  • 如果 debugtrue 并且 ApolloServer 使用默认的 logger,则 Apollo Server 将打印所有 DEBUG 级别消息。
    • Apollo Server 3 很少发送 DEBUG 级别的消息,因此这主要影响使用提供的 logger 发送 DEBUG 消息的插件。
  • debug 标志也可供 GraphQLRequestContext 使用,它们可以按需使用。

Apollo Server 4 去除了 构造函数选项 debug。取而代之的是一个新的 includeStacktraceInErrorResponses 选项,该选项控制其同名的功能。和 debug 选项一样,除非 NODE_ENV 环境变量是 productiontest,否则该选项默认为 true

如果您在 Apollo Server 3 中使用 debug,您可以在 Apollo Server 4 中使用具有相同值的 includeStacktraceInErrorResponses

const apolloServerInstance = new ApolloServer<MyContext>({
typeDefs,
resolvers,
includeStacktraceInErrorResponses: true,
});

另外,如果您的应用程序或插件使用 DEBUG 线程级别的日志消息,并且您的服务器没有使用自定义 logger,则您负责设置默认的日志级别。例如,您可以使用 Apollo Server 默认使用的相同 Logger 实现:

import loglevel from 'loglevel';
const logger = loglevel.getLogger('apollo-server');
logger.setLevel(shouldShowDebugMessages ? loglevel.levels.DEBUG : loglevel.levels.INFO);
const server = new ApolloServer({
logger,
// ...
});

(注意,堆栈跟踪本身已从 extensions.exception.stacktrace 移至 extensions.stacktrace。)

formatResponse 钩子

Apollo Server 3 为顶级构造函数提供了 formatResponse 钩子。在操作成功进入 “执行” 阶段后,该钩子会被调用,允许您在将响应对象发送到客户端之前转换 GraphQL 响应对象的结构。

formatResponse 钩子接收一个成功操作的建议 operation's responserequestContext(包含一个未设置的 response 字段)。如果 formatResponse 钩子返回一个非空的 GraphQLResponse,则使用该响应代替最初接收到的 response 参数。

Apollo Server 4 去除了 formatResponse 钩子。我们建议使用 willSendResponse 插件钩子,这样您可以执行您之前使用 formatResponse 所能做到的所有事情。该 willSendResponse 插件钩子接收一个操作的建议 requestContext,其中包含一个包含 GraphQLResponse 对象的 response 字段。注意,willSendResponse 钩子允许更改 requestContext.response 字段。

Apollo Server 4 改变了 GraphQLResponse 的结构,以下将详细介绍。

Apollo Server 调用 willSendResponse 插件钩子,以处理所有执行到足以调用 requestDidStart (即:具有可解析JSON主体的请求等。Apollo Server 调用 willSendResponse 钩子的情况比之前的 formatResponse 钩子要多。

要仅在操作's "execution" 阶段之后使用 willSendResponse (例如,类似于之前的 formatResponse 钩子),可以为结果中的 data 字段存在性创建一个过滤器。如果操作的结果中有 data 字段,则它已经进入了执行阶段。请注意,有些边缘情况:例如,变量值强制转换错误会调用 formatResponse 但结果中没有 data。如果区分这些边缘情况对您很重要,请提出一个问题,我们会帮助您。

例如,如果您的 Apollo Server 3 代码像这样使用 formatResponse

new ApolloServer({
typeDefs,
resolvers,
formatResponse({ response, requestContext }) {
return {
...response,
extensions: {
...(response.extensions),
hello: 'world',
},
},
},
}),

您的 Apollo Server 4 代码可以这样使用 willSendResponse

new ApolloServer<MyContext>({
typeDefs,
resolvers,
plugins: [
{
async requestDidStart() {
return {
async willSendResponse(requestContext) {
const { response } = requestContext;
// Augment response with an extension, as long as the operation
// actually executed. (The `kind` check allows you to handle
// incremental delivery responses specially.)
if (response.body.kind === 'single' && 'data' in response.body.singleResult) {
response.body.singleResult.extensions = {
...response.body.singleResult.extensions,
hello: 'world',
};
}
},
};
},
},
],
});

executor

在 Apollo Server 3 中,有两种不同的方法来指定替换 graphql-js 执行功能的替代方案。这两种方法都需要定义一个类型为 GraphQLExecutor 的函数。一种方法是将该函数直接指定为 executor 构造函数选项。另一种方法是使用 gateway 选项。

在 Apollo Server 4 中,这种冗余已被删除:不再存在 executor 构造函数选项。

此外,TypeScript GraphQLExecutor 类型已重命名为 GatewayExecutor 并移动到 @apollo/server-gateway-interface 软件包中。)

new ApolloServer({
executor,
// ...
});
new ApolloServer({
executor,
// ...
});

如果您在 Apollo Server 3 代码中定义了一个 executor 函数并像这样使用它:

new ApolloServer({
gateway: {
async load() {
return { executor };
},
onSchemaLoadOrUpdate() {
return () => {};
},
async stop() {},
},
});
new ApolloServer({
gateway: {
async load() {
return { executor };
},
onSchemaLoadOrUpdate() {
return () => {};
},
async stop() {},
},
});

Removed features

从 Apollo Server 4 中删除了一些小型功能。

Health checks

在 Apollo Server 3 中,健康检查功能支持一个简单的 HTTP 级别健康检查,总是返回状态码 200。

Apollo Server 4 不再支持内置健康检查。我们发现在您的服务器状态检查过程中运行一个简单的 GraphQL 查询是一个更好的方法,因为查询可以确保您的服务器成功地处理流量并执行 GraphQL 操作。

每个__typenameGraphQL服务器都支持一个简单的查询,用于请求顶级Query类型的信息。这意味着每个GraphQL服务器都可以对指向URL的GET请求做出响应,例如:

https://your.server/?query=%7B__typename%7D

您还应该发送一个带有apollo-require-preflight: true头部的健康检查,以便不会被CSRF预防特性

如果您想要一个与健康检查引擎无关的HTTP服务器健康检查(例如,像Apollo Server 3中的健康检查功能),您可以在您的Web框架中添加一个始终成功的GET处理程序。

为了防止不准确的健康检查结果,请确保Accept头未设置或显式设置为application/json.

📝 注意

使用带有Apollo Server 3健康检查功能的简单查询总是生成一个不包含HTML页面的GraphQL响应。除非Accept头的值未设置或显式设置为application/json.

路径解析

在Apollo Server 3中,许多框架集成允许您使用path选项来配置Apollo Server处理请求的URL路径。默认情况下,path选项使用/graphqlURL路径。

在Apollo Server 4中,您应该使用你框架的路由功能,在你想让Apollo Server处理请求的URL路径上挂载你的集成。例如,如果您正在使用Apollo Server 3中的apollo-server-express并且希望继续使用默认的/graphql路径,现在您应该在/graphql路径上挂载expressMiddleware函数。

HTTP body 解析和 CORS

gql GraphQL 标签

Apollo Server 3 依赖于 graphql-tag npm 包,并重新导出其 gql 模板字符串标签。这个 gql 标签实际上是在围绕 graphql-js's 解析器的一个缓存包装器,并且大多数 IDE 都知道将 gql 字符串的内容视为 GraphQL。

Apollo Server 4 不依赖 graphql-tag 库,也不导出 gql 标签。如果您想继续使用 gql 标签,您可以直接将该包安装到您的应用中,然后更新您的导入,将此行替换为:

import { gql } from 'apollo-server';

这样一行

import gql from 'graphql-tag';

apollo-server 包将 gql 作为命名导出,而 gql 标签是 graphql-tag 的默认导出。

ApolloError

Apollo Server 4 移除了 ApolloErrortoApolloError,改为使用 GraphQLError

graphql 包导出了 GraphQLError,您可以使用它如下:

import { GraphQLError } from 'graphql';
// ...
throw new GraphQLError(message, {
extensions: { code: 'YOUR_ERROR_CODE' },
});

如果使用 ApolloError 的可选 code 参数:

throw new ApolloError(message, 'YOUR_ERROR_CODE');

您现在应将您的错误代码传递给 GraphQLError's extensions 选项;请参阅上述代码片段作为示例。

内置错误类

Apollo Server 3 导出几个错误类。Apollo Server 在特定情况下使用这些错误类中的一个(例如,SyntaxErrorValidationErrorUserInputError),而其他类(如 ForbiddenErrorAuthenticationError)则用于用户的 apps 中。所有这些错误类都是主要 ApolloError 类的子类。

在 Apollo Server 4 中,ApolloError 已不存在,因此 Apollo Server 不再导出特定的错误类。相反,您可以使用 graphqlGraphQLError 类来创建自己的错误代码。此外,Apollo Server 现在提供了一个错误代码枚举(ApolloServerErrorCode),您可以对其进行检查,以确定给定的错误是否是 Apollo Server 所识别的类型。

在 Apollo Server 3 中,您可以抛出一个新的 ForbiddenError,如下所示:

import { ForbiddenError } from 'apollo-server';
throw new ForbiddenError("my message", { myExtension: "foo" })

在 Apollo Server 4 中,您应该使用 GraphQLError 定义自己的错误,如下所示:

import { GraphQLError } from 'graphql';
throw new GraphQLError("my message", {
extensions: {
code: 'FORBIDDEN',
myExtension: "foo",
},
});

对于 AuthenticationError,使用代码 'UNAUTHENTICATED'

在 Apollo Server 3 中,您可以检查错误的类型,如下所示:

if (error instanceof SyntaxError)

在 Apollo Server 4 中,您可以使用 ApolloServerErrorCode 枚举来检查错误是否是 Apollo Server 所识别的类型,如下所示:

import { ApolloServerErrorCode } from '@apollo/server/errors';
if (error.extensions?.code === ApolloServerErrorCode.GRAPHQL_PARSE_FAILED)

对于 ValidationError,使用 ApolloServerErrorCode.GRAPHQL_VALIDATION_FAILED。对于 UserInputError,使用 ApolloServerErrorCode.BAD_USER_INPUT

__resolveObject

Apollo Server 4 删除了对 @apollographql/apollo-tooling 的依赖,同时删除了 __resolveObject 伪解析器。该 __resolveObject 函数是 __resolveReference 方法的非官方前身。虽然我们认为 __resolveObject 是一项有用的功能,但如果直接在 graphql-js 中实现,将更有效,而不是在 Apollo Server 中。

requestAgent 选项到 ApolloServerPluginUsageReporting

Apollo Server 的使用情况报告插件(即 ApolloServerPluginUsageReporting)允许您使用 fetcher 选项 替换其 HTTP 客户端。在 Apollo Server 3 中,您可以使用一个旧 requestAgent 选项,通过非标准的 agent 选项传递给 fetcher 函数。

Apollo Server 4 删除了 requestAgent 选项从 ApolloServerPluginUsageReporting。现在,您将传递给 ApolloServerPluginUsageReporting 的所有选项都是 Fetch API 规范的一部分。

如果您正在使用 Apollo Server 3 中的 requestAgent,可以使用 node-fetch npm 包来覆盖 fetcher

因此,您之前可以这样写:

ApolloServerPluginUsageReporting({ requestAgent })

现在您可以这样写:

import fetch from 'node-fetch';
ApolloServerPluginUsageReporting({
fetcher: (url, options) => fetch(url, {
...options,
agent: requestAgent,
}),
});

rewriteError 插件选项

在 Apollo Server 3 中,您可以通过 rewriteError 选项将一个函数指定为在将错误发送到 Apollo 服务器之前重写错误,该选项用于 ApolloServerPluginUsageReporting(适用于单体应用)和 ApolloServerPluginInlineTrace(适用于 )。

在 Apollo Server 4 中,您将在 ApolloServerPluginUsageReportingsendErrors 选项上指定相同的函数,作为 ApolloServerPluginInlineTraceincludeErrors 选项。

(此外,默认行为现在已更改为隐藏错误。

因此,您之前可以这样写:

// monoliths
new ApolloServer({
plugins: [ApolloServerPluginUsageReporting({ rewriteError })],
// ...
})
// subgraphs
new ApolloServer({
plugins: [ApolloServerPluginInlineTrace({ rewriteError })],
// ...
})

现在您可以这样写:

// monoliths
new ApolloServer({
plugins: [ApolloServerPluginUsageReporting({
sendErrors: { transform: rewriteError },
})],
// ...
})
// subgraphs
new ApolloServer({
plugins: [ApolloServerPluginInlineTrace({
includeErrors: { transform: rewriteError },
})],
// ...
})

请求中双转义 变量扩展

Apollo Server 3 和 4 都接受 JSON 体格式的 POST 请求。

Apollo Server 3 支持一个边缘情况,即 POST 请求的 JSON 编码体中,封装于询问体内部的 变量扩展 字段可以是 JSON 编码字符串。

Apollo Server 4 要求在 POST 请求的 JSON 编码体内部,变量扩展 字段必须是对象(不是双封装)。

例如,以下是一个有效的 查询

{
"query": "{ __typename }", "extensions": { "foo": 1 }
}

由于这个 查询 将是无效的:

{
"query": "{ __typename }", "extensions": "{ \"foo\": 1 }"
}

(此外,Apollo Server 4 在 variablesextensions 以对象以外的任何类型(例如数组、布尔值或 null)提供在 POST 主体中时,将以 400 状态码响应。类似地,如果 operationNamePOST 主体中以字符串以外的任何类型提供,它将以 400 状态码响应。)

如果您想恢复以前的行为,您可以在框架解析请求正文的元素之后解析 variablesextensions 字段。在 Express 中可能如下所示:)

app.use(express.json());
app.use((req, res, next) => {
if (typeof req.body?.variables === 'string') {
try {
req.body.variables = JSON.parse(req.body.variables);
} catch (e) {
// https://github.com/graphql/graphql-over-http/blob/main/spec/GraphQLOverHTTP.md#json-parsing-failure
res.status(400).send(e instanceof Error ? e.message : e);
}
}
next();
});
app.use(expressMiddleware(server));

更改功能

针对无服务器框架的新方法

Apollo Server 3中,我们创建无服务器框架的方法是通过继承 ApolloServer 并覆盖 serverlessFramework() 方法。

Apollo Server 4中, 无服务器 集成通过使用 startInBackgroundHandlingStartupErrorsByLoggingAndFailingAllRequests 方法来区分自己。这个函数名称的长度阻止了在构建非 无服务器 应用时使用它。

无服务器集成用户在传入 ApolloServer 实例之前不应调用任何与启动相关的函数:

const server = new ApolloServer({
typeDefs,
resolvers,
});
exports.handler = startServerAndCreateLambdaHandler(server);

在上面的示例中,startServerAndCreateLambdaHandler 无服务器集成函数应调用 server.startInBackgroundHandlingStartupErrorsByLoggingAndFailingAllRequests() 方法。

Apollo Server 4 的其他非无服务器框架集成需要开发者在验证服务器是否启动之前调用 server.start() 方法,并通过调用 server.assertStarted() 验证。

初始化函数 context

在 Apollo Server 3 中,您可以通过向 ApolloServer 构造函数中添加一个 context 初始化函数,将初始 contextcontext 提供给您的解析器。

// Apollo Server 3 Constructor
const server = new ApolloServer({
typeDefs,
resolvers,
csrfPrevention: true,
context: ({ req }) => ({
authScope: getScope(req.headers.authorization)
})
});

在 Apollo Server 4 中,context 函数是一个命名参数,传递给您的网络集成函数(例如,expressMiddlewarestartStandaloneServer)。ApolloServer 现在有一个泛型类型参数,指定了您的上下文值的类型。context 函数应该返回一个对象,然后您的服务器的解析器(通过 contextValue 字段)可以访问该对象。

以下是如何提供给 startStandaloneServer 函数一个 context 初始化函数的示例:

interface MyContext {
token: String;
}
const server = new ApolloServer<MyContext>({
typeDefs,
resolvers,
});
const { url } = await startStandaloneServer(server, {
// A named context function is required if you are not
// using ApolloServer<BaseContext>
context: async ({ req, res }) => ({
token: await getTokenForRequest(req),
}),
listen: { port: 4000 },
});
const server = new ApolloServer({
typeDefs,
resolvers,
});
const { url } = await startStandaloneServer(server, {
// A named context function is required if you are not
// using ApolloServer<BaseContext>
context: async ({ req, res }) => ({
token: await getTokenForRequest(req),
}),
listen: { port: 4000 },
});

context 函数的语法与 expressMiddleware 函数类似:

interface MyContext {
token: String;
}
const server = new ApolloServer<MyContext>({
typeDefs,
resolvers,
});
await server.start();
const app = express();
app.use(
// A named context function is required if you are not
// using ApolloServer<BaseContext>
expressMiddleware(server, {
context: async ({ req, res }) => ({
token: await getTokenForRequest(req),
}),
}),
);
const server = new ApolloServer({
typeDefs,
resolvers,
});
await server.start();
const app = express();
app.use(
// A named context function is required if you are not
// using ApolloServer<BaseContext>
expressMiddleware(server, {
context: async ({ req, res }) => ({
token: await getTokenForRequest(req),
}),
}),
);

expressMiddleware 函数中,传递给 context 函数的 reqres 对象是 express.Requestexpress.Response 类型。在 startStandaloneServer 函数中,reqres 对象是 http.IncomingMessagehttp.ServerResponse 类型。如果您需要在 context 函数中使用 Express 特定的属性,请使用 expressMiddleware

executeOperation 接受上下文值

方法 server.executeOperation 允许您通过直接指定操作文本而不是通过 HTTP 请求来执行 GraphQL 操作。您可以使用 executeOperation测试您的服务器

在 Apollo Server 3 中,您可以通过将第二个可选参数传递给 executeOperation 方法间接指定操作的上下文值;ApolloServer 然后将此参数传递给其 context 函数。例如,如果您正在使用 apollo-server-express,您可以创建一个 Express 请求和响应,然后将它们作为一个 { req, res } 对象传递给 executeOperation

在 Apollo Server 4 中,executeOperation 方法可以直接接收上下文值,无需绕过您的 context 函数。如果您想测试 context 函数的行为,我们建议您针对服务器运行实际的 HTTP 请求。

此外,返回的 GraphQLResponse 的结构已如以下所述发生更改。

因此,一个像这样的 Apollo Server 3 的测试:

const server = new ApolloServer({
typeDefs: 'type Query { hello: String!}',
resolvers: {
Query: {
hello: (_, __, context) => `Hello ${context.name}!`,
},
},
context: async ({ req }) => ({ name: req.headers.name }),
});
const result = await server.executeOperation(
{
query: 'query helloContext { hello }',
},
{
// A half-hearted attempt at making something vaguely like an express.Request,
// and not bothering to make the express.Response at all.
req: { headers: { name: 'world' } },
},
);
expect(result.data?.hello).toBe('Hello world!'); // -> true
const server = new ApolloServer({
typeDefs: 'type Query { hello: String!}',
resolvers: {
Query: {
hello: (_, __, context) => `Hello ${context.name}!`,
},
},
context: async ({ req }) => ({ name: req.headers.name }),
});
const result = await server.executeOperation(
{
query: 'query helloContext { hello }',
},
{
// A half-hearted attempt at making something vaguely like an express.Request,
// and not bothering to make the express.Response at all.
req: { headers: { name: 'world' } },
},
);
expect(result.data?.hello).toBe('Hello world!'); // -> true

在 Apollo Server 4 中看起来像这样:

interface MyContext {
name: string;
}
const server = new ApolloServer<MyContext>({
typeDefs: 'type Query { hello: String!}',
resolvers: {
Query: {
hello: (_, __, context) => `Hello ${context.name}!`,
},
},
});
const { body } = await server.executeOperation(
{
query: 'query helloContext { hello }',
},
{
contextValue: {
name: 'world',
},
},
);
// Note the use of Node's assert rather than Jest's expect; if using
// TypeScript, `assert` will appropriately narrow the type of `body`
// and `expect` will not.
assert(body.kind === 'single');
expect(body.singleResult.data?.hello).toBe('Hello world!'); // -> true
const server = new ApolloServer({
typeDefs: 'type Query { hello: String!}',
resolvers: {
Query: {
hello: (_, __, context) => `Hello ${context.name}!`,
},
},
});
const { body } = await server.executeOperation(
{
query: 'query helloContext { hello }',
},
{
contextValue: {
name: 'world',
},
},
);
// Note the use of Node's assert rather than Jest's expect; if using
// TypeScript, `assert` will appropriately narrow the type of `body`
// and `expect` will not.
assert(body.kind === 'single');
expect(body.singleResult.data?.hello).toBe('Hello world!'); // -> true

错误格式化更改

formatError 改进

Apollo Server 3 支持 formatError 钩子,其符号如下:

(error: GraphQLError) => GraphQLFormattedError

此钩子接收一个已经被 Apollo Server 3 更改的错误,与最初抛出的错误不同。

Apollo Server 4 中,这变为:

(formattedError: GraphQLFormattedError, error: unknown) => GraphQLFormattedError

在上面的代码中,formattedError 是根据 GraphQL 规范发送的默认 JSON 对象,error 是最初抛出的错误。如果您需要原始错误中的字段但不在 GraphQLFormattedError 中,您可以从 error 参数中访问该值。

需要注意的是:如果错误是在解析器内部抛出的,error 不是解析器代码抛出的错误;它是一个 GraphQLError 包装了它并添加了一些有用的上下文,例如解析器字段中的 path。如果您想获得您抛出的确切错误,Apollo Server 4 提供了在 @apollo/server/errors 中定义的 unwrapResolverError 函数,它可以移除这个外部层(如果您传递了解析器错误)或返回其参数(否则)。

因此,您可以按如下方式格式化错误

import { unwrapResolverError } from '@apollo/server/errors';
new ApolloServer({
formatError: (formattedError, error) => {
// Don't give the specific errors to the client.
if (unwrapResolverError(error) instanceof CustomDBError) {
return { message: 'Internal server error' };
}
// Strip `Validation: ` prefix and use `extensions.code` instead
if (formattedError.message.startsWith('Validation:')) {
return {
...formattedError,
message: formattedError.message.replace(/^Validation: /, ''),
extensions: { ...formattedError?.extensions, code: 'VALIDATION' },
};
}
// Otherwise, return the original error. The error can also
// be manipulated in other ways, as long as it's returned.
return formattedError;
},
// ...
});

error.extensions.exception 已删除

当 Apollo Server 3 格式化错误时,可能会添加一个名为 exception 的扩展。这个扩展是一个对象,对于原始错误中的每个 可枚举 属性都有一个 field。如果原始错误已经是一个 GraphQLError,则不适用。.) 此外,在开发/调试模式下时,exception 包含一个名为 stacktrace 的字符串数组。

例如,如果此代码在一个 resolver 中运行:

const e = new Error("hello");
e.extraProperty = "bye";
throw e;

那么(在调试模式下),Apollo Server 3 将按如下方式格式化错误:

{
"errors": [
{
"message": "hello",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": ["x"],
"extensions": {
"code": "INTERNAL_SERVER_ERROR",
"exception": {
"extraProperty": "bye",
"stacktrace": [
"Error: hello",
" at Object.x (file:///private/tmp/as3-t/server.mjs:8:27)",
" at field.resolve (/private/tmp/as3-t/node_modules/apollo-server-core/dist/utils/schemaInstrumentation.js:56:26)",
// more lines elided
]
}
}
}
]
}

通常很难预测哪些错误的哪些属性将被以这种方式公开,这可能导致意外信息泄露。

Apollo Server 4 中,没有 exception 扩展。将 stacktrace 直接提供在 extensions 上。如果您想从原始错误中复制一些或所有属性到格式化的错误中,您可以使用 formatError 钩子。

如果您想将错误格式化为与 Apollo Server 3 相同(带有堆栈跟踪和原始错误的可枚举属性在 exception 扩展上),您可以使用以下 formatError 实现:

function formatError(formattedError: GraphQLFormattedError, error: unknown) {
const originalError = unwrapResolverError(error);
const exception: Record<string, unknown> = {
...(typeof originalError === 'object' ? originalError : null),
};
delete exception.extensions;
if (formattedError.extensions?.stacktrace) {
exception.stacktrace = formattedError.extensions.stacktrace;
}
const extensions: Record<string, unknown> = {
...formattedError.extensions,
exception,
};
delete extensions.stacktrace;
return {
...formattedError,
extensions,
};
}
function formatError(formattedError, error) {
const originalError = unwrapResolverError(error);
const exception = {
...(typeof originalError === 'object' ? originalError : null),
};
delete exception.extensions;
if (formattedError.extensions?.stacktrace) {
exception.stacktrace = formattedError.extensions.stacktrace;
}
const extensions = {
...formattedError.extensions,
exception,
};
delete extensions.stacktrace;
return {
...formattedError,
extensions,
};
}

Apollo Server 3.5.0 及更高版本包含一个 TypeScript declare module 声明,它教 TypeScript error.extensions.exception.stacktrace 是所有 GraphQLError 对象上的字符串数组。Apollo Server 4 不提供替换品:也就是说,我们没有告诉 TypeScript error.extensions.codeerror.extensions.stacktrace 的类型。

对解决器之外错误处理改进

Apollo Server 3 以 text/plain 错误消息的形式返回与 GraphQL 操作相关的某些错误。

Apollo Server 4 现在将以 application/json JSON 响应返回所有与 landing-page 无关的响应。这意味着所有单个错误响应都像任何其他 GraphQL 错误一样渲染:

{
"errors":[{"message": "..."}]
}

此外,formatError 钩子 接收并可以格式化所有这些错误实例。

Apollo Server 4 还引入了新的插件钩子 startupDidFail, contextCreationDidFail, invalidRequestWasReceivedunexpectedErrorProcessingRequest,使插件可以观察新的设置中的错误。

在 Apollo Server 3 中,如果你的 context 函数抛出错误,则字符串 "Context creation failed: " 将始终附加到其消息中,并且错误将使用 HTTP 状态码 500(如果错误是具有 extensions.code 等于 INTERNAL_SERVER_ERROR 的 GraphQL 错误)或 400 响应。你不能选择不同的 HTTP 状态码或控制 HTTP 响应头。

在 Apollo Server 4 中,如果你的 context 函数抛出错误,则仅在抛出的错误不是 GraphQLError 时才会将字符串 "Context creation failed: " 附加到消息中。没有对 extensions.code 进行特殊处理;相反,你可以使用 extensions.http 来设置 HTTP 状态码或头部。如果没有提供该扩展,状态码默认为 500(而不是 400)。

在 Apollo Server 4 中,如果 execute 函数抛出错误,该错误将使用 HTTP 状态码 500 响应(而不是 400)。请注意,execute 函数通常会返回一个非空错误列表而不是抛出显式错误。

未进行排空的服務器警告

从 Apollo Server 3.2 及以上版本开始,在服务器关闭时添加了一个 "排空" 阶段,允许服务器在继续关闭之前完成正在进行的操作。

在 Apollo Server 4 中,如果你的服务器未设置排空而在服务器关闭时接收到操作,服务器将在失败该操作之前记录一条警告。

如果你正在使用 startStandaloneServer 函数,你的服务器将自动进行排水。如果你正在使用 expressMiddleware 或其他基于 http.Server 的 Web 服务器,你可以通过使用 ApolloServerPluginDrainHttpServer 插件 来添加排水。

缓存控制插件为未缓存的请求设置 cache-control 头部

缓存控制插件默认安装。它做两件事:根据静态和动态提示计算 requestContext.overallCachePolicy,并设置 Cache-Control 响应 HTTP 头。

在 Apollo Server 3 中,缓存控制插件仅在响应可缓存时设置 Cache-Control 头。

在 Apollo Server 4 中,缓存控制插件还设置了 Cache-Control 头(值 no-store),当响应不可缓存时。

要恢复 Apollo Server 3 的行为,您可以安装缓存控制插件并设置 calculateHttpHeaders: 'if-cacheable'

import { ApolloServer } from '@apollo/server';
import { ApolloServerPluginCacheControl } from '@apollo/server/plugin/cacheControl';
new ApolloServer({
// ...
plugins: [ApolloServerPluginCacheControl({ calculateHttpHeaders: 'if-cacheable' })],
});
import { ApolloServer } from '@apollo/server';
import { ApolloServerPluginCacheControl } from '@apollo/server/plugin/cacheControl';
new ApolloServer({
// ...
plugins: [ApolloServerPluginCacheControl({ calculateHttpHeaders: 'if-cacheable' })],
});

CacheScope 类型

在 Apollo Server 4 中,CacheScope 现在是一个字符串(PUBLICPRIVATE)的联合,而不是枚举:

export type CacheScope = 'PUBLIC' | 'PRIVATE';

您不能再键入 CacheScope.PublicCacheScope.Private。取而代之的是,只需使用字符串 'PUBLIC''PRIVATE'。定义为 CacheScope 的值将仅接受这两个值,所以任何键入错误仍会在编译时捕捉到。

现在,您可以从新的 @apollo/cache-control-types 包(而不是从 Apollo Server 包中导入)导入 CacheScope。这使得与多个 (如 @apollo/subgraph)协作的库可以引用 CacheScope,而不需要依赖于 @apollo/server

parseOptions 只影响操作解析

在 Apollo Server 3 中,parseOptions 构造函数选项用于修改 GraphQL 解析的两种无关地方:解析 GraphQL 操作时,以及当通过 typeDefs 提供模式时解析模式。如果您使用 typeDefs,则无法独立控制这些选项(如 noLocation)。此外,parseOptions 选项的 TypeScript 定义使用一个类型(来自 @graphql-tools/schema 包的 makeExecutableSchema 函数,该函数实现了 typeDefs 选项),该类型包含仅为解析模式相关的选项,例如 assumeValidSDL

在 Apollo Server 4 中,parseOptions 构造函数选项仅在解析 GraphQL 操作时使用,并且其 TypeScript 类型仅包含与解析操作相关的选项。

如果您像这样在 Apollo Server 3 中同时使用了两个 parseOptionstypeDefs 构造函数选项:

const parseOptions = { noLocation: true };
new ApolloServer({
typeDefs,
resolvers,
parseOptions,
});

并且您希望在 Apollo Server 4 中继续针对操作解析和模式解析应用相同的选项,运行 npm install @graphql-tools/schema,并将您的代码更改为如下所示:

import { makeExecutableSchema } from '@graphql-tools/schema';
const parseOptions = { noLocation: true };
new ApolloServer({
schema: makeExecutableSchema({
typeDefs,
resolvers,
// Note that if you're using `@graphql-tools/schema` v9 or newer, the parse
// options such as noLocation are passed *directly* to makeExecutableSchema,
// which we accomplish here with the `...` syntax.
// In older versions, pass it as a single option named `parseOptions`
// (ie, remove the `...`).
...parseOptions,
}),
// This one affects operation parsing. Note that if you set any SDL-specific
// options in parseOptions, you'll need to pass a `parseOptions` here that
// does not contain those options.
parseOptions,
});

Content-Type 响应标头

在 Apollo Server 3 中,Content-Type 响应标头是 application/json。Apollo Server 4 通过 charset 参数包含编码: application/json; charset=utf-8,这是根据 GraphQL over HTTP 规范推荐的。

无法将“已禁用”插件与它们的启用版本组合使用

Apollo Server 有多个插件,在特定条件下默认安装。要覆盖此行为,每个这些插件都有一个阻止此默认安装的“已禁用”版本。

但是,如果您同时手动安装一个插件和它的禁用版本会发生什么?请考虑以下代码

const server = new ApolloServer({
schema,
plugins: [
ApolloServerPluginUsageReporting(),
ApolloServerPluginUsageReportingDisabled(),
]
});
await server.start();

在 Apollo Server 3 中,如果与启用的版本一起使用,“禁用”插件将被忽略。这可能导致混淆,因为可能会给人一种尝试禁用功能完全被忽略的印象。

在 Apollo Server 4 中,await server.start() 将会在将“禁用”插件与启用版本一起使用时抛出错误,例如错误信息为 您已尝试安装 ApolloServerPluginUsageReporting 和 ApolloServerPluginUsageReportingDisabled。如果您的服务器抛出此错误,请选择启用或禁用该功能,并只安装相应的插件。

(此更改会影响使用报告、内联跟踪和缓存控制功能。它也会影响模式报告功能,尽管 ApolloServerPluginSchemaReportingDisabled 在 Apollo Server 3 中不存在。由于技术原因,它不影响主页功能:将 ApolloServerPluginLandingPageDisabled 与主页插件结合应被视为未来版本中可能变化的未指定行为。)

插件 API 变更

GraphQLRequestContext 的字段

大多数插件 API 钩子将 GraphQLRequestContext 对象作为它们的第一个参数。 Apollo Server 4 对 GraphQLRequestContext 对象做出了几个更改。

context字段已被重命名为 contextValue 以保持与 graphql-js API 的一致性,并有助于区分集成函数中(返回上下文值的函数)的 “context” 选项。

现在,GraphQLRequestContext.logger 字段是 readonly。如果您依赖于更改 logger 的能力,请提交一个 issue。

Apollo Server 4 从 GraphQLRequestContext 中删除了 schemaHash 字段。该字段是由对您的模式执行 GraphQL 查询生成的 JSON 编码的不稳定哈希。 schemaHash 字段不保证在模式更改时改变(例如,它不受模式 应用更改的影响)。如果您需要一个模式哈希,您可以使用 graphql-js's printSchema 函数在 schema 字段上,然后对输出进行哈希处理。

Apollo Server 4 移除了 debug 字段从GraphQLRequestContext 因为ApolloServer 没有了模糊的debug 选项。这个字段没有直接替代

GraphQLServerContext 中的字段

Apollo Server 4 对GraphQLServerContext 对象做了几个修改。

Apollo Server 4 将 serverWillStart 插件钩子的 TypeScript 类型从GraphQLServiceContext 修改为GraphQLServerContext,以与钩子名保持一致。

Apollo Server 4 删除了schemaHash 字段(详细信息请见上一节).

Apollo Server 4 从GraphQLServerContext 中删除了persistedQueries 字段。我们没有理由向插件提供这种特定的配置,但如果这个字段对你很重要,请提交一个 GitHub 问题。

Apollo Server 4 删除了serverlessFramework 字段,引入了新的startedInBackground 字段,提供了几乎相同的信息。在 Apollo Server 3 中,serverlessFramework 字段如果使用 ApolloServer 的子类作为无服务器框架则为true新的 API 以一种无服务器友好的方式处理启动错误。如果您的服务器使用server.startInBackgroundHandlingStartupErrorsByLoggingAndFailingAllRequests 方法(应由您的无服务器集成执行)启动,则startedInBackground 字段将返回true

GraphQLRequest

Apollo Server 4 重新构建了GraphQLRequest 对象,该对象作为requestContext.requestserver.executeOperation 的参数由插件提供。

具体来说,http 字段现在是一个HTTPGraphQLRequest 类型,而不是基于 Fetch API 的Request 对象的类型。HTTPGraphQLRequest对象不包含 URL 路径,并且其headers 字段是一个Map(键为小写)而不是 Fetch API 的Headers 对象。

GraphQLResponse

Apollo Server 4 对 GraphQLResponse 对象进行重构,该对象可供插件作为 requestContext.response 使用,也是 server.executeOperation 返回的类型。

在 Apollo Server 3 中,dataerrorsextensions 字段存在在顶级位置,靠近 http

因为 Apollo Server 4 支持增量交付指令,例如 @defer@stream (当与相应版本的 graphql-js 结合使用时),所以响应的结构现在可以表示单个结果或多个结果,因此这些字段不再存在于 GraphQLResponse 的顶级位置。

相反,在 GraphQLResponse 的顶级位置有一个 body 字段。 response.body.kind 可以是 'single''incremental'。如果是 'single',则未使用增量交付,response.body.singleResult 是一个包含 dataerrorsextensions 字段的对象。如果是 'incremental',则

此外,dataextensions 字段的数据类型都是

现在,http.headers 的值现在是一个

plugins 构造函数参数不接受工厂函数。

在 Apollo Server 3 中,提供给 new ApolloServerplugins 数组中的每个元素可以是 ApolloServerPlugin(例如,具有 fieldsrequestDidStart 的对象)或一个不带参数的 ApolloServerPlugin 返回的 “factory” 函数。

在 Apollo Server 4 中,每个元素必须是一个 ApolloServerPlugin。如果您在设置插件时使用了一个工厂函数来引用 ApolloServer 对象本身,那么您可能希望使用新的 ApolloServer.addPlugin 方法,您可以在调用 startstartStandaloneServer 之前调用此方法。

例如,如果您的 Apollo Server 3 代码如下所示:

const server = new ApolloServer({
typeDefs,
plugins: [makeFirstPlugin, () => makeSecondPlugin(server)],
});
const server = new ApolloServer({
typeDefs,
plugins: [makeFirstPlugin, () => makeSecondPlugin(server)],
});

那么您的 Apollo Server 4 代码可以如下所示:

const server = new ApolloServer({
typeDefs,
plugins: [makeFirstPlugin()],
});
server.addPlugin(makeSecondPlugin(server));
const server = new ApolloServer({
typeDefs,
plugins: [makeFirstPlugin()],
});
server.addPlugin(makeSecondPlugin(server));

插件语义更改

在 Apollo Server 4 中,requestDidStart 回调将在并行而不是串行方式下调用。

Apollo Server 4 以更一致的方式处理由多个插件钩子抛出的错误。每个错误都被一个“未预期的错误处理请求”错误包装,并使用新的 unexpectedErrorProcessingRequest 插件钩子调用。

在 Apollo Server 3 中,如果 didResolveOperation 钩子抛出了一个 GraphQLError,默认情况下服务器会返回一个 400 错误。在 Apollo Server 4 中,服务器默认会返回一个 500 错误,但可以通过设置 extensions.http 来配置,这可以在 GraphQLError 对象上设置。

自定义 gatewayGraphQLDataSource 实现

gateway 选项用于 ApolloServer 构造函数是为了与来自 @apollo/gateway 包的 ApolloGateway 类一起使用。Apollo Server 4 改变了 Apollo Server 与此对象交互的细节。如果您使用支持 graphql 16 的任何版本的 @apollo/gateway 作为您服务器的 gateway,那么这些更改不会影响您。然而,如果您提供 不是 ApolloGateway 实例的任何其他内容给此选项,您可能需要调整您自定义的代码。

您需要来定义您 gateway 的所有 TypeScript 类型现在都是 @apollo/server-gateway-interface 包的一部分,而不是直接从 Apollo Server 包中导出。此外,许多这些类型已经被重命名。

在 Apollo Server 2 中,用于构造函数选项的 TypeScript 类型称为 GraphQLService。在 Apollo Server 3 中,TypeScript 类型已更改为 GatewayInterface,但是 apollo-server-core 包继续导出相同的 GraphQLService 类型。Apollo Server 4 不再导出传统的 GraphQLService 类型。相反,现在使用从 @apollo/server-gateway-interface 导出的 GatewayInterface

在 Apollo Server 3 中,您的 gateway 可能定义 onSchemaChange 或较新的 onSchemaLoadOrUpdate。在 Apollo Server 4 中,您的 gateway 必须定义 onSchemaLoadOrUpdate

在 Apollo Server 3 中,GatewayInterface.load 方法返回 Promise<GraphQLServiceConfig>,其中包含一个 schema 和一个 executorApollo Server 4 将 GraphQLServiceConfig 重命名为 GatewayLoadResult(从 @apollo/server-gateway-interface 导出),它现在只包含一个 executor 字段。如果您想接收模式,可以使用 onSchemaLoadOrUpdate 钩子。

在 Apollo Server 3 中,executor 函数接受一个类型为 GraphQLRequestContextExecutionDidStart 的参数。虽然 Apollo Server 4 仍然有一个名为此名称的类型,但该类型 不是 executor 的参数。相反,executor 的参数类型为 GatewayGraphQLRequestContext(从 @apollo/server-gateway-interface 导出)。此数据类型结构与 Apollo Server 3 中的 GraphQLRequestContextExecutionDidStart 结构匹配,而不是 Apollo Server 4 中的结构。

同样,如果您创建一个自定义 GraphQLDataSource 类型(从 @apollo/gateway's buildService 钩子返回),它的 process 方法参数已从 Apollo Server 3 的 GraphQLRequestContext 更改为相同的 GatewayGraphQLRequestContext。(为了清晰起见:GraphQLDataSource 是 Apollo Gateway 用来与子图通信的类;它与 Apollo Server 3 的 dataSources 选项 无关。)

此外,以下类型已被重命名,并且现在从 @apollo/server-gateway-interface 导出:

  • GraphQLExecutor 现在是 GatewayExecutor
  • SchemaLoadOrUpdateCallback 现在是 GatewaySchemaLoadOrUpdateCallback
  • Unsubscriber现在被称作GatewayUnsubscriber

除了需要onSchemaLoadOrUpdate外,上述所有更改都与TypeScript类型的名称和导出包有关;Apollo Server 4中Apollo Server和Apollo Gateway之间的实际运行时API未发生变化。也就是说,您只需要更新类型声明,而无需修改运行时代码本身。

默认值更改

Apollo Server 3在v3.0.0初始版本发布后引入了几个推荐功能,但为了与旧版本兼容,这些功能默认是关闭的。在Apollo Server 4中,推荐的行为是默认的。在每种情况下,如果您想匹配Apollo Server 3的默认行为,仍然可以配置您的服务器。

默认启用CSRF防范

Apollo Server 3.7增加了一个名为CSRF防范的推荐安全功能,您可以通过构造函数选项csrfPrevention: true启用它。在Apollo Server 4中,true是默认值。如果您想禁用这个推荐的安全功能,请传递csrfPrevention: false。有关CSRF防范和CORS的更多信息,请参阅配置CORS

默认关闭HTTP批处理

Apollo Server支持批量处理HTTP请求,使得单个HTTP请求可以执行多个GraphQL操作。在Apollo Server 3中,默认支持HTTP批处理。

在Apollo Server 4中,您必须明确通过向ApolloServer构造函数传递allowBatchedHttpRequests: true来启用此功能。

并非所有的都支持HTTP批处理,批处理请求不支持增量交付。HTTP批处理可以通过共享操作之间的context对象来提高性能,但它可能会使理解任何特定请求所做的工作变得更困难。

默认缓存是有界的

每个Apollo Server都使用后端缓存,用于多个功能,包括、响应缓存插件和RESTDataSource。Apollo Server默认使用内存缓存,但您可以使用cache构造函数选项配置它使用不同的后端(如Redis或Memcached)。

在Apollo Server 3中,默认缓存是一个无界的内存缓存。这个缓存容易受到内存耗尽攻击的拒绝服务攻击,我们不推荐用户使用默认缓存。

在Apollo Server 4中,默认缓存是一个有界的内存缓存后端(这对于生产使用是安全的)。这在Apollo Server 3.9或更高版本中相当于传递cache: 'bounded'

如果您想自定义 Apollo Server 使用的缓存,Apollo 提供了两个包装包来帮助您完成这个过程:

关于使用 KeyvAdapterInMemoryLRUCache 的示例,请参见 配置外部缓存

如果您想让您服务器使用无界限的内存缓存(这可能会使您的服务器易受内存耗尽攻击),您可以使用不带任何参数的默认 Keyv 实现:

import { ApolloServer } from '@apollo/server';
import { KeyvAdapter } from '@apollo/utils.keyvadapter';
import Keyv from 'keyv';
new ApolloServer({
// DANGEROUS: Match the unsafe default behavior of Apollo Server 3's with an
// unbounded in-memory cache.
cache: new KeyvAdapter(new Keyv()),
// ...
});
import { ApolloServer } from '@apollo/server';
import { KeyvAdapter } from '@apollo/utils.keyvadapter';
import Keyv from 'keyv';
new ApolloServer({
// DANGEROUS: Match the unsafe default behavior of Apollo Server 3's with an
// unbounded in-memory cache.
cache: new KeyvAdapter(new Keyv()),
// ...
});

本地落地页默认为嵌入式 Apollo 砂盒

在 Apollo Server 3 中,默认的开发登录页面是一个包含前往 Apollo 沙箱链接的闪存页面(托管在 https://studio.apollographql.com/)。这个沙箱只有在您的服务器 CORS 配置允许源 https://studio.apollographql.com/ 的时候才有效。使用 ApolloServerPluginLandingPageLocalDefault 插件,您可以将 Apollo 沙箱直接嵌入到您服务器的登录页面。将 embed: true 传递给 ApolloServerPluginLandingPageLocalDefault 插件可以允许您的沙箱在无需额外的 CORS 配置的情况下向您的服务器发送同源请求。

在 Apollo Server 4 中,默认的开发登录页面是嵌入式的 Apollo 沙箱。请注意,默认的生产登录页面没有任何变化。

要使用来自 Apollo Server 3 的闪存页面,您可以将以下代码添加到 Apollo Server 4 构造函数中:

import { ApolloServer } from '@apollo/server';
import { ApolloServerPluginLandingPageLocalDefault, ApolloServerPluginLandingPageProductionDefault } from '@apollo/server/plugin/landingPage/default';
new ApolloServer({
// ...
plugins: [process.env.NODE_ENV === 'production' ? ApolloServerPluginLandingPageProductionDefault() : ApolloServerPluginLandingPageLocalDefault({ embed: false })],
});
import { ApolloServer } from '@apollo/server';
import { ApolloServerPluginLandingPageLocalDefault, ApolloServerPluginLandingPageProductionDefault } from '@apollo/server/plugin/landingPage/default';
new ApolloServer({
// ...
plugins: [process.env.NODE_ENV === 'production' ? ApolloServerPluginLandingPageProductionDefault() : ApolloServerPluginLandingPageLocalDefault({ embed: false })],
});

使用情况报告和行内跟踪插件默认会遮挡错误

在 Apollo Server 3 中,使用报告插件从单体服务器发送到 Apollo 服务器的跟踪包含了每条操作中发生的每个 GraphQL 错误的完整消息和 。类似地,从子图发送到 Apollo 网关(然后发送到 Apollo 服务器)的行内跟踪默认包含完整错误细节。您可以使用对适当插件的 rewriteError 选项来修改或删除这些错误。

在 Apollo Server 4 中,默认情况下不包含错误细节。默认情况下,错误消息用 <masked> 替换,错误 扩展用命名执行的插件的单个扩展 maskedBy 替换(ApolloServerPluginUsageReportingApolloServerPluginInlineTrace)。

要恢复 Apollo Server 3 的行为,您可以将 { unmodified: true } 传递到每个插件上的选项中:

// monoliths
new ApolloServer({
plugins: [ApolloServerPluginUsageReporting({
sendErrors: { unmodified: true },
})],
// ...
})
// subgraphs
new ApolloServer({
plugins: [ApolloServerPluginInlineTrace({
includeErrors: { unmodified: true },
})],
// ...
})

(如上所述,所述rewriteError 选项已被 sendErrorsincludeErrors 上的 transform 选项所取代。)

在子图中,使用情况报告插件默认是关闭的

在一个 ,您的Apollo网关或 会将 使用报告 发送到Apollo的服务器;单个 内发生的信息将通过 内联追踪 发送到网关或 。也就是说:使用报告插件不是为在联合子图中使用而设计的。

Apollo Server 3中,如果您提供了一个Apollo API密钥以及 并且没有明确安装 ApolloServerPluginUsageReportingApolloServerPluginUsageReportingDisabled 插件,即使在子图中,也会安装默认配置的 ApolloServerPluginUsageReporting 插件。

Apollo Server 4中,在联合子图中不会发生这种自动安装。尽管不推荐这样做,但仍可以明确地在您的子图中安装 ApolloServerPluginUsageReporting,但会记录一条警告。

重命名的包

以下包在Apollo Server 4中已重命名:

请注意,一旦Apollo Server 4发布,所有积极维护的Apollo包都将以@apollo/开头。这为社区集成包(例如apollo-server-integration-fastify)留出了apollo-命名空间。

typescript类型变更

几个Apollo Server 4的变更只影响typescript定义,而不影响运行时行为。例如,我们将几个特定的typescript接口重命名以更加直观,并更改定义其他接口所使用的包。

改进的context类型

在Apollo Server 3中,你从未在设置服务器时显式指定context值的类型。这意味着没有编译时检查来确保你的context函数返回类型与你的context值类型(由你的解析器和插件读取)匹配。ApolloServer有一个泛型参数,但该参数是传递给你的arguments函数的参数类型,而不是你应用程序的context值类型。

在Apollo Server 4中,你将你的context值类型指定为ApolloServer的泛型参数。这在整个过程中提供了正确的context类型,确保你的context函数返回的类型与你的解析器和插件中可用的类型匹配。例如:

// You can optionally create a TS interface to set up types
// for your context
interface MyContext {
token: String
}
// Create a new ApolloServer instance, passing in your
// context's types to ApolloServer's integration function.
const server = new ApolloServer<MyContext>({
typeDefs,
resolvers: {
Query: {
hello: (root, args, { token }) {
return token; // token is properly inferred as a string
},
},
},
plugins: [{
async requestDidStart({ contextValue }) {
// token is properly inferred as a string; note that in Apollo Server 4 you
// write `contextValue` rather than `context` in plugins.
console.log(contextValue.token);
},
}],
});
const { url } = await startStandaloneServer(apolloServerInstance, {
context: async ({req, res}) => ({
// You now get proper type inference within your context function!
token: await getTokenForRequest(req),
}),
listen: { port: 4000 }
});

改进的validationRules类型

在Apollo Server 3中,validationRules选项声明为返回any的函数列表。然而,在运行时,这些函数实际上需要返回一个graphql-jsASTVisitor。在Apollo Server 4中,typescript类型确保validationRules是一个包含graphql-jsValidationRule的列表。如果你的validationRules有typescript错误,你需要修复一个或多个规则以正确返回ASTVisitor

@apollo/utils.fetcher替换apollo-server-env

在Apollo Server 3中,apollo-server-env包主要提供fetchURLAPI的typescript类型定义和polyfills。

Apollo Server 4引入了@apollo/utils.fetcher,它定义了一个最小的fetch API(Fetcher),提供Fetch API的typescript类型定义。

@apollo/cache-control-types

someField(parent, args, context, { cacheControl }) {
cacheControl.setCacheHint({ maxAge: 100 });
}

import { cacheControlFromInfo } from '@apollo/cache-control-types';
// ...
someField(parent, args, context, info) {
cacheControlFromInfo(info).setCacheHint({ maxAge: 100 });
}

重命名的类型

Apollo Server 4将构造函数选项类型从Config更改为ApolloServerOptions。在Apollo Server 3中,一些集成包导出了此类型的自定义版本(例如,ApolloServerExpressConfig)。在Apollo Server 4中,存在唯一的一个具有单一构造函数的ApolloServer类型,因此这些额外的类型不再必要。

现在,apollo-server-express中的两个类型从@apollo/server/express4导出了更多明确的名称。GetMiddlewareOptions现在是ExpressMiddlewareOptionsExpressContext现在是ExpressContextFunctionArgument

已删除的类型

本节列出了Apollo Server 4删除的TypeScript类型(即接口,不是类)(不包括本文中其他地方提到的类型)。

在Apollo Server 3中,GraphQLOptions类型内部用于创建集成,并出于技术原因进行导出;现在在Apollo Server 4中已删除。

Apollo Server 4删除了applyMiddleware函数及其相关ServerRegistration类型。

在Apollo Server 3中,CorsOptionsOptionsJson类型从corsbody-parser包重新导出。Apollo Server 4不再为您处理这些任务,因此这些类型不再导出。

在Apollo Server 4中,从apollo-serverserver.listen()返回的ServerInfo类型已删除。startStandaloneServer函数现在返回一个没有类型名称的更简单的数据结构。

上一个
开始
下一个
以前版本
评价文章评价在GitHub上编辑编辑论坛Discord

©2024Apollo Graph Inc.,亦称Apollo GraphQL。

隐私政策

公司