Back to Blog
Lesson 27 of the GraphQL: Your First GraphQL Schema & Server course
API ArchitectureAugust 14, 20264 min read

Using the Context Object: Dependency Injection in GraphQL

Learn how to use the GraphQL context object to share databases and services across resolvers. Master dependency injection to keep your server modular.

GraphQLContextDependency InjectionNode.jsApollo ServerBackend
Three syringes arranged on a red surface showcasing medical equipment with copy space.

Previously in this course, we explored the resolver signature, learning how parent and args allow us to navigate our data graph. In this lesson, we move beyond individual field resolution to address a critical architectural challenge: how to provide your resolvers with access to shared data sources, authentication utilities, and global state without polluting your global scope.

The Role of Context in GraphQL

As your application grows, your resolvers will eventually need more than just the parent object or incoming args. You’ll need access to a database connection, a cache layer, or perhaps the user's authentication token.

If you hard-code these imports directly inside your resolver files, you create "tight coupling." This makes testing difficult because you can't easily swap a real production database for a mock during unit tests. The context object is the standard solution to this problem in GraphQL. It acts as a bridge, allowing you to perform dependency injection by passing shared utilities into every resolver that executes during a single request.

Defining the Context Function

In Apollo Server, you define your context by providing a context function in your server constructor. This function runs once for every incoming operation, meaning you can derive context values dynamically—like extracting a token from an HTTP header to identify the currently logged-in user.

Here is how you initialize the context in your index.js file:

JAVASCRIPT
const { ApolloServer } = require(CE9178">'apollo-server');
const db = require(CE9178">'./database'); // A simulated database connection

const server = new ApolloServer({
  typeDefs,
  resolvers,
  // The context function is called for every request
  context: ({ req }) => {
    return {
      db, // Injecting our database utility
      user: req.headers.authorization ? getUser(req.headers.authorization) : null
    };
  }
});

Accessing Context in a Resolver

The context object is the third argument in the standard four-part resolver signature: (parent, args, context, info). Because it is available in every resolver, you can access your database or user state seamlessly.

Let’s update our project's user resolver to fetch data from the injected database instead of using static arrays:

JAVASCRIPT
const resolvers = {
  Query: {
    user: (parent, args, context) => {
      // Accessing the database through the context
      return context.db.users.findById(args.id);
    },
    currentUser: (parent, args, context) => {
      // Accessing the user derived from the request header
      if (!context.user) throw new Error("Unauthorized");
      return context.user;
    }
  }
};

Why Use Context?

Using the context object is essentially a form of dependency injection. It keeps your resolvers "pure" regarding their environment. If you ever need to mock your database for testing, you simply pass a different db object into the context during your test setup, rather than refactoring your actual business logic.

FeatureWithout ContextWith Context
Data SourceHard-coded importsInjected via context
TestabilityHard to isolate/mockEasy to swap dependencies
AuthenticationManual parsing in every resolverCentralized in context function
Global StateRisk of side effectsScoped to the individual request

Practice Exercise

  1. Open your current project and identify one resolver that relies on a hard-coded data file.
  2. In your ApolloServer configuration, add a context function that exposes that data file (or a database module) as data.
  3. Refactor that resolver to use context.data instead of the local import.
  4. Verify that your query still returns the correct data via Apollo Sandbox.

Common Pitfalls

  • Overloading the Context: While it's tempting to put everything in the context, keep it lean. Only include shared services (DB, cache, data loaders) and request-specific metadata (user, locale).
  • Performance: Remember that the context function runs on every request. Avoid doing heavy work (like complex database queries) directly inside the context function. Instead, inject the connection or client, and let the resolver perform the fetch.
  • Naming Collisions: Since every developer might add something to the context, use descriptive keys. db or models is better than data, which is ambiguous.

FAQ

Q: Can I pass functions into the context? A: Absolutely. Many developers pass helper functions or "DataLoaders" (which we will cover in a future lesson) into the context to simplify data fetching.

Q: Is the context shared between different users? A: No. A new context object is created for each individual request. This makes it thread-safe and isolated, ensuring user A's data never leaks into user B's request.

Recap

The context object is the primary way to manage shared state and dependencies in a GraphQL server. By defining a central context function, you decouple your resolvers from their environment, enabling cleaner code and easier testing.

Up next: We’ll explore Introduction to Mutations, where we will finally allow clients to modify data rather than just querying it.

Similar Posts