GraphQL Server: After ‘Hello, World’

Part 2 of 2: Resolvers & Caching Conventions

Justin Mandzik
5 min readJul 7, 2017

In Part 1, I covered how to approach structuring the schema for your GraphQL server projects. Here, I’ll be covering Resolvers and some of the surrounding topics.

Resolver Composition Patterns

As schemas are the what, resolvers are the how. At their core, they are just functions that are expected to return a value. Values returned that are Promises are resolved by the GraphQL runtime. Understanding the function signature concepts will pay off and is a necessity for anything non-trivial.

Most real-world applications are going to need to address concerns like authorization and authentication. After implementing the first few fields worth of logic, patterns of checking for session credentials (for example) on each field will jump out as ripe targets for trying to keep DRY. Since we are dealing with functions, we can borrow some techniques from functional programming practices to keep our code focused and testable. That they run in the context of a lifecycle of a query (or request) is something to bear in mind.

Two libraries (graphql-resolvers and apollo-resolvers) have sprung up to help us with this approach. They differ in ergonomics, but both are nice approaches to help us build small, reusable functions that chain together. graphql-resolvers uses a left-first composition approach where the first function to return a value “wins”.

apollo-resolvers has a similar composition approach, but offers some additional utilities around managing contextvia factories:

In both cases, the cross cutting concerns of auth can be lifted out of the models, allowing them to be focused solely on business logic of how to get the data you’re after. Your composed resolvers are just simple functions you can run and assert your outcomes on.

Caching Concerns

How, when, and where to cache API calls is a deep topic with highly case-dependent properties to it. That said, there are a few general thoughts that come to mind in a GraphQL context worth thinking about.

Intra-query caching via DataLoaders

Consider a fictitious work-related social network. The front-end wants to show a page with your info, your colleague’s info, and a list of your colleagues favorite co-workers. Since you’re awesome, there’s a chance that you’ll show up a number of times in the query response.

DataLoader, a library from Facebook, was designed as a caching utility to memoize calls for data for these types of situations. Where as a naive implementation would trigger repeated network dips to fetch your user information as many times as needed, leveraging a data loader incurs the wire cost only once per execution. Often, the tradeoffs around caching often involve determining your application’s tolerance for potentially stale data. In this case, the cache only lives as long as the request does. Subsequent (or concurrent!) queries will not get the benefit of the data loader’s cache, so the window for staleness is small.

If your application is structured in a Connectors/Models pattern, Connectors are a reasonable spot to house your loaders. Models leverage connectors to manage the low-level fetching & transport mechanics.

Inter-query caching

API development, particularly “real-time” APIs, often have different caching strategies than static web content. Caching at the transport layer with tools like Varnish can be an anti-pattern for APIs, as individual fields can have different ideal Time-To-Live (TTL) requirements. One strategy I found particularly flexible in a REST API I built for an ISP was to cache at the individual field-level in the Models. This particular API was a traditional MVC with thin controllers and thick models. These concepts map almost verbatim to GraphQL field resolvers if you structure your code that way. You can think of field resolvers as highly focused controllers; essentially a coordination layer that make use of models to produce their values.

Lets talk some tangibles. Imagine you have a network device (router, server, cable modem, etc) and you have a few queries & mutations you can take. These actions can be things like pinging the device or querying it for health metrics. Some pseudo-code:

class NetworkDevice {    ipAddress() {} // TTLs of 5 minutes, spans users    ping() {}      // TTLs of 1 second, needs IP Address    healthMetrics() {}      // TTLs of 5 seconds, needs IP Address}class CableModem extends NetworkDevice {    billingAccount() {} // TTLs of 1 hour}

ipAddress information is needed for all the different ways we will talk to a device. In reality, DCHP lease addresses can last weeks. 5 minute caching on the IP Address means we avoided millions of unnecessary dips to the database if we took on a small window of potentially stale information.
A one second cache on ping responses was a nice safety net against friendly DDOS if a device was being investigated by many people at the same time. Billing info was updated nightly; a 1 hour cache saved us many needless account lookups. What I’m driving at is thoughtful caching based on real-world constraints & observations can give way to optimizations that can save significant resources. Implementing this caching at the model layer rather than resolver means querying data like ping and healthMetrics can take advantage of prerequisite data (IP Address). Time and again, I’ve found value in capturing business logic in models that have no context of being run in a REST API, GraphQL, or even a CLI program.

Closing thoughts

There are a number of conventions around input types and (Relay) pagination that will come up quickly in API design that I want to cover. These topics will be easier to cover with a sample repo I’ll have in a follow up post. If you want to talk shop about GraphQL, I’m available on the Apollo and GraphQL Slack channels. I’m also available for work; feel free to reach out.

--

--