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):
import { GraphQLError } from 'graphql';const resolvers = {Query: {// Example resolveradminExample: (parent, args, contextValue, info) => {if (contextValue.authScope !== ADMIN) {throw new GraphQLError('not admin!', {extensions: { code: 'UNAUTHENTICATED' },});}},},};interface MyContext {// You can optionally create a TS interface to set up types// for your contextValueauthScope?: String;}const server = new ApolloServer<MyContext>({typeDefs,resolvers,});const { url } = await startStandaloneServer(server, {// Your async context function should async and// return an objectcontext: async ({ req, res }) => ({authScope: getScope(req.headers.authorization),}),});
import { GraphQLError } from 'graphql';const resolvers = {Query: {// Example resolveradminExample: (parent, args, contextValue, info) => {if (contextValue.authScope !== ADMIN) {throw new GraphQLError('not admin!', {extensions: { code: 'UNAUTHENTICATED' },});}},},};const server = new ApolloServer({typeDefs,resolvers,});const { url } = await startStandaloneServer(server, {// Your async context function should async and// return an objectcontext: async ({ req, res }) => ({authScope: getScope(req.headers.authorization),}),});
The above example assumes you're using either startStandaloneServer
or expressMiddleware
, both of which use Express under the hood. Your context
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:
context: async () => ({db: await client.connect(),})// Resolver(parent, args, contextValue, info) => {return contextValue.db.query('SELECT * FROM table_name');}
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:
context: async ({ req }) => {const user = await getUserFromReq(req);if (!user) {throw new GraphQLError('User is not authenticated', {extensions: {code: 'UNAUTHENTICATED',http: { status: 401 },}});}// If the below throws a non-GraphQLError, the server returns// `code: "INTERNAL_SERVER_ERROR"` with an HTTP status code 500, and// a message starting with "Context creation failed: ".const db = await getDatabaseConnection();return { user, db };},
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
:
import { AnimalAPI } from './datasources/animals';const resolvers = {Query: {// All of our resolvers can access our shared contextValue!dogs: (_, __, contextValue) => {return contextValue.dataSources.animalApi.getDogs();},cats: (_, __, contextValue) => {return contextValue.dataSources.animalApi.getCats();},},};interface MyContext {// Context typingdataSources: {animalApi: AnimalAPI;};}const server = new ApolloServer<MyContext>({typeDefs,resolvers,});const { url } = await startStandaloneServer(server, {context: async () => {const animalApi = new AnimalAPI();return {dataSources: {animalApi,},};},});
import { AnimalAPI } from './datasources/animals';const resolvers = {Query: {// All of our resolvers can access our shared contextValue!dogs: (_, __, contextValue) => {return contextValue.dataSources.animalApi.getDogs();},cats: (_, __, contextValue) => {return contextValue.dataSources.animalApi.getCats();},},};const server = new ApolloServer({typeDefs,resolvers,});const { url } = await startStandaloneServer(server, {context: async () => {const animalApi = new AnimalAPI();return {dataSources: {animalApi,},};},});
Plugins
Built-in and custom plugins can access contextValue
through request lifecycle functions, like so:
interface MyContext {token: string;}const server = new ApolloServer<MyContext>({typeDefs,resolvers: {Query: {hello: (root, args, { token }) => {return token;},},},plugins: [{async requestDidStart({ contextValue }) {// token is properly inferred as a stringconsole.log(contextValue.token);},},],});const { url } = await startStandaloneServer(server, {context: async ({ req, res }) => ({token: await getTokenForRequest(req),}),});
const server = new ApolloServer({typeDefs,resolvers: {Query: {hello: (root, args, { token }) => {return token;},},},plugins: [{async requestDidStart({ contextValue }) {// token is properly inferred as a stringconsole.log(contextValue.token);},},],});const { url } = await startStandaloneServer(server, {context: async ({ req, res }) => ({token: await getTokenForRequest(req),}),});