Structuring Schema with Apollo Server

Cole Turner
Cole Turner
5 min read
Cole Turner
Structuring Schema with Apollo Server
Blog
 
LIKE
 
LOVE
 
WOW
 
LOL

How do you structure data? That’s the question that GraphQL intends to solve and keep you up at night. Its strongly typed schema and atomic design is the pinnacle of client-driven development. Taking after JSON, the language has a familiar syntax and adds a robust framework around structure data. Many beginners may wonder about how to bootstrap a server or what conventions to follow.

Last week I dived in and ran
yarn install apollo-server
to bootstrap a Node backend API. A couple of boilerplate changes later and I had a working schema with a blank slate. Over the course of a couple days I added type definitions and resolvers — there’s an easy to follow guide on Apollo’s website. But I wanted something a bit more ergonomic.

The schema definition, recently adopted into the GraphQL spec, is an easy way to get started. You can use SDL to define types, fields, and just about anything. What’s neat is you can even create directives that add further flexibility.

directive @upper on FIELD_DEFINITION
directive @deprecated on FIELD_DEFINITION
directive @cache(maxAge: String!) on FIELD_DEFINITION

For this you you will need graphql-tools — it’s slurps up all the schema definition language and a map of the resolver functions. The advantage of defining your schema using SDL is it can be interpreted cross-platform.

# import '*' from 'graphql/directives.graphql'

schema {
  query: Query
  mutation: Mutation
}

This is especially necessary for microservice applications where types may be imported remotely. When I import my Book type I also need the definitions for Chapter and Page types.

There’s a neat package for that too: graphql-import by Graphcool. It’s a function that imports GraphQL type definitions and parses the headers for other imports.

# import Chapter from './Chapter.graphql'

type Book {
  chapters: [Chapter!]
}

It uses string parsing for the comment syntax however there exists a consensus among the open-source community that this kind of feature would be better surfaced in GraphQL JS itself.

As your schema grows, this allows you to take your types as if they were Legos and build with granularity. And some of these types may belong to multiple schemas, or be exported and imported remotely.

Keep them separated. This is great for JavaScript because maps of resolvers can be defined as modules themselves — ready for export and import.

Resolver functions have very specific purposes: their role is to take in a node in the graph and resolve a selected field. That means when I select the “chapters” field from my Book type — the resolver takes in the book object and returns an object that fits the shape of chapters.

A good resolver worries only about type and resolution:

  • Parse and validate arguments
  • Authorization and authentication
  • Return a value or promise that resolves to the same shape

Any orchestration or business logic belongs outside of GraphQL. For fetching data and caching you can take advantage of third party packages such as DataLoader which is often used with GraphQL JavaScript implementations.

With a little functional programming, we can compose resolvers in a reusable and predictable fashion. If we chain our resolvers together, we can handle every concern individually — adding effects declaratively.

import { createResolver } from 'apollo-resolvers';
import { createError, isInstance } from 'apollo-errors';

// Our base resolver that other resolvers will extend
export const baseResolver = createResolver(
   // transparent no-op
  null,

  // Mask unrecognized errors so that we don't leak
  // Database errors, etc
  (root, args, context, error) => isInstance(error) ? error : new UnknownError()
);

const AuthenticationRequiredError = createError('AuthenticationRequiredError', {
  message: 'You must be authenticated to do that!'
});

export const isAuthenticatedEffect = (root, args, { user }) => {
  if (!user) {
    throw new AuthenticationRequiredError();
  }
}

export const isAuthenticatedResolver = baseResolver.createResolver(
  // Extract the user from the context
  isAuthenticatedEffect
);

I’ll tell you why I keep them separate in a minute.

For this I am fond of apollo-resolvers and apollo-errors (shoutout to Andrew E. Rhyne for these neat packages). One night I added these packages to my app and setup my toolkit of resolver functions. From there I was rapidly writing new schema with ease. The one I most enjoyed writing was a resolver function that memoizes another resolver using promise-memoize.

import memoizePromise from 'promise-memoize';

export const memoizingResolver = (fn, opts) => {
  const next = memoizePromise(fn, opts);
  
  return baseResolver.createResolver(
    (...args) => {
      return next(...args);
    });
};

This became effective both as a way to avoid hitting third party services too often and rate-limiting incoming requests. Why stop here though?

As you add some weight to your schema the concerns of each resolver become repetitive. You long for a way to abstract all the duplicate validations and application logic. At least I did from the very start — I wanted to chain together resolvers so I could wrap my resolvers and add functionality.

// Easy way to compose resolvers that execute from right to left. This allows us to re-use functions for multiple behaviors.
function composeResolvers(...resolvers) {
  return resolvers.reduceRight((base, next) => base.createResolver(next));
}

// The desired effect is to ban the user
// Using composition, this creates a resolver that will:
// 1. Check if the request is authenticated
// 2. Check if the request is made by an Admin
// 3. Check if the admin has permission to ban the user
// 4. Ban the user
composeResolvers(banUser, canBanUser, isAdmin, isAuthenticated);

And that’s why I break up my resolvers into their concerning effects. It becomes easier to write schema quickly in a way that improves server side development. Small functions like isAuthenticatedEffect benefit from being reusable and testable. Rather than modify existing behavior, adding new effects avoids impacting existing flows. Until GraphQL I had never thought highly of the composition pattern however in this context it neatly wraps around thin logic to make for atomic effects.

GraphQL Server Architecture Flowchart

These are just some of many great tools out there for GraphQL and I have yet to see them all. I wanted to write this post to share my experience getting started with Apollo Server. The middleware fits right into a Express application in between business logic and your clients.

Having worked with GraphQL since 2016 using Ruby I found the JavaScript and Apollo experience to be so much simpler. Building a schema with the schema definition language just feels right and works well across different platforms. And much thanks to Meteor development group and the thousands of passionate folks that are collectively working together to make GraphQL in JavaScript a phenomenal experience from client to server.

If you know of other must-haves or conventions that make bootstrapping an Apollo server a breeze — please share in the comments.

 
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.