In a GraphQL request the context servers to be an entry point to services from your resolvers. We want to ensure that our context objects last only the life time of our query. The easiest way to do this is to use a dependency injection framework such as inversify to manage the scope and lifecycle of dependencies. Since inversify, and other DI frameworks for NodeJS, have no concept of query lifecycle inherently we can use the inRequestScope option which dictates that the resolution lifecycle be tied to the lifecycle of the parent to which the relationship is created.

What this means is our ContextProvider class will house a reference to services, other providers, managers, etc used through our query lifecycle. This ensures that we can resolve a ContextProvider from the container in our context callback on each request and that the bound services will survive so long as that context object lives.

This is invaluable in GraphQL, especially when dealing with lifecycle sensitive objects such as Facebook's DataLoader library. These DataLoaders must only exist in the lifecycle of a query otherwise you risk leaking sensitive information.

To start let's create a ContextProvider class.

const IContextProviderType = Symbol.for("IContextProvider");
interface IContextProvider {
    bookingService: IBookingService;
}

This is a simple example that creates an interface representing the contract we expect our ContextProvider model to fulfill as well as the IContextProviderType which serves as the bindable runtime representation of our interface. This is needed since JavaScript doesn't have full reflection capabilities and in TypeScript interfaces are compile time and not repsented in runtime in any way.

import { injectable, inject } from "inversify";
import { IBookingServiceType, IBookingService, IContextProvider } from "../core";

@injectable()
class ContextProvider implements IContextProvider {
    @inject(IBookingServiceType)
    public bookingService: IBookingService;
}

You can imagine the BookingService is implemented in similar fashion making itself injectable by inversify as well. In our IoC container we will register the BookingService and ContextProvider.

container.bind<IBookingService>(IBookingServiceType).to(BookingService).inRequestScope();
container.bind<IContextProvider>(IContextProviderType).to(ContextProvider).inRequestScope();

Now we will need to resolve an instance of the ContextProvider class in the request scope of a GraphQL query. To do so we will hook into the context callback option available in most GraphQL providers. The example below will use apollo-server-express.

app.use(
    '/graphql',
    cors(),
    bodyParser.json(),
    graphqlExpress((req) => (
        { 
            schema, 
            context: () => {
                const contextProvider = container.get<IContextProvider>(IContextProviderType);
                // do stuff with the context provider here
                return contextProvider;
            }
        }
    ))
);

This will make the ContextProvider and any bound dependencies available to your resolvers with their lifecycle scoped to the lifecycle of the GraphQL query.

export default {
    Booking: {
        orders: (parent: BookingDTO, { userId }, context: IContextProvider) => {
            return context.bookingService.getOrdersByUserId(userId);
        }
    }
}

The Context Provider Pattern provides a lifecycle-safe way to manage your resolver's service layer needs. Since the express (or similar framework) Request object is available to the context callback we can also use this provider to manage tasks such as authorization, localization, etc. I will have some posts in the future combining the Context Provider and Token Provider patterns to provide powerful authorization capabilities to your GraphQL service.

Thanks for reading and happy coding!