迁移到 Apollo Server 4
📣 Apollo Server 4 已 公开发布!
Apollo Server 4提供以下功能:
- 新的
- 可作为ECMAScript或CJS模块使用的包。
新的@apollo/server
包
Apache Server 3 以一组 固定包形式发布,用于与不同的Web框架和环境集成。主要的"一切皆包含" apollo-server
包通过提供一个最小可定制的 GraphQL服务器 来减少设置时间。
Apollo Server 3中,apollo-server-core
包定义了一个 ApolloServer
"基础"类,各个集成包(apollo-server-express
,apollo-server-lambda
,等等)通过略有不同的API进行子类化。这种封装结构意味着新的集成包版本将与Apollo Server本身体现在统一版本,这使得支持框架的主要版本和添加特定的集成更改具有挑战性。此外,Apollo Server 3不提供为额外的框架添加新集成的方法。
Apollo Server 4通过提供一个稳定的Web框架集成API,采取了不同的集成方法,其中包括明确支持 无服务器 框架的生命周期。
新的 @apollo/server
包包括:
- 类
ApolloServer
。 - Express 4集成(类似于Apollo Server 3的
apollo-server-express
包)。 - 独立服务器(类似于Apollo Server 3的
apollo-server
包)。 - 一套 核心插件(类似于Apollo Server 3的
apollo-server-core
包)
Apollo Server 4中没有任何特定的集成子类。而是有一个单一的 ApolloServer
类和一个所有集成都使用的单一API。
Apollo Server 3中,Apollo Server核心团队负责维护每个集成包。随着Apollo Server 4的推出,AS核心团队不再直接维护大多数集成包。相反,我们与更广泛的开源社区共同维护Apollo Server集成,使那些经常使用不同Web框架的人能够为他们的框架集成做出最佳选择。
如果您正从Apollo Server 3迁移到Apollo Server 4,请使用以下流程图查看您的迁移路径:
- 如果您目前正在使用
apollo-server
包,应使用startStandaloneServer
函数。 - 如果您目前正在使用
apollo-server-express
包,您应使用expressMiddleware
函数。
@apollo/server
包导出这些函数,并与 ApolloServer
类一起导出。
如果您使用的是其他 Apollo Server 3 框架集成包(如 apollo-server-koa
或 apollo-server-lambda
),请查看我们的 集成列表,以查看是否存在针对您选择的框架的社区维护的集成包。
如果您最喜欢的框架还没有 Apollo Server 集成 方案,请通过 构建新的集成 来帮助更广泛的社区!您还可以 加入讨论以维护我们现有的集成。
以下是使用框架集成的一些高层次更改
- 您可以直接将您的
context
初始化函数传递给您的框架集成函数(例如,expressMiddleware
或startStandaloneServer
),而不是传递给ApolloServer
构造函数。 - 您需要负责使用您框架集成函数的标准功能来 设置 HTTP 请求体解析和 CORS。
- 如果要您的服务器监听特定的 URL 路径,请将此路径直接传递给您的框架 路由器,而不是使用
path
选项。如果没有指定 URL 路径,Apollo Server 3 的默认值是/graphql
,因此为了保留现有行为,您应明确指定该路径。
以下部分显示了如何使用 apollo-server
或 apollo-server-express
的服务器更新到 Apollo Server 4。
从 apollo-server
迁移
在 Apollo Server 3 中,Apollo Server 包是一个“包含电池”的包,它封装了 apollo-server-express
,提供了具有最小 HTTP 级别定制的 HTTP 服务器。
如果你在 Apollo Server 3 中使用了“包含电池”的 apollo-server
包,请使用 Apollo Server 4 中的 startStandaloneServer
函数。
以下是 Apollo Server 3 代码:
// npm install apollo-server graphqlimport { 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}`);
// npm install apollo-server graphqlimport { 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 中看起来像这样:
// npm install @apollo/server graphqlimport { 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}`);
// npm install @apollo/server graphqlimport { 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
实例,该实例开始监听传入的请求。第二个是配置服务器选项的对象,它最显著地接受以下属性:
名称 类型 | 描述 |
---|---|
| 一个可选的 在Apollo Server 3中,您将 |
| 一个可选的 例如,在Apollo Server 3中,如果您使用 |
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.applyMiddleware
或server.getMiddleware
)。
要从Apollo Server 3的Apollo Server 3的apollo-server-express
包迁移到使用expressMiddleware
函数,请按照以下步骤操作:
- 安装
@apollo/server
和cors
包。 - 从
@apollo/server
导入符号(即,而不是从apollo-server-express
和apollo-server-core
导入)。 - 将
cors
添加到您的服务器设置中。 - 移除Apollo Server 3的
apollo-server-express
和apollo-server-core
包。 - 如果您正在使用
apollo-server-express
'的默认/graphql
URL路径(即,未指定另一个包含路径选项的URL),则可以将expressMiddleware
挂载到/graphql
以保持行为。要使用另一个URL路径,使用app.use
在指定的路径上挂载您的服务器。
以下是 Apollo Server 3 代码:
// npm install apollo-server-express apollo-server-core express graphqlimport { 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}`);
// npm install apollo-server-express apollo-server-core express graphqlimport { 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 中看起来像这样:
// npm install @apollo/server express graphql corsimport { 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`);
// npm install @apollo/server express graphql corsimport { 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-server-fastify
apollo-server-hapi
apollo-server-koa
apollo-server-lambda
apollo-server-micro
apollo-server-cloud-functions
apollo-server-cloudflare
apollo-server-azure-functions
相反,我们现在与更广泛的社区合作,维护以下 Apollo 服务器的开源集成:Apollo Server:
如果您的框架没有集成,您总是可以构建自己的!
在 Apollo Server 3 中,apollo-server-express
包支持 Express 以及其较旧的先行者 Connect。在 Apollo Server 4 中,expressMiddleware
已不再支持 Connect。感兴趣的开发者可以构建特定于 Connect 的中间件,如果有人这样做,欢迎提交 PR 至此迁移指南!
已合并到 @apollo/server
的软件包
如上所示,Apollo Server 4 将 apollo-server
、apollo-server-express
和 apollo-server-core
包的功能合并到一个新的 @apollo/server
包中。
但是:还有更多!@apollo/server
包还结合了以下包:
插件位于深层次导入中
在 Apollo Server 3 中,apollo-server-core
包中导出了内建的插件,如 ApolloServerUsageReporting
,在顶层。要使用这些插件,您必须安装 两个包:一个为 apollo-server-core
包,另一个为您用于导入 ApolloServer
的包(例如 apollo-server
或 apollo-server-express
)。
在 Apollo Server 4 中,这些内建的插件是主包 @apollo/server
的一部分,该包还导入了 ApolloServer
类。该 @apollo/server
包通过深层次导出导出这些内置插件。这意味着您需要为每个内置插件使用深层次导入,这样您就可以仅评估您在应用程序中使用的插件,并使打包器更容易删除未使用的代码。
有一个例外:ApolloServerPluginLandingPageGraphQLPlayground
插件现在有自己的包 @apollo/server-plugin-landing-page-graphql-playground
,您可以单独安装。
此插件安装了 GitHub 上的未维护项目 unmaintained GraphQL 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 中复制这种行为,您可以将 cache
和 context
参数传递给每个 DataSource
构造函数。在 Apollo Server 4 中,您可以在 ApolloServer
上找到一个新的只读字段 cache
。
例如,下面我们使用 RESTDataSource
类以创建一个 DataSource
,使用 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();
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 编写相同代码的方法。
import { RESTDataSource, AugmentedRequest } from '@apollo/datasource-rest';// KeyValueCache is the type of Apollo server's default cacheimport 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` throughthis.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}`);
import { RESTDataSource } from '@apollo/datasource-rest';// KeyValueCache is the type of Apollo server's default cacheimport { 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` throughthis.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
包中。这两个包之间的功能大部分是相同的。然而,在如何传递请求的 headers
、 params
、 cacheOptions
和 body
方面存在一些小的语法差异。有关更详细的信息,请参阅 从 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` throughthis.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` throughthis.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
实例提供模式。最常见的方法之一是提供 typeDefs
和 resolvers
选项(每个选项都可以是一个数组)。另一种方法是用包含 typeDefs
和 resolvers
键的对象数组来使用 modules
选项。在内部,这两种选项使用完全不同的逻辑来完成相同的事情。
为了简化其 API,Apollo Server 4 移除了 modules
构造函数选项。您可以替换任何之前的 modules
使用以下语法:
new ApolloServer({typeDefs: modules.map({ typeDefs } => typeDefs),resolvers: modules.map({ resolvers } => resolvers),})
此外,对应的 GraphQLSchemaModule
TypeScript 类型也不再导出。
mocks
和 mockEntireSchema
在 Apollo Server 3 中,mocks
和 mockEntireSchema
构造函数选项允许 Apollo Server 根据您的服务器模式返回模拟数据,用于 GraphQL 操作。在内部,Apollo Server 3 的模拟功能通过一个过时的 @graphql-tools/mocks
库提供。
Apollo Server 4 去除了 mocks
和 mockEntireSchema
构造函数选项。您可以将 @graphql-tools/mock
包直接集成到您的应用中,以获取最新的模拟功能。有关配置模拟的更多信息,请参阅 @graphql-tools/mocks
文档。
以下示例比较了 Apollo Server 3 中左侧的 mocks
和 mockEntireSchema
构造函数选项,以及右侧使用 @graphql-tools/mock
的替代方案。您还可以在不影响行为的情况下逐步将这些更改应用到 Apollo Server 3 中。
new ApolloServer({mocks: true,});
import { addMocksToSchema } from '@graphql-tools/mock';import { makeExecutableSchema } from '@graphql-tools/schema';new ApolloServer({schema: addMocksToSchema({schema: makeExecutableSchema({ typeDefs, resolvers }),}),});
const mocks = {Int: () => 6,};new ApolloServer({mocks,});
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,}),});
const mocks = {Int: () => 6,};new ApolloServer({mocks,mockEntireSchema: false,});
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
环境变量设置为 production
或 test
),它控制 Apollo Server 的多个不相关的方面:
- 如果
debug
为true
,则包含带有堆栈跟踪的 GraphQL 错误响应。 - 如果
debug
为true
并且 ApolloServer 使用默认的logger
,则 Apollo Server 将打印所有DEBUG
级别消息。- Apollo Server 3 很少发送
DEBUG
级别的消息,因此这主要影响使用提供的logger
发送DEBUG
消息的插件。
- Apollo Server 3 很少发送
- 该
debug
标志也可供GraphQLRequestContext
使用,它们可以按需使用。
Apollo Server 4 去除了 构造函数选项 debug
。取而代之的是一个新的 includeStacktraceInErrorResponses
选项,该选项控制其同名的功能。和 debug
选项一样,除非 NODE_ENV
环境变量是 production
或 test
,否则该选项默认为 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 response
和 requestContext
(包含一个未设置的 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 操作。
每个__typename
GraphQL服务器都支持一个简单的查询,用于请求顶级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
选项使用/graphql
URL路径。
在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 移除了 ApolloError
和 toApolloError
,改为使用 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 在特定情况下使用这些错误类中的一个(例如,SyntaxError、ValidationError 和 UserInputError),而其他类(如 ForbiddenError 和 AuthenticationError)则用于用户的 apps 中。所有这些错误类都是主要 ApolloError
类的子类。
在 Apollo Server 4 中,ApolloError 已不存在,因此 Apollo Server 不再导出特定的错误类。相反,您可以使用 graphql
的 GraphQLError
类来创建自己的错误代码。此外,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 中,您将在 ApolloServerPluginUsageReporting
的 sendErrors
选项上指定相同的函数,作为 ApolloServerPluginInlineTrace
的 includeErrors
选项。
(此外,默认行为现在已更改为隐藏错误。
因此,您之前可以这样写:
// monolithsnew ApolloServer({plugins: [ApolloServerPluginUsageReporting({ rewriteError })],// ...})// subgraphsnew ApolloServer({plugins: [ApolloServerPluginInlineTrace({ rewriteError })],// ...})
现在您可以这样写:
// monolithsnew ApolloServer({plugins: [ApolloServerPluginUsageReporting({sendErrors: { transform: rewriteError },})],// ...})// subgraphsnew 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 在 variables
和extensions 以对象以外的任何类型(例如数组、布尔值或 null)提供在 POST
主体中时,将以 400 状态码响应。类似地,如果 operationName
在 POST
主体中以字符串以外的任何类型提供,它将以 400 状态码响应。)
如果您想恢复以前的行为,您可以在框架解析请求正文的元素之后解析 variables
和 extensions
字段。在 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-failureres.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
初始化函数,将初始 context
context
提供给您的解析器。
// Apollo Server 3 Constructorconst server = new ApolloServer({typeDefs,resolvers,csrfPrevention: true,context: ({ req }) => ({authScope: getScope(req.headers.authorization)})});
在 Apollo Server 4 中,context
函数是一个命名参数,传递给您的网络集成函数(例如,expressMiddleware
或 startStandaloneServer
)。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
函数的 req
和 res
对象是 express.Request
和 express.Response
类型。在 startStandaloneServer
函数中,req
和 res
对象是 http.IncomingMessage
和 http.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` insteadif (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.code
或 error.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
, invalidRequestWasReceived
和 unexpectedErrorProcessingRequest
,使插件可以观察新的设置中的错误。
在 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
现在是一个字符串(PUBLIC
或 PRIVATE
)的联合,而不是枚举:
export type CacheScope = 'PUBLIC' | 'PRIVATE';
您不能再键入 CacheScope.Public
或 CacheScope.Private
。取而代之的是,只需使用字符串 'PUBLIC'
或 'PRIVATE'
。定义为 CacheScope
的值将仅接受这两个值,所以任何键入错误仍会在编译时捕捉到。
现在,您可以从新的 @apollo/cache-control-types
包(而不是从 Apollo Server 包中导入)导入 CacheScope
。这使得与多个 GraphQL 服务器(如 @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 中同时使用了两个 parseOptions
和 typeDefs
构造函数选项:
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 introspection 查询生成的 JSON 编码的不稳定哈希。 schemaHash
字段不保证在模式更改时改变(例如,它不受模式 directive 应用更改的影响)。如果您需要一个模式哈希,您可以使用 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.request
和server.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 中,data
、errors
和 extensions
字段存在在顶级位置,靠近 http
。
因为 Apollo Server 4 支持增量交付指令,例如 @defer
和 @stream
(当与相应版本的 graphql-js
结合使用时),所以响应的结构现在可以表示单个结果或多个结果,因此这些字段不再存在于 GraphQLResponse
的顶级位置。
相反,在 GraphQLResponse
的顶级位置有一个 body
字段。 response.body.kind
可以是 'single'
或 'incremental'
。如果是 'single'
,则未使用增量交付,response.body.singleResult
是一个包含 data
、errors
和 extensions
字段的对象。如果是 'incremental'
,则
此外,data
和 extensions
字段的数据类型都是
现在,http.headers
的值现在是一个
plugins
构造函数参数不接受工厂函数。
在 Apollo Server 3 中,提供给 new ApolloServer
的 plugins
数组中的每个元素可以是 ApolloServerPlugin
(例如,具有 fields 如 requestDidStart
的对象)或一个不带参数的 ApolloServerPlugin
返回的 “factory” 函数。
在 Apollo Server 4 中,每个元素必须是一个 ApolloServerPlugin
。如果您在设置插件时使用了一个工厂函数来引用 ApolloServer
对象本身,那么您可能希望使用新的 ApolloServer.addPlugin
方法,您可以在调用 start
或 startStandaloneServer
之前调用此方法。
例如,如果您的 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
对象上设置。
自定义 gateway
和 GraphQLDataSource
实现
将 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
和一个 executor
。Apollo 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
来启用此功能。
并非所有的GraphQL客户端都支持HTTP批处理,批处理请求不支持增量交付。HTTP批处理可以通过共享操作之间的context
对象来提高性能,但它可能会使理解任何特定请求所做的工作变得更困难。
默认缓存是有界的
每个Apollo Server都使用后端缓存,用于多个功能,包括APQs、响应缓存插件和RESTDataSource
。Apollo Server默认使用内存缓存,但您可以使用cache
构造函数选项配置它使用不同的后端(如Redis或Memcached)。
在Apollo Server 3中,默认缓存是一个无界的内存缓存。这个缓存容易受到内存耗尽攻击的拒绝服务攻击,我们不推荐用户使用默认缓存。
在Apollo Server 4中,默认缓存是一个有界的内存缓存后端(这对于生产使用是安全的)。这在Apollo Server 3.9或更高版本中相当于传递cache: 'bounded'
。
如果您想自定义 Apollo Server 使用的缓存,Apollo 提供了两个包装包来帮助您完成这个过程:
@apollo/utils.keyvadapter
- 提供了一个用于与KeyvAdapter
包装的类来使用keyv
包。@apollo/utils.keyvaluecache
- 同时提供了KeyValueCache
TypeScript 接口和一个InMemoryLRUCache
类(这是一个围绕lru-cache
包的包装器)。
关于使用 KeyvAdapter
和 InMemoryLRUCache
的示例,请参见 配置外部缓存。
如果您想让您服务器使用无界限的内存缓存(这可能会使您的服务器易受内存耗尽攻击),您可以使用不带任何参数的默认 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
替换(ApolloServerPluginUsageReporting
或 ApolloServerPluginInlineTrace
)。
要恢复 Apollo Server 3 的行为,您可以将 { unmodified: true }
传递到每个插件上的选项中:
// monolithsnew ApolloServer({plugins: [ApolloServerPluginUsageReporting({sendErrors: { unmodified: true },})],// ...})// subgraphsnew ApolloServer({plugins: [ApolloServerPluginInlineTrace({includeErrors: { unmodified: true },})],// ...})
(如上所述,所述,rewriteError
选项已被 sendErrors
或 includeErrors
上的 transform
选项所取代。)
在子图中,使用情况报告插件默认是关闭的
在一个 Apollo Federation 超级图,您的Apollo网关或 GraphOS路由器 会将 使用报告 发送到Apollo的服务器;单个 子图服务器 内发生的信息将通过 内联追踪 发送到网关或 路由器。也就是说:使用报告插件不是为在联合子图中使用而设计的。
在 Apollo Server 3中,如果您提供了一个Apollo API密钥以及 图引用 并且没有明确安装 ApolloServerPluginUsageReporting
或 ApolloServerPluginUsageReportingDisabled
插件,即使在子图中,也会安装默认配置的 ApolloServerPluginUsageReporting
插件。
在 Apollo Server 4中,在联合子图中不会发生这种自动安装。尽管不推荐这样做,但仍可以明确地在您的子图中安装 ApolloServerPluginUsageReporting
,但会记录一条警告。
重命名的包
以下包在Apollo Server 4中已重命名:
apollo-datasource-rest
现在为@apollo/datasource-rest
。apollo-server-plugin-response-cache
现在为@apollo/server-plugin-response-cache
。apollo-server-plugin-operation-registry
现在为@apollo/server-plugin-operation-registry
。apollo-reporting-protobuf
(用于使用报告插件的内部实现细节)现在为@apollo/usage-reporting-protobuf
。
请注意,一旦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 contextinterface 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-js
的ASTVisitor
。在Apollo Server 4中,typescript类型确保validationRules
是一个包含graphql-js
的ValidationRule
的列表。如果你的validationRules
有typescript错误,你需要修复一个或多个规则以正确返回ASTVisitor
。
@apollo/utils.fetcher
替换apollo-server-env
在Apollo Server 3中,apollo-server-env
包主要提供fetch
和URL
API的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
现在是ExpressMiddlewareOptions
,ExpressContext
现在是ExpressContextFunctionArgument
。
已删除的类型
本节列出了Apollo Server 4删除的TypeScript类型(即接口,不是类)(不包括本文中其他地方提到的类型)。
在Apollo Server 3中,GraphQLOptions
类型内部用于创建集成,并出于技术原因进行导出;现在在Apollo Server 4中已删除。
Apollo Server 4删除了applyMiddleware
函数及其相关ServerRegistration
类型。
在Apollo Server 3中,CorsOptions
和OptionsJson
类型从cors
和body-parser
包重新导出。Apollo Server
4不再为您处理这些任务,因此这些类型不再导出。
在Apollo Server 4中,从apollo-server
的server.listen()
返回的ServerInfo
类型已删除。startStandaloneServer
函数现在返回一个没有类型名称的更简单的数据结构。