GraphQL Resolvers: the Injector Pattern

Cole Turner
Cole Turner
2 min read
Cole Turner
GraphQL Resolvers: the Injector Pattern
Blog
 
LIKE
 
LOVE
 
WOW
 
LOL

One thing I like about Angular are dependency injections. When you need something — you ask for it and it’s there. Could we borrow that for GraphQL?

Resolvers are most often flat functions that take arguments and return a shape of data. Sound familiar? Think of them as controllers or directives in Angular. They need to return a specific shape that matches what the schema expects.

Pure functions are great because they make composition easier:

// Provide path-level objects from request arguments
function resolveRecipient(obj, args, ctx, info) {
    ctx.recipient = User.findById(args.recipientId);
}

// Executes resolvers from right to left
composeResolvers(
    function sharePost(obj, args, ctx, info) {
        // Pull recipient from the context
        const { recipient } = ctx;
        
        return Post.findByUser(recipient);
    },
    resolveRecipient
);

I’ve talked about composing resolvers once before — but here I’m focusing in on one kind of resolver: the injector. This functions takes in an argument and injects some data into our query context. It assigns an object, such as a data model or a return value from an API. One can even use this pattern for authentication or providing service factories. That’s enough Angular for now.

If you’re not already, you should be using Apollo Resolvers by Andrew E. Rhyne. It’s a great library that makes working with apollo-server a breeze. With this library, here is what an injector resolver looks like:

// Inject an object into context that our next resolver can use and manipulate
function resolveRecipient(obj, args, ctx, info) {
  ctx.recipient = User.findById(args.recipientId);
}

Inject an object into context that our next resolver can use and manipulate

Now you’re wondering why bother having a one-line function that mutates its input. Why would I want stateful functions in my application?

Any kind of refactoring work will benefit from this pattern. These functions insulate your resolvers from dependency changes or API refactors. An injector resolver separates out business logic that might be susceptible to change. Injector resolvers are useful for fetching objects from arguments but they can inject anything into context. These single-concern functions are reusable too. Here’s what a major refactor looks like:

// First version of our library:
function resolveRecipient(obj, args, ctx, info) {
  ctx.recipient = User.findById(args.recipientId);
}

// After a migration, how an injector resolver helps:
function resolveRecipient(obj, args, ctx, info) {
  ctx.recipient = Service('User').find.byId(args.recipientId);
}

A high impact change isolated to one modular function

Why does this matter? API changes happen often and the growing number of dependencies in our application adds exponential impact. I recently spent five months changing half a hundred modules to add a single argument. This kind of abstraction would save thousands of hours and keep your schema nimble.

Give the injector pattern and try and share your thoughts. The next developer will appreciate such a small change and the impact it will have. And who knows, that next developer may be you.

 
LIKE
 
LOVE
 
WOW
 
LOL

Related Tags

GraphQLJavaScript

More Stories

Melt of the Day: Brisket, Muenster, & Grilled Onions

2 min read

You can't go wrong with brisket. This recipe uses Snow's BBQ, Texas Monthly's #1 Pick, and grilled onions for the perfect brisket and grilled cheese melt.

Lessons Learned in Freelancing

12 min read

If you want to become a freelance web developer, these are the lessons that will help you have a great time working with clients and being effective.

See more posts

Read it before anyone else. Subscribe to my newsletter for early access to the latest news in software engineering, web development, and more.