Migrating to Apollo Server 3
Apollo Server 3 is generally available.
The focus of this major-version release is to provide a lighter, nimbler core library as a foundation for future features and improved extensibility.
Many Apollo Server 2 users don't need to make any code changes to upgrade to Apollo Server 3! This is especially likely if you use the "batteries-included" apollo-server
library (as opposed to a middleware-specific library).
This document explains which features do require code changes and how to make them. If you encounter issues while migrating, please create an issue.
For a list of all breaking changes, see the changelog.
Bumped dependencies
Node.js
Apollo Server 3 supports Node.js 12 and later. (Apollo Server 2 supports back to Node.js 6.) This includes all LTS and Current versions at the time of release.
If you're using an older version of Node.js, upgrade your runtime before upgrading to Apollo Server 3.
graphql
Apollo Server has a peer dependency on graphql
(the core JS GraphQL implementation), which means you are responsible for choosing the version installed in your app.
Apollo Server 3 supports graphql
v15.3.0 and later. (Apollo Server 2 supported graphql
v0.12 through v15.)
If you're using an older version of graphql
, upgrade it to a supported version before upgrading to Apollo Server 3.
Removed integrations
Apollo Server 2 provides built-in support for subscriptions and file uploads via the subscriptions-transport-ws
and graphql-upload
packages, respectively. It also serves GraphQL Playground from its base URL by default.
Apollo Server 3 removes these built-in integrations, in favor of enabling users to provide their own mechanisms for these features.
You can reenable all of these integrations as they exist in Apollo Server 2.
Subscriptions
Apollo Server 2 provides limited, built-in support for WebSocket-based GraphQL subscriptions via the subscriptions-transport-ws
package. This integration doesn't work with Apollo Server's plugin system or Apollo Studio usage reporting.
Apollo Server 3 no longer contains this built-in integration. However, you can still use subscriptions-transport-ws
for subscriptions if you depend on this implementation. Note that as with Apollo Server 2, this integration won't work with the plugin system or Studio usage reporting.
Additionally, Apollo Server 3 no longer re-exports all of the exports from the graphql-subscriptions
package such as PubSub
.
We hope to add more fully-integrated subscription support to Apollo Server in a future version.
Note that the subscriptions-transport-ws
library is no longer maintained. A newer, actively-maintained graphql-ws
package exists. These libraries implement different protocols for GraphQL subscriptions over WebSocket, so you need to adjust your client to support graphql-ws
. This migration guide shows how to reenable subscriptions-transport-ws
to make your transition from Apollo Server 2 to Apollo Server 3 as easy as possible, but we recommend that you then migrate from subscriptions-transport-ws
to graphql-ws
once you've finished the upgrade. The subscriptions documentation shows how to use the newer graphql-ws
rather than the unmaintained subscriptions-transport-ws
.
Reenabling subscriptions
Currently, these instructions are only for Apollo Server's Express integration. It is likely possible to integrate subscriptions-transport-ws
with other integrations. PRs to this migration guide are certainly welcome!
In Apollo Server 3, you can't use subscriptions-transport-ws
with the "batteries-included" apollo-server
package. If your project uses apollo-server
with subscriptions, first swap to apollo-server-express
.
Then, complete the following:
Install
subscriptions-transport-ws
and@graphql-tools/schema
.npm install subscriptions-transport-ws @graphql-tools/schemaAdd the following imports in the module where your Apollo Server is currently instantiated. We'll use these in the subsequent steps.
import { createServer } from 'http';import { execute, subscribe } from 'graphql';import { SubscriptionServer } from 'subscriptions-transport-ws';import { makeExecutableSchema } from '@graphql-tools/schema';Create an
http.Server
instance with your Expressapp
.In order to set up both the HTTP and WebSocket servers, we'll need to create an
http.Server
. Do this by passing your Expressapp
to thecreateServer
function which we imported from the Node.jshttp
module.// This `app` is the returned value from `express()`.const httpServer = createServer(app);Create an instance of
GraphQLSchema
(if one doesn't already exist).Your server may already pass a
schema
to theApolloServer
constructor. If it does, this step can be skipped. You'll use the existingschema
instance in a later step.The
SubscriptionServer
(which we'll instantiate next) doesn't accepttypeDefs
andresolvers
directly, but rather an executableGraphQLSchema
. We can pass thisschema
object to both theSubscriptionServer
andApolloServer
. This way, it's clear that the same schema is being used in both places.const schema = makeExecutableSchema({ typeDefs, resolvers });// ...const server = new ApolloServer({schema,});Create the
SubscriptionServer
.const subscriptionServer = SubscriptionServer.create({// This is the `schema` we just created.schema,// These are imported from `graphql`.execute,subscribe,// This `server` is the instance returned from `new ApolloServer`.// Ensures the same graphql validation rules are applied to both the Subscription Server and the ApolloServervalidationRules: server.requestOptions.validationRules// Providing `onConnect` is the `SubscriptionServer` equivalent to the// `context` function in `ApolloServer`. Please [see the docs](https://github.com/apollographql/subscriptions-transport-ws#constructoroptions-socketoptions--socketserver)// for more information on this hook.async onConnect(connectionParams: Object,webSocket: WebSocket,context: ConnectionContext) {// If an object is returned here, it will be passed as the `context`// argument to your subscription resolvers.}}, {// This is the `httpServer` we created in a previous step.server: httpServer,// This `server` is the instance returned from `new ApolloServer`.path: server.graphqlPath,});Add a plugin to your
ApolloServer
constructor to close theSubscriptionServer
.index.js-10const server = new ApolloServer({schema,plugins: [{async serverWillStart() {return {async drainServer() {subscriptionServer.close();}};}}],});Finally, adjust the existing
listen
.Most applications will be calling
app.listen(...)
on their Express app. This should be changed tohttpServer.listen(...)
using the same arguments. This will begin listening on the HTTP and WebSocket transports simultaneously.
A completed example of migrating subscriptions is shown below:
File uploads
Apollo Server 2 provides built-in support for file uploads via an outdated version of the graphql-upload
library. Using an updated version of graphql-upload
requires you to disable this built-in support due to backward incompatible changes.
This built-in support is removed in Apollo Server 3. To use graphql-upload
, you can choose an appropriate version and integrate it yourself. Note that graphql-upload
does not support federation or every Node.js framework supported by Apollo Server.
To use graphql-upload
with Apollo Server 3, see the documentation on enabling file uploads in Apollo Server. Note that if your project currently uses uploads with the "batteries-included" apollo-server
package, you must first swap to apollo-server-express
.
Warning: using graphql-upload
in your server without proper mitigation increases your server's vulnerability to Cross-Site Request Forgery (CSRF) attacks. This affected the default configuration of Apollo Server 2 and will affect you if you manually integrate with graphql-upload
. We do not typically recommend the use of graphql-upload
and think that file uploads generally work best out of band from your GraphQL API; however, if you do want to use graphql-upload
then you must take actions to protect your users from CSRF attacks. The easiest way to do this is to ensure you are using at least Apollo Server 3.7 and enable our CSRF prevention feature by passing csrfPrevention: true
to new ApolloServer()
. Note that you will have to configure your upload client (like apollo-upload-client
) to pass a non-empty Apollo-Require-Preflight
header to your server once this feature is enabled.
Additionally, Apollo Server 3 no longer re-exports the GraphQLUpload
symbol from the graphql-upload
package.
GraphQL Playground
By default, Apollo Server 2 serves the (now-retired) GraphQL Playground IDE from its base URL in non-production environments. In production, it serves no landing page. Apollo Server 2 also accepts a playground
constructor option to override this default behavior (for example, to enable GraphQL Playground even in production).
Apollo Server 3 removes GraphQL Playground (and its associated constructor option) in favor of a landing page that links to Apollo Sandbox in non-production environments. In production, it serves a simplified landing page instead.
You can customize your Apollo Server 3 landing page, as described in Build and run queries.
Reenabling GraphQL Playground
You can continue to use GraphQL Playground by installing its associated plugin. You customize its behavior by passing options to the plugin instead of via the playground
constructor option.
If your code did not specify the playground
constructor option and you'd like to keep the previous behavior instead of trying the new splash page, you can do that as follows:
import { ApolloServerPluginLandingPageGraphQLPlayground,ApolloServerPluginLandingPageDisabled } from 'apollo-server-core';new ApolloServer({plugins: [process.env.NODE_ENV === 'production'? ApolloServerPluginLandingPageDisabled(): ApolloServerPluginLandingPageGraphQLPlayground(),],});
If your code passed new ApolloServer({playground: true})
, you can keep the previous behavior with:
import { ApolloServerPluginLandingPageGraphQLPlayground } from 'apollo-server-core';new ApolloServer({plugins: [ApolloServerPluginLandingPageGraphQLPlayground(),],});
If your code passed new ApolloServer({playground: false})
you can keep the previous behavior with:
import { ApolloServerPluginLandingPageDisabled } from 'apollo-server-core';new ApolloServer({plugins: [ApolloServerPluginLandingPageDisabled(),],});
If your code passed an options object like new ApolloServer({playground: playgroundOptions})
, you can keep the previous behavior with:
import { ApolloServerPluginLandingPageGraphQLPlayground } from 'apollo-server-core';new ApolloServer({plugins: [ApolloServerPluginLandingPageGraphQLPlayground(playgroundOptions),],});
Additional changes to GraphQL Playground
Specifying an endpoint
In Apollo Server 2, the default value of GraphQL Playground's endpoint
option is determined in different ways by different Node.js framework integrations. In many cases, it's necessary to manually specify playground: {endpoint}
.
In Apollo Server 3, the default endpoint used by GraphQL Playground is the browser's current URL. In many cases, this means that you don't have to specify endpoint
anymore. If your Apollo Server 2 app specified playground: {endpoint}
(and you wish to continue using GraphQL Playground), try removing endpoint
from the options passed to ApolloServerPluginLandingPageGraphQLPlayground
and see if it works for you.
Specifying settings
In Apollo Server 2, the behavior of the settings
GraphQL Playground option can be surprising. If you don't explicitly pass {playground: {settings: {...}}}
then GraphQL Playground always uses settings that are built into its React application (some of which can be adjusted by the user in their browser). However, if you pass any object as playground: {settings: {...}}
, several default value overrides take effect.
This surprising behavior is removed in Apollo Server 3. All settings
use default values from the GraphQL Playground React app if they aren't specified in the settings
option to ApolloServerPluginLandingPageGraphQLPlayground
.
If your app does pass in playground: {settings: {...}}
and you want to make sure the settings used in your GraphQL Playground do not change, you should copy any relevant settings from the Apollo Server 2 code into your app.
For example, you could replace:
new ApolloServer({playground: {settings: {'some.setting': true}}})
with:
import { ApolloServerPluginLandingPageGraphQLPlayground } from 'apollo-server-core';new ApolloServer({plugins: [ApolloServerPluginLandingPageGraphQLPlayground({settings: {'some.setting': true,'general.betaUpdates': false,'editor.theme': 'dark','editor.cursorShape': 'line','editor.reuseHeaders': true,'tracing.hideTracingResponse': true,'queryPlan.hideQueryPlanResponse': true,'editor.fontSize': 14,'editor.fontFamily': `'Source Code Pro', 'Consolas', 'Inconsolata', 'Droid Sans Mono', 'Monaco', monospace`,'request.credentials': 'omit',},}),],});
Removed constructor options
The following ApolloServer
constructor options have been removed in favor of other features or configuration methods.
extensions
Apollo Server 3 removes support for the graphql-extensions
API, which was used to extend Apollo Server's functionality. This API has numerous limitations relative to the plugins API introduced in Apollo Server v2.2.0.
Unlike graphql-extensions
, the plugins API enables cross-request state, and its hooks virtually all interact with the same GraphQLRequestContext
object.
If you've written your own extensions (passed to new ApolloServer({extensions: ...})
), you should rewrite them as plugins before upgrading to Apollo Server 3.
engine
"Engine" is a previous name of Apollo Studio. Prior to Apollo Server v2.18.0, you passed the engine
constructor option to configure how Apollo Server communicates with Studio. Additionally, you could specifying your Apollo API key with the ENGINE_API_KEY
environment variable, and you could specify a graph variant with the ENGINE_SCHEMA_TAG
environment variable.
In later versions of Apollo Server (including Apollo Server 3), you instead provide this configuration via a combination of the apollo
constructor option, plugins, and APOLLO_
-prefixed environment variables. Apollo Server 3 does not support the engine
constructor option or the ENGINE_
-prefixed environment variables.
If your project still uses the engine
option or the ENGINE_
-prefixed environment variables, see Migrating from the engine
option before upgrading to Apollo Server 3.
Note that if you are using Studio, make sure to set your graph ref or graph ID.
schemaDirectives
In Apollo Server 2, you can pass schemaDirectives
to new ApolloServer
alongside typeDefs
and resolvers
. These arguments are all passed through to the makeExecutableSchema
function from the graphql-tools
package.
The graphql-tools
project deprecated the schemaDirectives
feature and removed it in v8 of @graphql-tools/schema
.
In Apollo Server 3, the ApolloServer
constructor now only passes typeDefs
, resolvers
, and parseOptions
through to makeExecutableSchema
.
If you would like to still use schemaDirectives
, you can install an older version of @graphql-tools/schema
yourself with npm install @graphql-tools/schema@7
and call makeExecutableSchema
yourself and pass its returned schema as the schema
constructor option.
That is, you can replace:
new ApolloServer({typeDefs,resolvers,schemaDirectives,});
with:
// Make sure you are using v7, not anything newer!import { makeExecutableSchema } from '@graphql-tools/schema';new ApolloServer({schema: makeExecutableSchema({typeDefs,resolvers,schemaDirectives,}),});
This can help you with the initial migration to Apollo Server 3. As schemaDirectives
is no longer part of the actively maintained version of @graphql-tools/schema
, we recommend that once you've successfully migrated to Apollo Server 3, you then port your schema directive to the newer schema directives API which uses the mapSchema
function in @graphql-tools/utils
.
In Apollo Server 2, there are subtle differences between providing a schema with schema
versus providing it with typeDefs
and resolvers
. For example, the automatic definition of the @cacheControl
directive is added only in the latter case. These differences are removed in Apollo Server 3 (for example, the definition of the @cacheControl
directive is never automatically added).
tracing
In Apollo Server 2, the tracing
constructor option enables a trace mechanism implemented in the apollo-tracing
package. This package uses a comparatively inefficient JSON format for execution traces returned via the tracing
GraphQL response extension. The format is consumed only by the deprecated engineproxy
and GraphQL Playground. It is not the tracing format used for Apollo Studio usage reporting or federated inline traces.
The tracing
constructor option is removed in Apollo Server 3. The apollo-tracing
package has been deprecated and is no longer being published.
If you rely on this deprecated trace format, you might be able to use the old version of apollo-server-tracing
directly:
new ApolloServer({plugins: [require('apollo-tracing').plugin()]});
This workaround has not been tested! If you need this to work and it doesn't, please file an issue and we will investigate a fix to enable support in Apollo Server 3.
cacheControl
In Apollo Server 2, cache policy support is configured via the cacheControl
constructor option. There are several improvements to the semantics of cache policies in Apollo Server 3, as well as changes to how caching is configured.
The cacheControl
constructor option is removed in Apollo Server 3. To customize cache control, you instead manually install the cache control plugin and provide custom options to it.
For example, if you currently provide defaultMaxAge
and/or calculateHttpHeaders
to cacheControl
like so:
new ApolloServer({cacheControl: {defaultMaxAge,calculateHttpHeaders,},});
You now provide them like so:
import { ApolloServerPluginCacheControl } from 'apollo-server-core';new ApolloServer({plugins: [ApolloServerPluginCacheControl({defaultMaxAge,calculateHttpHeaders,}),],})
If you currently pass cacheControl: false
like so:
new ApolloServer({cacheControl: false,});
You now install the disabling plugin like so:
import { ApolloServerPluginCacheControlDisabled } from 'apollo-server-core';new ApolloServer({plugins: [ApolloServerPluginCacheControlDisabled(),],})
In Apollo Server 2, cacheControl: true
was a shorthand for setting cacheControl: {stripFormattedExtensions: false, calculateHttpHeaders: false}
. If you either passed cacheControl: true
or explicitly passed stripFormattedExtensions: false
, Apollo Server 2 would include a cacheControl
response extension inside your GraphQL response. This was used by the deprecated engineproxy
server. Support for writing this response extension has been removed from Apollo Server 2. This allows for a more memory-efficient cache control plugin implementation.
In Apollo Server 2, definitions of the @cacheControl
directive (and the CacheControlScope
enum that it uses) were sometimes automatically inserted into your schema. (Specifically, they were added if you defined your schema with the typeDefs
and resolvers
options, but not if you used the modules
or schema
options or if you were a federated gateway. Passing cacheControl: false
did not stop the definitions from being inserted!) In Apollo Server 3, these definitions are never automatically inserted.
So if you use the @cacheControl
directive in your schema, you should add these definitions to your schema:
enum CacheControlScope {PUBLICPRIVATE}directive @cacheControl(maxAge: Intscope: CacheControlScopeinheritMaxAge: Boolean) on FIELD_DEFINITION | OBJECT | INTERFACE | UNION
(You may add them to your schema in Apollo Server 2 before upgrading if you'd like.)
In Apollo Server 2, plugins that want to change the operation's overall cache policy can overwrite the field requestContext.overallCachePolicy
. In Apollo Server 3, that field is considered read-only, but it does have new methods to mutate its state. So you should replace:
requestContext.overallCachePolicy = { maxAge: 100 };
with:
requestContext.overallCachePolicy.replace({ maxAge: 100 });
(You may also want to consider using restrict
instead of replace
; this method only allows maxAge
to be reduced and only allows scope
to change from PUBLIC
to PRIVATE
.)
In Apollo Server 2, fields returning a union type are treated similarly to fields returning a scalar type: @cacheControl
on the type itself is ignored, and maxAge
if unspecified is inherited from its parent in the operation (unless it is a root field) instead of defaulting to defaultMaxAge
(which itself defaults to 0). In Apollo Server 3, fields returning a union type are treated similarly to fields returning an interface or object type: @cacheControl
on the type itself is honored, and maxAge
if unspecified defaults to defaultMaxAge
. If you were relying on the inheritance behavior, you can specify @cacheControl(maxAge: ...)
explicitly on your union types or union-returning fields, or you can use the new @cacheControl(inheritMaxAge: true)
feature on the union-returning field to restore the Apollo Server 2 behavior. If your schema contained union SomeUnion @cacheControl(...)
, that directive will start having an effect when you upgrade to Apollo Server 3.
In Apollo Server 2, the @cacheControl
is honored on type definitions but not on type extensions. That is, if you write type SomeType @cacheControl(maxAge: 123)
it takes effect but if you write extend type SomeType @cacheControl(maxAge: 123)
it does not take effect. In Apollo Server 3, @cacheControl
is honored on object, interface, and union extensions. If your schema accidentally contained @cacheControl
on an extend
, that directive will start having an effect when you upgrade to Apollo Server 3.
In Apollo Server 2, most of the logic related to cache control lives in the apollo-cache-control
package. This package exports a function called plugin
as well as TypeScript types CacheControlFormat
, CacheHint
, CacheScope
, and CacheControlExtensionOptions
. In Apollo Server 3, this logic lives in apollo-server-core
and the apollo-cache-control
package is no longer published. apollo-server-core
exports ApolloServerPluginCacheControl
and ApolloServerPluginCacheControlOptions
(which are similar to plugin
and CacheControlExtensionOptions
). apollo-server-types
exports CacheHint
and CacheScope
. There is no equivalent export to CacheControlFormat
because as described above, the code to write the cacheControl
response extension is not part of Apollo Server 3.
playground
See GraphQL Playground.
Removed exports
In Apollo Server 2, apollo-server
and framework integration packages such as apollo-server-express
import many symbols from third-party packages and re-export them. This effectively ties the API of Apollo Server to a specific version of those third-party packages and makes it challenging to upgrade to a newer version or for you to upgrade those packages yourself.
In Apollo Server 3, most of these "re-exports" are removed. If you want to use these exports, you should import them directly from their originating package.
Exports from graphql-tools
Apollo Server 2 exports every symbol exported by graphql-tools
v4. If you're importing any of the following symbols from an Apollo Server package, you should instead run npm install graphql-tools@4.x
and import the symbol from graphql-tools
.
Alternatively, read the GraphQL Tools docs and find out which @graphql-tools/subpackage
the symbol is exported from in more modern versions of GraphQL Tools.
AddArgumentsAsVariables
AddTypenameToAbstract
CheckResultAndHandleErrors
DirectiveResolverFn
ExpandAbstractTypes
ExtractField
FilterRootFields
FilterToSchema
FilterTypes
GraphQLParseOptions
IAddResolveFunctionsToSchemaOptions
IConnectorCls
IConnectorFn
IConnector
IConnectors
IDelegateToSchemaOptions
IDirectiveResolvers
IEnumResolver
IExecutableSchemaDefinition
IFieldIteratorFn
IFieldResolver
IGraphQLToolsResolveInfo
ILogger
IMockFn
IMockOptions
IMockServer
IMockTypeFn
IMocks
IResolverObject
IResolverOptions
IResolverValidationOptions
IResolversParameter
IResolvers
ITypeDefinitions
ITypedef
MergeInfo
MergeTypeCandidate
MockList
NextResolverFn
Operation
RenameRootFields
RenameTypes
ReplaceFieldWithFragment
Request
ResolveType
Result
SchemaDirectiveVisitor
SchemaError
TransformRootFields
Transform
TypeWithResolvers
UnitOrList
VisitTypeResult
VisitType
WrapQuery
addCatchUndefinedToSchema
addErrorLoggingToSchema
addMockFunctionsToSchema
addResolveFunctionsToSchema
addSchemaLevelResolveFunction
assertResolveFunctionsPresent
attachConnectorsToContext
attachDirectiveResolvers
buildSchemaFromTypeDefinitions
chainResolvers
checkForResolveTypeResolver
concatenateTypeDefs
decorateWithLogger
defaultCreateRemoteResolver
defaultMergedResolver
delegateToSchema
extendResolversFromInterfaces
extractExtensionDefinitions
forEachField
introspectSchema
makeExecutableSchema
makeRemoteExecutableSchema
mergeSchemas
mockServer
transformSchema
Exports from graphql-subscriptions
Apollo Server 2 exports every symbol exported by graphql-subscriptions
. If you are importing any of the following symbols from an Apollo Server package, you should instead run npm install graphql-subscriptions
and import the symbol from graphql-subscriptions
instead.
FilterFn
PubSub
PubSubEngine
PubSubOptions
ResolverFn
withFilter
Exports from graphql-upload
Apollo Server 2 exports the GraphQLUpload
symbol from (our fork of) graphql-upload
. Apollo Server 3 no longer has built-in graphql-upload
integration. See the documentation on how to enable file uploads in Apollo Server 3.
Exports related to GraphQL Playground
Apollo Server 2 exports a defaultPlaygroundOptions
object, along with PlaygroundConfig
and PlaygroundRenderPageOptions
types to support the playground
top-level constructor argument.
In Apollo Server 3, GraphQL Playground is one of several landing pages implemented via plugins, and there are no default options for it. The ApolloServerPluginLandingPageGraphQLPlaygroundOptions
type exported from apollo-server-core
plays a similar role to PlaygroundConfig
and PlaygroundRenderPageOptions
. See the section on playground
above for more details on configuring GraphQL Playground in Apollo Server 3.
Removed features
Several small features have been removed from Apollo Server 3.
Guessing Apollo Studio graph ID from API key
In Apollo Server 2, if you specify an Apollo API key (e.g., with the APOLLO_KEY
environment variable) that starts with service:your-graph-id:
, Apollo Server automatically guesses that your Studio graph ID (used for usage reporting, schema reporting, managed federation, and so on) is your-graph-id
.
In Apollo Server 3, you should instead specify your Studio graph ID explicitly when using features that connect to Studio. You can specify the graph ID by itself in the APOLLO_GRAPH_ID
environment variable (or via new ApolloServer({apollo: {graphId}}))
, or alongside the variant in a string like your-graph-id@your-graph-variant
in the APOLLO_GRAPH_REF
environment variable (or via new ApolloServer({apollo: {graphRef}})
). See the apollo
constructor option for more details.
If you set your API key but do not set your graph ref or ID:
- If you explicitly set up features like the usage reporting plugin or managed federation, Apollo Server 3 throws an error on startup.
- If you don't explicitly set up or disable the usage reporting plugin, Apollo Server 3 logs a warning suggesting that you either set your graph ref or disable the usage reporting plugin.
ApolloServer.schema
field
Apollo Server 2 has a deprecated field ApolloServer.schema
(which doesn't work when the server is a federated gateway). Apollo Server 3 does not contain this field. To access your server's schema, you have a few options:
- Construct the schema yourself (e.g., with
const schema = makeExecutableSchema({typeDefs, resolvers})
from@graphql-tools/schema
), pass it in asnew ApolloServer({schema})
, and refer to this sameschema
value elsewhere. Apollo Server 3 won't modify the schema you pass in. - Write a plugin that uses the
serverWillStart
event to obtain the schema. Note that if your server is a gateway, this receives the first schema that's loaded on startup but doesn't receive any subsequent schemas if it updates dynamically. - If your server is a gateway, register a callback with
gateway.onSchemaChange
. Note that this API has some inconsistent behavior. To resolve this, we are considering adding an Apollo Server plugin event that receives all schema updates.
apollo-server-testing
Apollo Server 2 contains a package apollo-server-testing
. This package is a thin wrapper around the server.executeOperation
method. As of Apollo Server 2.25.0, we no longer document this package and instead document using executeOperation
directly.
In Apollo Server 3, we no longer publish this package. It's possible that the Apollo Server 2 version of this package might work with Apollo Server 3 servers. If you do use apollo-server-testing
, we suggest that you migrate to using executeOperation
directly (with at least 2.25.0 of Apollo Server) before upgrading to Apollo Server 3.
If you used apollo-server-testing
like so:
const { createTestClient } = require('apollo-server-testing');const { query, mutate } = createTestClient(server);await query({ query: QUERY });await mutate({ mutation: MUTATION });
then you can use executeOperation
like so:
await server.executeOperation({ query: QUERY });await server.executeOperation({ query: MUTATION });
Note that prior to Apollo Server 2.25.0, apollo-server-testing
functions allowed you to pass your operation as a string or a DocumentNode
as returned from gql
, but executeOperation
only supported strings. As of v2.25.0, executeOperation
takes string
or DocumentNode
, which makes the change shown above straightforward in all cases.
apollo-datasource-rest
: baseURL
override change
When you create a RESTDataSource
subclass, you need to provide its baseURL
. This can be done via this.baseURL = ...
in the constructor or resolveURL
, or via baseURL = ...
at the class level.
In Apollo Server 2 you can also provide baseURL
via a getter like get baseURL() { ... }
. Apollo Server 3 is compiled with TypeScript 4, which no longer supports overriding a property with an accessor, so this is no longer allowed.
If you used a getter in order to provide a dynamic URL, like this:
class MyDataSource extends RESTDataSource {get baseURL() {return someDynamicallyCalculatedURL();}}
you can instead override resolveURL
:
class MyDataSource extends RESTDataSource {async resolveURL(request: RequestOptions) {if (!this.baseURL) {this.baseURL = someDynamicallyCalculatedURL();}return super.resolveURL(request);}}
apollo-server-env
's global type definitions
Global TypeScript definitions have been removed from apollo-server-env
since they conflicted with similar global types provided by @types/supertest
, which we use in Apollo Server's test suite. These removed types include types which resemble those of the Fetch API including, e.g., fetch
, RequestInfo
, Headers
, Request
, Response
, ResponseInit
, and more. See the full list prior to removal here.
We don't expect this to affect many users and we have not publicly suggested using these types in the past, but it's possible that implementations may be using them inadvertently (e.g., from auto-import on apollo-server-env
). While other type definition sets provide similar hand-curated types, for the time being, we have chosen to rely the same-named types from TypeScript's lib.dom.d.ts
— e.g., its RequestInfo
type definition. Even if those types are more appropriate for browsers, they're a reliable and well-maintained source. For more details, including our plan for adjusting this again in the future, see PR #5165.
Changed features
Plugin API
Almost all plugin events are now async
In Apollo Server 2, some plugin events are synchronous (their return value is not a Promise
), and some are "maybe-asynchronous" (they could return a Promise
if they wanted, but didn't have to). This means that you can't do asynchronous work in the former events, and the typings for the latter events are somewhat complex.
In Apollo Server 3, almost all plugin methods are always asynchronous: they always return a Promise
type. This includes end hooks as well. The exceptions are willResolveField
and its end hook and schemaDidLoadOrUpdate
, which are always synchronous.
In practice, this means that all of your plugin events should use async
functions or methods. If you are using TypeScript, you need to do this for your code to compile.
In practice, Apollo Server generally uses await
or Promise.all
on values returned by plugin methods instead of an explicit .then
. These constructs accept both Promise
s and normal values. If you aren't using TypeScript, you might be able to get away with synchronous plugin methods for the time being.
willSendResponse
is called more consistently
In Apollo Server 2, some errors related to persisted queries invoke the requestDidStart
and didEncounterError
plugin events without invoking the willSendResponse
event afterwards.
In Apollo Server 3, any request that makes it far enough to invoke requestDidStart
also invokes willSendResponse
. See this PR for details.
executionDidStart
can no longer return a function
In Apollo Server 2, the executionDidStart
plugin event could return nothing, an object, or a function. If a function was provided, it would be called when execution finished (one might imagine an executionDidEnd
).
In Apollo Server 3, executionDidStart
must return either nothing or an object. If you previously returned a function, you can now return an object with an executionDidEnd
field like so:
const server = new ApolloServer({schema,plugins: [{async requestDidStart() {return {async executionDidStart() {return {async executionDidEnd() {// your code here}}},};}}],});
For more information, you can check out the related plugin documentation on end hooks.
Gateway interface renamed and simplified
In Apollo Server 2, the TypeScript type used for the gateway
constructor option is called GraphQLService
. In Apollo Server 3, it's called GatewayInterface
. (For now, an identical interface named GraphQLService
continues to be exported.)
This interface now requires the following:
- The
stop
method must be present. - The
executor
method must async - The
apollo
option must always be passed to theload
method.
All recent versions of @apollo/gateway
satisfy these stronger requirements.
Bad request errors more consistently return 4xx
In Apollo Server 2, certain poorly formatted requests receive HTTP responses with a 5xx status (indicating a server error) instead of 4xx (indicating a client error). For example, this occurs in some integrations for a missing POST body or a JSON parse error.
In Apollo Server 3, these errors are handled in a more consistent manner across integrations and consistently return HTTP responses with a 4xx status.
Extensions (custom details) on ApolloError
Initializing an error
In Apollo Server 2, error extensions can be passed to the ApolloError
constructor either as the third argument, or as an extensions
option on the third argument.
In other words, these two lines are equivalent with Apollo Server 2:
new ApolloError(message, code, {key: 'value'})new ApolloError(message, code, {extensions: {key: 'value'}})
In Apollo Server 3, only the first line above is supported. If you try to use the second line, the constructor throws an error. Before upgrading, replace any code using the second form with the first form.
Reading error extensions
In Apollo Server 2, an error extension (foo
) is present in two locations on an ApolloError
object (error
): error.foo
and error.extensions.foo
. When that object is serialized for a JSON response, the extension is also present in two locations in the JSON: extensions.foo
and extensions.exception.foo
.
In Apollo Server 3, an error extension is present in one ApolloError
location: error.extensions.foo
. When the error is serialized to JSON, the extension is present only in extensions.foo
.
See this PR for more details.
Mocking
In Apollo Server 2, the mocks
and mockEntireSchema
constructor options are essentially implemented as follows:
import { addMockFunctionsToSchema } from 'graphql-tools'; // v4.xconst { mocks, mockEntireSchema } = constructorOptions;const schemaWithMocks = addMockFunctionsToSchema({schema,mocks:typeof mocks === 'boolean' || typeof mocks === 'undefined'? {} : mocks,preserveResolvers:typeof mockEntireSchema === 'undefined' ? false : !mockEntireSchema,});
In Apollo Server 3, we've upgraded from graphql-tools
v4 to @graphql-tools/mock
v8. The details of how mocking works in GraphQL Tools has changed, and the name of the function that implements it has changed too. The new implementation is as follows:
import { addMocksToSchema } from '@graphql-tools/mock'; // v8.xconst { mocks, mockEntireSchema } = constructorOptions;const schemaWithMocks = addMocksToSchema({schema,mocks: mocks === true || typeof mocks === 'undefined' ? {} : mocks,preserveResolvers:typeof mockEntireSchema === 'undefined' ? false : !mockEntireSchema,});
So the name of the function has changed, and additionally, the semantics of the function have changed. You can read the GraphQL Tools migration docs to see if you use any mocking features that have changed between v4 and v8, and adjust the value of your mocks
argument if so.
To use features of addMocksToSchema
that require passing more options to it than the three that ApolloServer
passes through, you can use the library directly. To do so, run npm install @graphql-tools/mock @graphql-tools/schema
in your app, and replace
new ApolloServer({ typeDefs, resolvers, mocks });
with
import { addMocksToSchema } from '@graphql-tools/mock';import { makeExecutableSchema } from '@graphql-tools/schema';new ApolloServer({schema: addMocksToSchema({schema: makeExecutableSchema({ typeDefs, resolvers }),mocks: // ...// ...}),});
Alternatively, to keep the current behavior without changing your mock functions at all, you can continue to use graphql-tools
v4. To do this, run npm install graphql-tools@v4.x
in your app, and replace
new ApolloServer({typeDefs, resolvers, mocks, preserveResolvers});
with
import { makeExecutableSchema, addMockFunctionsToSchema } from 'graphql-tools';new ApolloServer({schema: addMockFunctionsToSchema({schema: makeExecutableSchema({typeDefs, resolvers}),mocks,preserveResolvers:typeof mockEntireSchema === 'undefined' ? false : !mockEntireSchema,}),});
apollo-server-caching
test suite helpers
In Apollo Server 2, the apollo-server-caching
package exports functions like testKeyValueCache
, testKeyValueCache_Basic
, and testKeyValueCache_Expiration
, which define Jest test suites. The package also exports a TestableKeyValueCache
type that's required for these test suites to be able to flush and close the cache.
You can use this to define Jest test suites for your own implementation of the KeyValueCache
interface, although this requires intricate Jest and TypeScript config to set up.
In Apollo Server 3, these functions and type are removed. Instead, a runKeyValueCacheTests
function is exported which can be run in any test suite (without any Jest-specific behavior). See the apollo-server-caching
README for more details.
Changes to framework integrations
start()
now mandatory for non-serverless framework integrations
Apollo Server v2.22 introduced the server.start()
method for non-serverless framework integrations (Express, Fastify, Hapi, Koa, Micro, and Cloudflare). Users of these integrations were encouraged to await a call to it immediately after creating an ApolloServer
object:
async function startServer() {const server = new ApolloServer({...});await server.start();server.applyMiddleware({ app });}
If any plugin serverWillStart
events throw, or if the server fails to load its schema properly (for example, if the server is a gateway and cannot load its schema), then await server.start()
will throw. This allows you to ensure that Apollo Server has successfully loaded its configuration before you start listening for HTTP requests.
In Apollo Server 2, calling this method was optional. If you didn't call it, you could still call methods like applyMiddleware
and start listening. If a startup failure occurred, all GraphQL requests would fail.
In Apollo Server 3, you must await server.start()
immediately after new ApolloServer
before calling applyMiddleware
(or getMiddleware
or getHandler
, depending on the integration). Note that you don't have to call it before calling the testing method executeOperation
. Otherwise, that method will throw.
This does not apply to the batteries-included apollo-server
package (the server is started as part of the async method server.listen()
) or to serverless framework integrations.
Peer deps instead of direct deps
In Apollo Server 2, the apollo-server-express
, apollo-server-koa
, and apollo-server-micro
packages have direct dependencies on express
, koa
, and micro
respectively, and apollo-server-fastify
and apollo-server-hapi
have no dependency on fastify
, hapi
, or @hapi/hapi
.
In Apollo Server 3, these packages have peer dependencies on their corresponding framework packages. This means that you need to install a version of that package of your choice yourself in your app (though most likely that was already the case).
CORS *
is now the default
In Apollo Server 2, the default CORS configuration for most packages is to serve an access-control-allow-origin: *
header for all responses.
However, this is not the case in Apollo Server 2 for apollo-server-lambda
, apollo-server-cloud-functions
, or apollo-server-azure-functions
(these serve no CORS headers by default), or for apollo-server-koa
(this serves an access-control-allow-origin
header with a value matching the request's origin by default).
In Apollo Server 3, all implementations of ApolloServer
that support CORS configuration have the same default of access-control-allow-origin: *
.
To use your framework's Apollo Server 2 default CORS behavior in your Apollo Server 3 application, provide the options specified below to the createHandler
/getMiddleware
method.
Lambda / Cloud Functions
server.createHandler({expressGetMiddlewareOptions: {cors: false}})
Azure Functions
server.createHandler({cors: false})
Koa
server.getMiddleware({cors: {}})
apollo-server-micro
and apollo-server-cloudflare
do not support CORS configuration in Apollo Server 2 or 3.
apollo-server-express
In Apollo Server 2, apollo-server-express
officially supports both the express
framework and the older connect
framework.
In Apollo Server 3, we no longer guarantee future support for connect
. We do still run a test suite against it and we will try not to unintentionally break functionality under connect
, but if future changes are easier to implement in an Express-only fashion, we reserve the right to break connect
compatibility within Apollo Server 3.
apollo-server-lambda
Now based on Express
In Apollo Server 2, apollo-server-lambda
implements parsing AWS Lambda events and performing standard web framework logic inside the package itself. As Lambda added new capabilities, the logic had to adapt, and as Apollo Server added new features, they had to be implemented from scratch in apollo-server-lambda
. Additionally, there was no way to add additional HTTP functionality (e.g., "middleware") to apollo-server-lambda
.
In Apollo Server 3, apollo-server-lambda
is implemented as a wrapper around apollo-server-express
, using an actively maintained package to parse AWS Lambda events into Express requests. This simplifies the implementation and enables us to support new Lambda integrations such as Application Load Balancers. Additionally, because it now uses Express internally, you can extend HTTP functionality via Express middleware using the new expressAppFromMiddleware
option to createHandler
.
createHandler
arguments changed
As part of the update described above, the arguments to createHandler
have changed. Instead of taking only cors
and onHealthCheck
, the method takes an expressGetMiddlewareOptions
option, which is an object that supports any of the options that the apollo-server-express
applyMiddleware
and getMiddleware
can take (other than app
).
For example, instead of:
server.createHandler({onHealthCheck})
you now write
server.createHandler({expressGetMiddlewareOptions: {onHealthCheck}})`
Handler is always async
In Apollo Server 2, the handler returned by createHandler
can be called either as a function that takes a callback as a third argument, or (starting with Apollo Server v2.21.2) as an async function that returns a Promise
.
In Apollo Server 3, the handler returned by createHandler
is always an async function that returns a Promise
. Any callback passed in will be ignored.
All currently supported Lambda runtimes support async handlers. However, if you are currently wrapping the handler returned from createHandler
in your own larger handler and passing a callback into it, this no longer works. Rewrite your outer handler to be an async function and await
the Promise
returned from the handler returned by createHandler
.
For example, if your server looked like this:
const apolloHandler = server.createHandler();exports.handler = function (event, context, callback) {doSomethingFirst();apolloHandler(event, context, (error, result) => {doSomethingLater();callback(error, result);});}
you can change it to look like this:
const apolloHandler = server.createHandler();exports.handler = async function (event, context) {doSomethingFirst();try {return await apolloHandler(event, context);} finally {doSomethingLater();}}
apollo-server-cloud-functions
In Apollo Server 2, apollo-server-cloud-functions
implements standard web framework logic inside the package itself. Even though the Node.js API for Google Cloud Functions provides its request and response in the form of Express request and response objects, Apollo Server 2 has a bespoke implementation unrelated to apollo-server-express
and does not support all features supported by apollo-server-express
. Additionally, there is no way to add additional HTTP functionality (e.g., "middleware") to apollo-server-cloud-functions
.
In Apollo Server 3, apollo-server-cloud-functions
is implemented on top of apollo-server-express
and supports all features supported by apollo-server-express
. You can add additional HTTP functionality via Express middleware using the new expressAppFromMiddleware
option to createHandler
.
As part of this, the arguments to createHandler
have changed. Instead of taking only cors
, it takes an expressGetMiddlewareOptions
option, which is an object taking any of the options that the apollo-server-express
applyMiddleware
and getMiddleware
can take (other than app
). For example, instead of:
server.createHandler({cors})
you now write:
server.createHandler({expressGetMiddlewareOptions: {cors}})
apollo-server-fastify
In Apollo Server 2, apollo-server-fastify
supports Fastify v2 and does not support Fastify v3. There is no dependency or peer dependency making this requirement clear.
In Apollo Server 3, apollo-server-fastify
supports Fastify v3. It has not been tested with versions of Fastify older than v3. There is a peer dependency on fastify
v3.
apollo-server-hapi
We are not certain exactly which versions of Hapi are supported by apollo-server-hapi
in Apollo Server 2. We are certain that only @hapi/hapi
v20.1.2 and higher are supported by Node 16, and Apollo Server 2 was not tested with versions of hapi
newer than v17.8.5. (Hapi's package name changed from hapi
to @hapi/hapi
between v18.1.0 and v18.2.0.)
In Apollo Server 3, apollo-server-hapi
works with @hapi/hapi
v20.1.2 and Node 16. It is not tested with older versions of Hapi. Note that the Hapi project believes that all versions older than v20 have security issues.