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.

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:
JAVASCRIPTconst { 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:
JAVASCRIPTconst 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.
| Feature | Without Context | With Context |
|---|---|---|
| Data Source | Hard-coded imports | Injected via context |
| Testability | Hard to isolate/mock | Easy to swap dependencies |
| Authentication | Manual parsing in every resolver | Centralized in context function |
| Global State | Risk of side effects | Scoped to the individual request |
Practice Exercise
- Open your current project and identify one resolver that relies on a hard-coded data file.
- In your
ApolloServerconfiguration, add acontextfunction that exposes that data file (or a database module) asdata. - Refactor that resolver to use
context.datainstead of the local import. - 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
contextfunction 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.
dbormodelsis better thandata, 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.
Work with me

Laravel REST API Development
Clean, secure, well-documented Laravel REST APIs — the backend engine for your app, mobile client, or SaaS. Built by an API specialist.

FilamentPHP Admin Panel & Dashboard Development
A powerful admin panel for your Laravel app — built with FilamentPHP so you can manage everything without touching the database.


