Context and contextValue
Sharing information and request details throughout your server
During a GraphQL operation, you can share data throughout your server's resolvers and plugins by creating an object named contextValue
.
You can pass useful things through your contextValue
that any resolver might need, like authentication scope, sources for fetching data, database connections, and custom fetch functions. If you're using dataloaders to batch requests across resolvers, you can also attach them to the shared contextValue
.
The context
function
📣 Apollo Server 4 changes the syntax for defining a
context
function. See more details.
The context
function should be asynchronous and return an object. This object is then accessible to your server's resolvers and plugins using the name contextValue
.
You can pass a context
function to your integration function of choice (e.g., expressMiddleware
or startStandaloneServer
).
Your server calls the context
function once for every request, enabling you to customize your contextValue
with each request's details (such as HTTP headers):
1import { GraphQLError } from 'graphql';
2
3const resolvers = {
4 Query: {
5 // Example resolver
6 adminExample: (parent, args, contextValue, info) => {
7 if (contextValue.authScope !== ADMIN) {
8 throw new GraphQLError('not admin!', {
9 extensions: { code: 'UNAUTHENTICATED' },
10 });
11 }
12 },
13 },
14};
15
16interface MyContext {
17// You can optionally create a TS interface to set up types
18// for your contextValue
19 authScope?: String;
20}
21
22const server = new ApolloServer<MyContext>({
23 typeDefs,
24 resolvers,
25});
26
27const { url } = await startStandaloneServer(server, {
28 // Your async context function should async and
29 // return an object
30 context: async ({ req, res }) => ({
31 authScope: getScope(req.headers.authorization),
32 }),
33});
The above example assumes you're using either
startStandaloneServer
orexpressMiddleware
, both of which use Express under the hood. Yourcontext
function's incoming arguments might differ if you're using a different integration.
If you are using TypeScript, you must provide a named context
function if you type your context by passing a type parameter to ApolloServer
(i.e., you don't use ApolloServer<BaseContext>
).
Because the context
initialization function is asynchronous, you can use it to establish database connections and wait for other operations to complete:
1context: async () => ({
2 db: await client.connect(),
3})
4
5// Resolver
6(parent, args, contextValue, info) => {
7 return contextValue.db.query('SELECT * FROM table_name');
8}
Throwing errors
By default, if your context
function throws an error, Apollo Server returns that error in a JSON response with a 500 HTTP status code. If the error is not a GraphQLError
, the error's message is prepended with "Context creation failed: "
.
You can change the HTTP status code of an error by throwing a GraphQLError
with an http
extension. For example:
1context: async ({ req }) => {
2 const user = await getUserFromReq(req);
3 if (!user) {
4 throw new GraphQLError('User is not authenticated', {
5 extensions: {
6 code: 'UNAUTHENTICATED',
7 http: { status: 401 },
8 }
9 });
10 }
11
12 // If the below throws a non-GraphQLError, the server returns
13 // `code: "INTERNAL_SERVER_ERROR"` with an HTTP status code 500, and
14 // a message starting with "Context creation failed: ".
15 const db = await getDatabaseConnection();
16
17 return { user, db };
18},
The contextValue
object
The context
function returns an object, contextValue
, that is accessible to your plugins and resolvers.
Resolvers
Resolvers should never destructively modify the
contextValue
argument. This ensures consistency across all resolvers and prevents unexpected errors.
Your resolvers can access the shared contextValue
object via their third positional argument. All resolvers that are executing for a particular operation have access to contextValue
:
1import { AnimalAPI } from "./datasources/animals";
2
3const resolvers = {
4 Query: {
5 // All of our resolvers can access our shared contextValue!
6 dogs: (_, __, contextValue) => {
7 return contextValue.dataSources.animalApi.getDogs();
8 },
9 cats: (_, __, contextValue) => {
10 return contextValue.dataSources.animalApi.getCats();
11 },
12 },
13};
14
15interface MyContext { // Context typing
16 dataSources: {
17 animalApi: AnimalAPI;
18 }
19}
20
21const server = new ApolloServer<MyContext>({
22 typeDefs,
23 resolvers,
24});
25
26const { url } = await startStandaloneServer(server, {
27 context: async () => {
28 const animalApi = new AnimalAPI();
29 return {
30 dataSources: {
31 animalApi
32 }
33 }
34 }
35});
Plugins
Built-in and custom plugins can access contextValue
through request lifecycle functions, like so:
1interface MyContext {
2 token: string
3}
4
5const server = new ApolloServer<MyContext>({
6 typeDefs,
7 resolvers: {
8 Query: {
9 hello: (root, args, { token }) => {
10 return token;
11 },
12 },
13 },
14 plugins: [{
15 async requestDidStart({ contextValue }) {
16 // token is properly inferred as a string
17 console.log(contextValue.token);
18 },
19 }],
20});
21
22const { url } = await startStandaloneServer(server, {
23 context: async ({req, res}) => ({
24 token: await getTokenForRequest(req),
25 })
26});