Docs
Try Apollo Studio

Migrating to Apollo Server 4 (Alpha)


⚠️ Apollo Server 4 is currently in public alpha. It is not yet feature-complete, and breaking changes might occur between this release and general availability. Learn about release stages.

This alpha primarily aims to enable community members to develop integrations between Apollo Server and their favorite web frameworks. Many Apollo Server 3 users can't upgrade to this alpha yet. For example, this alpha removes multiple web framework integrations and doesn't support using RESTDataSource or hosting an Apollo Gateway.

We are working on updating our documentation to reflect the changes introduced in Apollo Server 4. This article explains which features do require code changes and how to make them.

This major release focuses on improving Apollo Server's extensibility and making it simpler to use, maintain, and document. To learn more about the inspiration behind this release, see the Apollo Server Roadmap.

The Apollo Server 4 alpha provides the following features, with more to come in the full release:

During Apollo Server 4's alpha, we are actively looking to gather feedback and issues from community members and customers.

For a list of all breaking changes, see the changelog.

The new @apollo/server package

Apollo Server 3 is distributed as a fixed set of packages for integrating with different web frameworks and environments. The main "batteries-included" apollo-server package reduces setup time by providing a minimally customizable GraphQL server.

In Apollo Server 3, the apollo-server-core package defines an ApolloServer "base" class, which each integration package (apollo-server-express,apollo-server-lambda, etc.) subclasses with a slightly different API. This packaging structure means that new integration package releases are lockstep versioned to Apollo Server itself, making it challenging to support major versions of frameworks and add integration-specific changes. Additionally, Apollo Server 3 doesn't provide a way to add new integrations for additional frameworks.

Apollo Server 4 takes a different approach to integrations by providing a stable web framework integration API, which includes explicit support for serverless framework life cycles.

The new @apollo/server package contains:

  • The ApolloServer class
  • An Express 4 integration (similar to Apollo Server 3's apollo-server-express package)
  • A standalone server (similar to Apollo Server 3's apollo-server package)
  • A set of core plugins (similar to Apollo Server 3's apollo-server-core package)

There are no integration-specific subclasses in Apollo Server 4. Instead, there's a single ApolloServer class with a single API that all integrations use.

In Apollo Server 3, the Apollo Server core team was responsible for maintaining every integration package. With Apollo Server 4, the AS core team will stop directly maintaining most integration packages. Instead, we will work with the broader open source community to maintain Apollo Server integrations, enabling those who regularly use different web frameworks to make the best choices for their framework's integration.

For those migrating from Apollo Server 3 to Apollo Server 4, use the below flowchart to see your migration path:

No
Yes
No
Yes
Am I using the apollo-server package?
Am I using the apollo-server-express package?
Use the startStandaloneServer function
You can’t upgrade during the alpha yet*
Use the expressMiddleware function
  • If you're currently using the apollo-server package, you should use the startStandaloneServer function.
  • If you're currently using the apollo-server-express package, you should use the expressMiddleware function.

The @apollo/server package exports these functions alongside the ApolloServer class.

If you are using any other Apollo Server 3 framework integration package, you can’t upgrade during the Apollo Server 4 alpha release yet. Please help us by building new integrations or discussing how to maintain existing integrations to ensure there is an Apollo Server 4 integration for your favorite framework.

Below are a few high-level changes for using framework integrations:

  • You can pass your context initialization function directly to your framework's integration function (e.g., expressMiddleware or startStandaloneServer) instead of the ApolloServer constructor.
  • You are responsible for setting up HTTP body parsing and CORS using your framework integration's standard functionality.
  • If you want your server to listen on a specific URL path, pass that path directly to your framework's router instead of using the path option. If you did not specify a URL path, the default in Apollo Server 3 was /graphql, so to preserve existing behavior, you should specify that path explicitly.

The following sections show how servers using apollo-server or apollo-server-express can update to Apollo Server 4.

Migrate from apollo-server

In Apollo Server 3, the apollo-server package is a "batteries-included" package that wraps apollo-server-express, providing an HTTP server with minimal HTTP-level customization.

If you used the "batteries included" apollo-server package in Apollo Server 3, use the startStandaloneServer function in Apollo Server 4.

This Apollo Server 3 code:

apollo-server-3-standalone.ts
// npm install apollo-server graphql
import { ApolloServer } from 'apollo-server';
import { typeDefs, resolvers } from './schema';
interface MyContext {
token?: String;
}
async function startApolloServer() {
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}`);
}

looks like this in Apollo Server 4:

apollo-server-4-standalone.ts
// npm install @apollo/server graphql
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { typeDefs, resolvers } from './schema';
interface MyContext {
token?: String;
}
async function startApolloServer() {
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}`);
}

The startStandaloneServer function accepts two arguments; the first is the instance of ApolloServer that should begin listening for incoming requests. The second is an object for configuring your server's options, which most notably accepts the following properties:

Name /
Type
Description
context

Function

An optional context initialization function. The context function receives req and res options (see below for more details.).

In Apollo Server 3, you pass the context function to the constructor. In Apollo Server 4, you pass the context function to startStandaloneServer.

listen

Object

An optional listen configuration option. The listen option accepts an object with the same properties as the net.Server.listen options object.

For example, in Apollo Server 3, if you used server.listen(4321), you'll now pass listen: { port: 4321 } to the startStandaloneServer function in Apollo Server 4. If you didn't pass any arguments to Apollo Server 3's server.listen() method; you don't need to specify this listen option.

The startStandaloneServer function doesn't enable you to configure your server's CORS behavior. If you previously used the cors constructor option to customize your CORS settings in Apollo Server 3, use the expressMiddleware function in Apollo Server 4.

Similarly, if you used the stopGracePeriodMillis constructor option in Apollo Server 3, use the expressMiddleware function and specify stopGracePeriodMillis to the ApolloServerPluginDrainHttpServer plugin.

Migrate from apollo-server-express

If you used the apollo-server-express package in Apollo Server 3, use the expressMiddleware function in Apollo Server 4 (i.e., instead of using server.applyMiddleware or server.getMiddleware).

To migrate from Apollo Server 3's apollo-server-express package to using the expressMiddleware function, do the following:

  1. Install the @apollo/server, cors, and body-parser packages.
  2. Import symbols from @apollo/server (i.e., instead of from apollo-server-express and apollo-server-core).
  3. Add cors and bodyParser.json() to your server setup.
  4. Remove the Apollo Server 3 apollo-server-express and apollo-server-core packages.
  5. If you are using apollo-server-express's default /graphql URL path (i.e., not specifying another URL with the path option), you can mount expressMiddleware at /graphql to maintain behavior. To use another URL path, mount your server (with app.use) at the specified path.

This Apollo Server 3 code:

apollo-server-3.ts
// npm install apollo-server-express apollo-server-core express graphql
import { ApolloServer } from 'apollo-server-express';
import { ApolloServerPluginDrainHttpServer } from 'apollo-server-core';
import express from 'express';
import http from 'http';
import { typeDefs, resolvers } from './schema';
interface MyContext {
token?: String;
}
async function startApolloServer() {
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 http://localhost:4000${server.graphqlPath}`);
}

looks like this in Apollo Server 4:

apollo-server-4.ts
// npm install @apollo/server express graphql cors body-parser
import { ApolloServer } from '@apollo/server';
import { expressMiddleware } from '@apollo/server/express4';
import { ApolloServerPluginDrainHttpServer } from '@apollo/server/plugin/drainHttpServer';
import express from 'express';
import http from 'http';
import cors from 'cors';
import { json } from 'body-parser';
import { typeDefs, resolvers } from './schema';
interface MyContext {
token?: String;
}
async function startApolloServer() {
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>(),
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 http://localhost:4000/graphql`);
}

Removed integrations

The Apollo Server core team no longer maintains the following integration packages in Apollo Server 4. We are looking for collaborators who actively use these frameworks to maintain Apollo Server 4 compatible integration packages.

Apollo Server 4 removes the below integration packages:

In Apollo Server 3, the apollo-server-express package supported both Express and its older predecessor Connect. In Apollo Server 4, expressMiddleware no longer supports Connect. An interested developer could build a Connect-specific middleware, and a PR to this migration guide is welcome if someone does this!

Packages merged into @apollo/server

As shown above, Apollo Server 4 combines the functionality of the apollo-server, apollo-server-express, and apollo-server-core packages into a new @apollo/server package.

But wait: there's more! The @apollo/server package also combines the following packages:

Plugins are in deep imports

In Apollo Server 3, the apollo-server-core package exports built-in plugins, like ApolloServerUsageReporting, at the top level. To use these plugins, you must install both the apollo-server-core package and the package you use to import ApolloServer (e.g., apollo-server or apollo-server-express).

In Apollo Server 4, these built-in plugins are part of the main @apollo/server package, which also imports the ApolloServer class. The @apollo/server package exports these built-in plugins with deep exports. This means you use deep imports for each built-in plugin, enabling you to evaluate only the plugin you use in your app and making it easier for bundlers to eliminate unused code.

The @apollo/server package exports the following plugins:

PluginImport path
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
ApolloServerPluginLandingPageGraphQLPlayground@apollo/server/plugin/landingPage/graphqlPlayground
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

For example, replace this Apollo Server 3 code:

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

with this Apollo Server 4 code:

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

You can also import each plugin's associated TypeScript types (e.g., ApolloServerPluginUsageReportingOptions) from the same deep import as that plugin.

Once you've updated your imports, you can remove your project's dependency on apollo-server-core.

Bumped dependencies

Node.js

Apollo Server 4 supports Node.js 14 and later. (Apollo Server 3 supports Node.js 12.) This includes all LTS and Current versions at the time of release.

If you're using Node.js 12, upgrade your runtime before upgrading to Apollo Server 4.

graphql

Apollo Server has a peer dependency on graphql (the core JS GraphQL implementation). Apollo Server 4 supports graphql v16.5.0 and later. (Apollo Server 3 supports graphql v15.3.0 through v16.)

If you're using an older version of graphql, upgrade it to a supported version before upgrading to Apollo Server 4.

TypeScript

If you use Apollo Server with TypeScript, you must use TypeScript v4.7.0 or newer.

For background, Apollo Server uses type system features introduced in v4.7. We want to put out "downleveled" versions of @apollo/server's type definitions for older versions of TypeScript, but have found TypeScript's typesVersions feature challenging to use.

If supporting older versions of TypeScript is important to you and you'd like to help us get typesVersions working, we'd appreciate PRs!

@apollo/gateway

⚠️ Note: The alpha version of Apollo Server 4 does not work as an Apollo Gateway. You can still use this alpha to serve subgraphs, just not Gateways. We will fix this before the general release of Apollo Server 4.

If you use Apollo Server with Apollo Gateway, Apollo Server 4 drops support for @apollo/gateway versions below v0.35.0.

Removed constructor options

The following ApolloServer constructor options have been removed in favor of other features or configuration methods.

dataSources

⚠️ This feature is in active development and does not currently work as described.

The Apollo Server 4 alpha doesn't support importing from the apollo-datasource-rest package. We intend to rename the apollo-datasource-rest package to @apollo/datasource-rest and update it to be fully compatible with Apollo Server 4.

In Apollo Server 3, the top-level dataSources constructor option essentially adds a post-processing step to your app's context function, creating DataSource subclasses and adding them to a dataSources field on your context object. This means the TypeScript type the context function returns is different from the context type your resolvers and plugins receive. Additionally, this design obfuscates that DataSource objects are created once per request (i.e., like the rest of the context object).

Apollo Server 4 removes the dataSources constructor option. You can now treat DataSources like any other part of your context object.

In Apollo Server 3, immediately after constructing each DataSource subclass, Apollo Server invokes the dataSource.initialize({ cache, context }) function on each new DataSource. If you need to replicate this behavior in Apollo Server 4, you can pass the cache and context arguments to each DataSource constructor. In Apollo Server 4, you can find cache as a new readonly field on ApolloServer.

For example, below, we use the RESTDataSource class to create a DataSource with Apollo Server 3:

Apollo Server 3
import { RESTDataSource } from 'apollo-datasource-rest';
import { ApolloServer } from 'apollo-server';
class MoviesAPI extends RESTDataSource {
baseURL = 'https://movies-api.example.com/';
willSendRequest(request: RequestOptions) {
request.headers.set('Authorization', this.context.token);
}
async getMovie(id: string): Movie {
return this.get(`movies/${encodeURIComponent(id)}`);
}
}
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();

Below is how you write the same code in Apollo Server 4. Note that the @apollo/datasource-rest package is now renamed @apollo/datasource-rest.

⚠️ This feature is in active development, and the below code snippets do not currently work as described. See above for more details.

Apollo Server 4
import { RESTDataSource, RESTDataSourceOptions } 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/';
private token: string;
constructor(options: { token: string } & RESTDataSourceOptions) {
super(options); // this should send `cache` through
this.token = options.token;
}
willSendRequest(request: RequestOptions) {
request.headers.set('Authorization', this.token);
}
async getMovie(id: string): Movie {
return this.get(`movies/${encodeURIComponent(id)}`);
}
}
interface ContextValue {
token: string;
dataSources: {
moviesAPI: MoviesAPI;
}
};
const server = new ApolloServer<ContextValue>({
typeDefs,
resolvers,
});
await startStandaloneServer(server, {
context: ({ req }) => {
const token = getTokenFromRequest(req);
const { cache } = server;
return {
token,
dataSources: {
moviesAPI: new MoviesAPI({ cache, token }),
},
};
},
});

If you want to access your entire context's value within your DataSource, you can do so by making your context value a class (enabling it to refer to itself via this in its constructor):

import { RESTDataSource, RESTDataSourceOptions } from '@apollo/datasource-rest';
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { IncomingMessage } from 'http';
class MoviesAPI extends RESTDataSource {
baseURL = 'https://movies-api.example.com/';
private contextValue: ContextValue;
constructor(options: { contextValue: ContextValue } & RESTDataSourceOptions) {
super(options); // this should send `cache` through
this.contextValue = options.contextValue;
}
willSendRequest(request: RequestOptions) {
request.headers.set('Authorization', this.contextValue.token);
}
async getMovie(id: string): Movie {
return this.get(`movies/${encodeURIComponent(id)}`);
}
}
class ContextValue {
public token: string;
public dataSources: {
moviesAPI: MoviesAPI;
}
constructor({ req, server }: { req: IncomingMessage, server: ApolloServer }) {
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: ({ req }) => new ContextValue({ req, server }),
});

modules

In Apollo Server 3, there are several ways to provide your ApolloServer instance with a schema. One of the most common ways is to provide typeDefs and resolvers options (each of which can optionally be an array). Another way is using the modules option with an array of objects, each object containing typeDefs and resolvers keys. Under the hood, these two options use entirely different logic to do the same thing.

To simplify its API, Apollo Server 4 removes the modules constructor option. You can replace any previous usage of modules with the following syntax:

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

Additionally, the corresponding GraphQLSchemaModule TypeScript type is no longer exported.

mocks and mockEntireSchema

In Apollo Server 3, the mocks and mockEntireSchema constructor options enable Apollo Server to return simulated data for GraphQL operations based on your server's schema. Under the hood, Apollo Server 3's mocking functionality is provided via an outdated version of the @graphql-tools/mocks library.

Apollo Server 4 removes both the mocks and mockEntireSchema constructor options. You can instead directly incorporate the @graphql-tools/mock package into your app, enabling you to get the most up-to-date mocking features. For more details on configuring mocks, see the @graphql-tools/mocks docs.

The following examples compare the mocks and mockEntireSchema constructor options in Apollo Server 3 on the left and a replacement using @graphql-tools/mock on the right. You can also incrementally apply these changes in Apollo Server 3 without affecting behavior.

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

debug

In Apollo Server 3, the debug constructor option (which defaults to true unless the NODE_ENV environment variable is either production or test) controls several unrelated aspects of Apollo Server:

  • If debug is true, GraphQL responses with errors include stack traces.
  • If debug is true and ApolloServer uses the default logger, Apollo Server prints all DEBUG log-level messages.
    • Apollo Server 3 rarely sends messages at the DEBUG level, so this primarily affects plugins that use the provided logger to send DEBUG messages.
  • The debug flag is also available to plugins on GraphQLRequestContext to use as they wish.

Apollo Server 4 removes the debug constructor option. In its place is a new includeStacktraceInErrorResponses option which controls its namesake feature. Like debug, this option defaults to true unless the NODE_ENV environment variable is either production or test.

If you use debug in Apollo Server 3, you can use includeStacktraceInErrorResponses with the same value in Apollo Server 4:

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

Additionally, if your app or a plugin uses DEBUG-level log messages and your server doesn't use a custom logger, you are responsible for setting the default log level. For example, you can use the same Logger implementation that Apollo Server uses by default:

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

(Note that the stack traces themselves have moved from extensions.exception.stacktrace to extensions.stacktrace.)

formatResponse hook

Apollo Server 3 provides the formatResponse hook as a top-level constructor argument. The formatResponse hook is called after an operation successfully gets to the "execution" stage, enabling you to transform the structure of GraphQL response objects before sending them to a client.

The formatResponse hook receives a successful operation's response and requestContext (containing an unset response field). If the formatResponse hook returns a non-null GraphQLResponse, it uses that response instead of the initially received response argument.

Apollo Server 4 removes the formatResponse hook. We instead recommend using the willSendResponse plugin hook, which enables you to do everything you previously did with formatResponse. The willSendResponse plugin hook receives an operation's requestContext, which has a response field containing a GraphQLResponse object. Note that the willSendResponse hook is allowed to mutate the requestContext.response field.

Apollo Server 4 changes the structure of GraphQLResponse, see below for more details.

Apollo Server calls the willSendResponse plugin hook for all requests that get far enough along to invoke requestDidStart (i.e., requests with a parsable JSON body, etc. ). This means that Apollo Server calls the willSendResponse hook in more contexts than the previous formatResponse hook.

To only use willSendResponse after an operation's "execution" stage (i.e., like the previous formatResponse hook), you can make a filter checking for the existence of a data field in the result. If an operation has a data field in the result, it has made it to the execution phase. Note, there are some edge cases: for example, an error in the coercion of variable values calls formatResponse but doesn't have data in the result. If differentiating these edge cases is important to you, please open an issue, and we'll help.

For example, if your Apollo Server 3 code used formatResponse like this:

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

your Apollo Server 4 code can use willSendResponse, like so:

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.
if ('data' in response.result) {
response.result.extensions = {
...(response.result.extensions),
hello: 'world',
};
}
},
};
},
},
],
});

executor

In Apollo Server 3, there are two different ways to specify a replacement for graphql-js's execution functionality. Both of them involve defining a function of the type GraphQLExecutor. One way is to specify that function directly as the executor constructor option. The other way involves using the gateway option.

In Apollo Server 4, this redundancy has been removed: there is no longer an executor constructor option. (Additionally, the TypeScript GraphQLExecutor type has been renamed GatewayExecutor and moved to the @apollo/server-gateway-interface package.)

If your Apollo Server 3 code defined an executor function and used it like this:

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

your Apollo Server code can use gateway, like so:

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

Removed features

Several small features have been removed from Apollo Server 4.

Health checks

In Apollo Server 3, the health check feature supports a simple HTTP-level health check that always returns a 200 status code.

Apollo Server 4 no longer supports built-in health checks. We found that running a trivial GraphQL query was a better way of checking the status of your server, because a query ensures your server successfully serves traffic and performs GraphQL operations.

Every GraphQL server supports a trivial query that requests the __typename of the top-level Query type. This means every GraphQL server can respond to a GET request to a URL, such as:

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

You should also send an apollo-require-preflight: true header alongside your health check, so it isn't blocked by the CSRF prevention feature.

If you want a health check for your HTTP server unrelated to the health of the GraphQL execution engine (i.e., like Apollo Server 3's health check feature), you can add a GET handler that always succeeds to your web framework.

Path parsing

In Apollo Server 3, many framework integrations enable you to use the path option to configure the URL path where Apollo Server processes requests. By default, the path option uses the /graphql URL path.

In Apollo Server 4, you should use your framework's routing feature to mount your integration at the URL path where you want Apollo Server to process requests. For example, if you are using apollo-server-express in Apollo Server 3 and would like to continue using the default /graphql path, you should now mount the expressMiddleware function at the /graphql path.

Apollo Server 3's batteries-included apollo-server package, replaced by startStandaloneServer in Apollo Server 4, serves all URLs (i.e., rather than only listening on /graphql).

body-parser and cors

In Apollo Server 3, framework integrations automatically set up HTTP body parsing and CORS response headers. You can customize your integration's CORS or body parsing functionality using the Apollo Server API; these configuration options vary by integration.

In Apollo Server 4, it's your responsibility to set up HTTP body parsing and CORS headers for your web framework. Specifically, when using expressMiddleware, you should install the body-parser and cors npm packages and use them in your Express app, just like with any other JSON-based API server. If you passed a cors option to applyMiddleware or getMiddleware, pass the same value to the cors function. If you passed a bodyParserConfig option to applyMiddleware or getMiddleware, pass the same value to the body-parser package's json function.

Note that startStandaloneServer sets up body parsing and CORS functionality for you, but you can't configure this behavior. In Apollo Server 3, you could configure the batteries-included apollo-server's CORS behavior via the cors constructor option. In Apollo Server 4, if you need to configure CORS behavior, use expressMiddleware rather than startStandaloneServer.

gql GraphQL tag

Apollo Server 3 depends on the graphql-tag npm package and re-exports its gql template literal tag. The gql tag is essentially a caching wrapper around graphql-js's parser, and most IDEs know to treat the contents of gql strings as GraphQL.

Apollo Server 4 does not depend on the graphql-tag library, nor does it export the gql tag. If you want to continue using the gql tag, you can directly install graphql-tag into your app, then update your import, replacing this line:

import { gql } from 'apollo-server';

with this line:

import gql from 'graphql-tag';

The apollo-server package exports gql as a named export, whereas the gql tag is the default export for graphql-tag.

ApolloError

Apollo Server 4 removes both ApolloError and toApolloError in favor of using GraphQLError.

The graphql package exports GraphQLError, and you can use it like so:

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

If you used the optional code argument with ApolloError:

throw new ApolloError(message, 'YOUR_ERROR_CODE');

you should now pass your error code to GraphQLError's extensions option; see the above code snippet for an example.

Built-in error classes

Apollo Server 3 exports several error classes. Apollo Server uses some of these error classes in specific situations (e.g., SyntaxError, ValidationError, and UserInputError), while other classes (ForbiddenError and AuthenticationError) are for users to use in their apps. All of these error classes are subclasses of the main ApolloError class.

In Apollo Server 4, ApolloError no longer exists, so Apollo Server doesn't export specific error classes. Instead, you can create your own error codes using graphql's GraphQLError class. Additionally, Apollo Server now provides an enum of error codes (ApolloServerErrorCode) that you can check against to see if a given error is one of the types recognized by Apollo Server.

In Apollo Server 3, you can throw a new ForbiddenError, like so:

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

In Apollo Server 4, you should define your own error using GraphQLError, like so:

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

For an AuthenticationError, use the code 'UNAUTHENTICATED'.

In Apollo Server 3, you can check the type of an error, like so:

if (error instanceof SyntaxError)

In Apollo Server 4, you can use the ApolloServerErrorCode enum to check if an error is one of the types recognized by Apollo Server, like so:

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

For ValidationError, use ApolloServerErrorCode.GRAPHQL_VALIDATION_FAILED. For UserInputError, use ApolloServerErrorCode.BAD_USER_INPUT.

__resolveObject

Apollo Server 4 removes the dependency on @apollographql/apollo-tooling, additionally removing the __resolveObject pseudo-resolver. The __resolveObject function was an undocumented predecessor to the __resolveReference method. While we believe __resolveObject is a useful feature, it would work better if implemented directly in graphql-js rather than in Apollo Server.

requestAgent option to ApolloServerPluginUsageReporting

Apollo Server's usage reporting plugin (i.e., ApolloServerPluginUsageReporting) lets you replace its HTTP client using the fetcher option. In Apollo Server 3, you can use an older requestAgent option, passed to the fetcher function via the non-standard agent option.

Apollo Server 4 removes the requestAgent option from ApolloServerPluginUsageReporting. Now, all of the options you pass to ApolloServerPluginUsageReporting's fetcher are part of the Fetch API spec.

If you are using requestAgent in Apollo Server 3, you can use the node-fetch npm package to override fetcher.

So, where you previously wrote:

ApolloServerPluginUsageReporting({ requestAgent })

You can now write:

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

Doubly-escaped variables and extensions in requests

Apollo Server 3 and 4 both accept POST requests with a JSON body.

Apollo Server 3 supports an edge case where the variables and extensions fields inside a POST request's JSON-encoded body can be JSON-encoded strings.

Apollo Server 4 requires that within a POST request's JSON-encoded body, the variables and extensions fields must be objects (not doubly-encoded).

For example, below is a valid query:

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

Whereas this query would be invalid:

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

Changed features

New approach to serverless frameworks

In Apollo Server 3, our approach to creating serverless frameworks involves subclassing ApolloServer and overriding the serverlessFramework() method.

In Apollo Server 4, serverless integrations differentiate themselves by using the startInBackgroundHandlingStartupErrorsByLoggingAndFailingAllRequests method. The length of this function's name discourages its use when building non-serverless apps.

Users of serverless integrations shouldn't call any start-related functions before passing in an ApolloServer instance:

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

In the above example, the lambdaHandler serverless integration function should call the server.startInBackgroundHandlingStartupErrorsByLoggingAndFailingAllRequests() method.

Apollo Server 4's other non-serverless framework integrations expect the developer to await the server.start() method, before validating that the server has started by calling server.assertStarted().

context initialization function

In Apollo Server 3, you could provide an initial context to your resolvers by adding a context initialization function to the ApolloServer constructor:

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

In Apollo Server 4, the context function is a named argument passed into your web integration function (e.g., expressMiddleware or startStandaloneServer). ApolloServer itself now has a generic type parameter specifying the type of your context value. The context function should return an object, which is then accessible to your server's resolvers and plugins (via the contextValue field).

Below is an example of providing a context initialization function to the startStandaloneServer function:

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 }
})

The context function's syntax is similar for the expressMiddleware function:

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),
}),
}),
);

In the expressMiddleware function, the req and res objects passed to the context function are express.Request and express.Response types. In the startStandaloneServer function, the req and res objects are http.IncomingMessage and http.ServerResponse types. If you need to use Express-specific properties in your context function, use expressMiddleware.

executeOperation accepts context value

The server.executeOperation method enables you to execute GraphQL operations by specifying an operation's text directly instead of doing so via an HTTP request. You can use executeOperation to test your server.

In Apollo Server 3, you can indirectly specify an operation's context value by passing a second optional argument to executeOperation; ApolloServer then passes this argument to its context function. For example, if you're using apollo-server-express, you can create an Express request and response then pass them to executeOperation as a { req, res } object.

In Apollo Server 4, the executeOperation method optionally receives a context value directly, bypassing your context function. If you want to test the behavior of your context function, we recommend running actual HTTP requests against your server.

So a test for Apollo Server 3 that looks like this:

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

looks like this in 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 { result } = await server.executeOperation({
query: 'query helloContext { hello }',
}, {
name: 'world',
});
expect(result.data?.hello).toBe('Hello world!'); // -> true

Error formatting changes

formatError improvements

Apollo Server 3 supports the formatError hook, which has the following signature:

(error: GraphQLError) => GraphQLFormattedError

This hook receives an error already altered by Apollo Server 3, and differs from the initially thrown error.

In Apollo Server 4, this becomes:

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

Above, formattedError is the default JSON object sent in the response according to the GraphQL specification, and error is the originally thrown error. If you need a field from the original error that isn't in GraphQLFormattedError, you can access that value from the error argument.

One caveat: if the error was thrown inside a resolver, error is not the error your resolver code threw; it is a GraphQLError wrapping it and adding helpful context such as the path in the operation to the resolver's field. If you want the exact error you threw, Apollo Server 4 provides the unwrapResolverError function in @apollo/server/errors, which removes this outer layer if you pass it a resolver error (and returns its argument otherwise).

So, you can format errors like this:

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

error.extensions.exception is removed

When Apollo Server 3 formats an error, it may add an extension called exception. This extension is an object with a field for every enumerable property of the originally thrown error. (This does not apply if the originally thrown error was already a GraphQLError.) In addition, when in dev/debug mode, exception contains an array of strings called stacktrace.

For example, if this code runs in a resolver:

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

then (in debug mode) Apollo Server 3 will format the error like this:

{
"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
]
}
}
}
]
}

It was often hard to predict exactly which properties of which errors would be publicly exposed in this manner, which could lead to surprising information leaks.

In Apollo Server 4, there is no exception extension. The stacktrace is provided directly on extensions. If you'd like to copy some or all properties from the original error onto the formatted error, you can do that with the formatError hook.

If you'd like your errors to be formatted like they are in Apollo Server 3 (with the stack trace and the enumerable properties of the original error on the exception extension), you can provide this formatError implementation:

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,
};
}

Apollo Server 3.5.0 and newer included a TypeScript declare module declaration that teaches TypeScript that error.extensions.exception.stacktrace is an array of strings on all GraphQLError objects. Apollo Server 4 does not provide a replacement for this: that is, we do not tell TypeScript the type of error.extensions.code or error.extensions.stacktrace. (The Apollo Server 3 declare module declaration also incorrectly teaches TypeScript that error.extensions.exception.code is a string, which should have been error.extensions.code instead.)

HTTP error handling changes

Apollo Server 3 returns specific errors relating to GraphQL operations over HTTP/JSON as text/plain error messages.

Apollo Server 4 now returns all non-landing-page-related responses as application/json JSON responses. This means all single-error responses render like any other GraphQL error:

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

Additionally, the formatError hook receives and can format all of these error instances.

Apollo Server 4 also introduces new plugin hooks startupDidFail, contextCreationDidFail, invalidRequestWasReceived, and unexpectedErrorProcessingRequest, enabling plugins to observe errors in new settings.

In Apollo Server 4, if either the resolveOperation or execute function throws an error, that error is rendered with the HTTP status code 500 (rather than 400). Note that the execute function commonly returns a non-empty list of errors rather than throwing an explicit error.

Warning for servers without draining

Versions of Apollo Server 3.2 and above add a "draining" phase to server shutdown, enabling a server to complete in-progress operations before continuing to shut down. Without this draining phase, operations can reach your server even after it has stopped, causing those operations to fail.

In Apollo Server 4, if your server hasn't set up draining and it receives an operation as the server is shutting down, the server logs a warning before failing that operation.

If you are using the startStandaloneServer function, your server drains automatically. If you are using expressMiddleware or another http.Server-based web server, you can add draining using the ApolloServerPluginDrainHttpServer plugin.

CacheScope type

In Apollo Server 4, CacheScope is now a union of strings (PUBLIC or PRIVATE) rather than an enum:

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

You can no longer type CacheScope.Public or CacheScope.Private. Instead, just use the string 'PUBLIC' or 'PRIVATE'. Values defined as CacheScope will only accept those two values, so any typos are still caught at compile time.

You can now import CacheScope from the new @apollo/cache-control-types package (instead of importing it from an Apollo Server package). This enables libraries that work with multiple GraphQL servers (such as @apollo/subgraph) to refer to CacheScope without depending on @apollo/server.

Plugin API changes

Fields on GraphQLRequestContext

Most plugin API hooks take a GraphQLRequestContext object as their first argument. Apollo Server 4 makes several changes to the GraphQLRequestContext object.

The context field has been renamed contextValue for consistency with the graphql-js API and to help differentiate from the context option of integration functions (the function which returns a context value).

Apollo Server 4 removes the logger and cache fields from GraphQLRequestContext. These fields are now available as public readonly fields on the ApolloServer object. The GraphQLRequestContext object now provides the ApolloServer object in a new field named server. This means you should replace requestContext.logger and requestContext.cache with requestContext.server.logger and requestContext.server.cache respectively.

Apollo Server 4 removes the schemaHash field from GraphQLRequestContext. This field is an unstable hash of a JSON encoding resulting from running a GraphQL introspection query against your schema. The schemaHash field is not guaranteed to change when the schema changes (e.g., it is not affected by changes to schema directive applications). If you want a schema hash, you can use graphql-js's printSchema function on the schema field and then hash the output.

Apollo Server 4 removes the debug field from GraphQLRequestContext because ApolloServer no longer has a vague debug option. There is no direct replacement for this field. If this is a problem for you, please open a GitHub issue, and we can find an appropriate improvement.

Fields on GraphQLServerContext

Apollo Server 4 makes several changes to the GraphQLServerContext object.

Apollo Server 4 renames the TypeScript type for the argument to the serverWillStart plugin hook from GraphQLServiceContext to GraphQLServerContext, for consistency with the hook name.

Apollo Server 4 removes the logger field from the GraphQLServerContext object. This field is now available as a public readonly field on the ApolloServer object, which GraphQLServerContext provides via a new field named server. This means serviceContext.logger should be replaced with serverContext.server.logger.

Apollo Server 4 removes the schemaHash field (see the previous section for details).

Apollo Server 4 removes the persistedQueries field from GraphQLServerContext. We don't have a reason for providing this particular configuration to plugins, but if having this field is important for you, please file a GitHub issue.

Apollo Server 4 removes the serverlessFramework field, with the new startedInBackground field providing essentially the same information. In Apollo Server 3, the serverlessFramework field returns true if using a subclass of ApolloServer for a serverless framework. In Apollo Server 4, there are no subclasses, and the new API handles startup errors in a serverless-friendly way. The startedInBackground field returns true if your server is started using the server.startInBackgroundHandlingStartupErrorsByLoggingAndFailingAllRequests() method (which should be done by your serverless integration).

GraphQLRequest

Apollo Server 4 refactors the GraphQLRequest object, which is available to plugins as requestContext.request and as an argument to server.executeOperation.

Specifically, the http field is now an HTTPGraphQLRequest type instead of a type based on the Fetch API's Request object. The HTTPGraphQLRequest object does not contain a URL path, and its headers field is a Map (with lower-case keys) rather than a Fetch API Headers object.

GraphQLResponse

Apollo Server 4 refactors the GraphQLResponse object, which is available to plugins as requestContext.response and is the type server.executeOperation returns.

The data, errors, and extensions fields are now nested within an object returned by the result field:

export interface GraphQLResponse {
// The below result field contains an object with the
// data, errors, and extensions fields
result: FormattedExecutionResult;
http: HTTPGraphQLHead;
}

Additionally, the data and extensions fields are both type Record<string, unknown>, rather than Record<string, any>.

The value of http.headers is now a Map (with lower-case keys) rather than a Fetch API Headers object.

We plan to implement experimental support for incremental delivery (@defer/@stream) before the v4.0.0 release and expect this to change the structure of GraphQLResponse further.

Changes to plugin semantics

In Apollo Server 4, requestDidStart hooks are called in parallel rather than in series.

Apollo Server 4 more consistently handles errors thrown by multiple plugin hooks. Each error is wrapped in an "Unexpected error handling request" error and invoked using the new unexpectedErrorProcessingRequest plugin hook.

Custom gateway implementations

⚠️ Note: The alpha version of Apollo Server 4 does not work as an Apollo Gateway. You can still use this alpha to serve subgraphs, just not Gateways. We will fix this before the general release of Apollo Server 4.

The gateway option to the ApolloServer constructor is designed to be used with the ApolloGateway class from the @apollo/gateway package. Apollo Server 4 changes the details of how Apollo Server interacts with this object. If you use a supported version of @apollo/gateway as your server's gateway, these changes won't affect you. However, if you provide something other than an ApolloGateway instance to this option, you might need to adjust your custom code.

All the TypeScript types you need to define your gateway are now part of the @apollo/server-gateway-interface package rather than being exported from an Apollo Server package directly. Additionally, many of these types have been renamed.

In Apollo Server 2, the TypeScript type used for the gateway constructor option is called GraphQLService. In Apollo Server 3, the TypeScript type changed to GatewayInterface, but the apollo-server-core package continued to export an identical GraphQLService type. Apollo Server 4 no longer exports the legacy GraphQLService type. Instead, use GatewayInterface, now exported from @apollo/server-gateway-interface.

In Apollo Server 3, your gateway may define either onSchemaChange or the newer onSchemaLoadOrUpdate. In Apollo Server 4, your gateway must define onSchemaLoadOrUpdate.

In Apollo Server 3, the GatewayInterface.load method returns Promise<GraphQLServiceConfig>, which contains a schema and an executor. Apollo Server 4 renames GraphQLServiceConfig to GatewayLoadResult (exported from @apollo/server-gateway-interface), which now only has an executor field. You can use the onSchemaLoadOrUpdate hook if you want to receive the schema.

Additionally, the following types have been renamed and are now exported from @apollo/server-gateway-interface:

  • GraphQLExecutor is now GatewayExecutor
  • SchemaLoadOrUpdateCallback is now GatewaySchemaLoadOrUpdateCallback
  • Unsubscriber is now GatewayUnsubscriber

Changes to defaults

Apollo Server 3 introduced several recommended features after the initial v3.0.0 release, but these features were turned off by default for backward compatibility. In Apollo Server 4, the recommended behavior is the default. In each case, you can still configure your server to match the default behavior of Apollo Server 3 if you want to.

CSRF prevention is on by default

Apollo Server 3.7 added a recommended security feature called CSRF prevention, which you can enable with the constructor option csrfPrevention: true. In Apollo Server 4, true is the default value. If you want to disable this recommended security feature, pass csrfPrevention: false. For more information about CSRF prevention and CORS, see Configuring CORS.

HTTP batching is off by default

Apollo Server supports batching HTTP requests, enabling a single HTTP request to execute multiple GraphQL operations. In Apollo Server 3, support for HTTP batching was on by default.

In Apollo Server 4, you must explicitly enable this feature by passing allowBatchedHttpRequests: true to the ApolloServer constructor.

Not all GraphQL clients support HTTP batching, and batched requests will not support incremental delivery when Apollo Server implements that feature. HTTP batching can help performance by sharing a context object across operations, but it can make it harder to understand the amount of work any given request does.

Default cache is bounded

Each Apollo Server has a cache backend used in several features, including APQs, the response cache plugin, and RESTDataSource. Apollo Server uses an in-memory cache by default, but you can configure it to use a different backend (such as Redis or Memcached) using the cache constructor option.

In Apollo Server 3, the default cache is an unbounded in-memory cache. This cache is vulnerable to denial of service attacks via memory exhaustion, and we do not recommend that users use the default cache.

In Apollo Server 4, the default cache is a bounded in-memory cache backend (which is safe for production use). This is equivalent to passing cache: 'bounded' in Apollo Server 3.9 or newer.

If you want to customize the cache Apollo Server uses, Apollo provides two wrapper packages to help with this process:

For examples of using both KeyvAdapter and InMemoryLRUCache, see Configuring external caching.

If you want your server to use an unbounded in-memory cache (which might make your server vulnerable to memory exhaustion attacks), you can use the default Keyv implementation with no arguments:

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()),
// ...
});

Local landing page defaults to Embedded Apollo Sandbox

In Apollo Server 3, the default development landing page is a splash page containing a link to the Apollo Sandbox (hosted at https://studio.apollographql.com/). This Sandbox only works if your server's CORS configuration allows the origin https://studio.apollographql.com/. The ApolloServerPluginLandingPageLocalDefault plugin enables you to embed Apollo Sandbox directly on your server's landing page. Passing embed: true to the ApolloServerPluginLandingPageLocalDefault plugin allows your Sandbox to make same-origin requests to your server with no additional CORS configuration.

In Apollo Server 4, the default development landing page is the embedded Apollo Sandbox. Note that nothing changes about the default production landing page.

To use the splash page from Apollo Server 3, you can add the following to your Apollo Server 4 constructor:

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 })
],
});

Renamed packages

The following packages have been renamed in Apollo Server 4:

Note that once Apollo Server 4 is released, all actively maintained Apollo packages will start with @apollo/. This leaves the apollo- namespace open for community integration packages (e.g., apollo-server-integration-fastify).

TypeScript type changes

Several Apollo Server 4 changes only affect TypeScript typings, not runtime behavior. For example, we rename several specific TypeScript interfaces to be more straightforward and change which packages we use to define other interfaces.

Improved typing for context

In Apollo Server 3, you never explicitly specify the type of your context value when setting up your server. This means there is no compile-time check to ensure your context function return type matches the type of your context value (read by your resolvers and plugins). ApolloServer has a generic parameter, but that parameter is the type of the arguments passed to your context function, not the type of your app's context value.

In Apollo Server 4, you specify the type of your context value as a generic parameter to ApolloServer. This gives you proper context typing throughout, ensuring that the type returned from your context function matches the type available in your resolvers and plugins. For example:

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

@apollo/utils.fetcher replaces apollo-server-env

In Apollo Server 3, the apollo-server-env package primarily provides TypeScript typings and polyfills for the fetch and URL APIs.

Apollo Server 4 introduces @apollo/utils.fetcher, which defines a minimal fetch API (Fetcher) that provides Fetch API TypeScript typings.

The @apollo/utils.fetcher package has a more precise name and only supports argument structures that are likely to be compatible across implementations of the Fetch API. Specifically, @apollo/utils.fetcher doesn't permit you to pass Request or Headers objects to fetch because libraries often can only recognize their implementations of these interfaces.

@apollo/cache-control-types

In Apollo Server 3, you could import the CacheScope, CacheHint, CacheAnnotation, CachePolicy, and ResolveInfoCacheControl types from your chosen Apollo Server package.

In Apollo Server 4, the new @apollo/cache-control-types package exports the CacheScope, CacheHint, CacheAnnotation, CachePolicy, and ResolveInfoCacheControl types. This enables libraries that work with multiple GraphQL servers (such as @apollo/subgraph) to refer to these types without depending on @apollo/server.

Apollo Server 4 no longer uses the declare module TypeScript feature to declare that all GraphQLResolveInfo objects (i.e., the info argument to resolvers) have a cacheControl field. Instead, @apollo/cache-control-types provides a GraphQLResolveInfoWithCacheControl interface that you can cast info to (if you don't want run-time validation), or if you do want runtime validation, you can use the maybeCacheControlFromInfo and cacheControlFromInfo functions.

For example, if you had this resolver in Apollo Server 3:

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

you can write this in Apollo Server 4:

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

Alternatively, you can declare info to be of type GraphQLResolveInfoWithCacheControl. For example, if using graphql-code-generator with typescript-resolvers, you can use the customResolveInfo option.

Note: this is a TypeScript-specific change. The runtime representation hasn't changed, and JavaScript code can continue to access info.cacheControl directly.

The CacheAnnotation type is no longer exported from any package.

Renamed types

This section lists the TypeScript-only types (i.e., interfaces, not classes) that Apollo Server 4 changes (not including those mentioned elsewhere in this article).

Apollo Server 4 changes the name of the constructor options type from Config to ApolloServerOptions. In Apollo Server 3, some integration packages export their own versions of this type (e.g., ApolloServerExpressConfig). In Apollo Server 4, there is only one ApolloServer type with only one constructor, so these additional types are no longer necessary.

Two types in apollo-server-express now have more explicit names exported from @apollo/server/express4. GetMiddlewareOptions is now ExpressMiddlewareOptions and ExpressContext is now ExpressContextFunctionArgument.

Removed types

This section lists the TypeScript-only types (i.e., interfaces, not classes) that Apollo Server 4 removes (not including those mentioned elsewhere in this article).

In Apollo Server 3, the GraphQLOptions type was internally used to create integrations and was exported for technical reasons; it is now gone in Apollo Server 4.

Apollo Server 4 removes the applyMiddleware function and its related ServerRegistration type.

In Apollo Server 3, the CorsOptions and OptionsJson types are re-exported from the cors and body-parser packages. Apollo Server 4 no longer handles these tasks for you, so these types aren't exported.

The ServerInfo type (returned from server.listen() in apollo-server) is gone in Apollo Server 4. The startStandaloneServer function now returns a simpler data structure with no type name.

Edit on GitHub
Previous
Get started
Next
Building integrations