GraphQL + Apollo – Part 2: Building a Server

In this series, we are taking a dive into GraphQL through the lens of the Apollo toolchain. Part 1 of the series explains some core concepts of GraphQL and introduces Apollo as a GraphQL solution. In this post, we will take a closer look at building a GraphQL server with Apollo tools. Part 3 will take a look at the Apollo Client libraries and implement an example client.

What is Apollo Server?

Apollo Server is essentially a set of tools which are useful for building a GraphQL Server. The package name that includes these server tools is graphql-tools, which is created and maintained by the Apollo community. Graphql-tools are a set of tools which enable production-ready GraphQL.js schema building using the GraphQL schema language, rather than using the (rather clunky) GraphQL.js type constructors directly.

Apollo also created another package called graphql-server, which is a Node.js GraphQL Server for Express, Connect, hapi, and Koa.

Essentially, Apollo Server is a thin API and routing layer for your application. It will, at a high level:

  • Receive incoming GraphQL queries/mutations and map to schema
  • Resolve and validate input types
  • Map query/mutation to root resolver to begin resolving the request
  • Resolve and validate types for return data

Defining the Schema

The core of a GraphQL Server is the schema. The schema defines types and their relationships with each other, along with the specific queries and mutations which can be executed. Apollo Server uses the GraphQL schema language notation, which it then compiles to a GraphQL.js schema.

In Part 1, we looked at the following schema:


const schema = `
type Person {
  name: String!
  age: Int
  gender: Gender
  height(unit: HeightUnit = METER) : Float
}

enum Gender {
  MALE
  FEMALE
}

enum HeightUnit { 
  METER
  INCH
}

type Query {
  personWithName(name: String!): Person
  guys: [Person]
  girls: [Person]
}

schema {
  query: Query
}
`;

This schema defines three root queries: personWithName, guys, and girls, all of which return either a single Person or a Person array. We also have a Person type which defines several different fields on that object.

Setting up Resolvers

Resolvers are the glue between the schema and your data. Apollo uses them to figure out how to respond to a query and resolve the incoming or return types. Every query and mutation requires a resolver function, and each field in every type can have a resolver.

Apollo will continue to invoke the chain of resolvers until it reaches a scalar type (i.e. String, Int, Float, Boolean). So, we should define a few root resolvers for the queries:


const rootResolvers = {
  Query: {
    personWithName(root, { name }, context) {
      return findBy('name', name).then(people => people ? people[0] : []);
    },

    guys(root, _, context) {
      return findBy('gender', 'MALE');
    },

    girls(root, _, context) {
      return findBy('gender', 'FEMALE');
    }
  }
};

// This `findBy` method simulates a database query, hence it returning a promise.
const findBy = (field, value) => Promise.resolve(people.filter(person => person[field] === value));

const people = [
  {
    name: 'George',
    age: 17,
    gender: 'MALE',
    height: 72 
  }, {
    name: 'Jill',
    age: 19,
    gender: 'FEMALE',
    height: 65 
  }, {
    name: 'Alexander',
    age: 32,
    gender: 'MALE',
    height: 68 
  }
];

So there we have it: three query resolvers. For the sake of keeping this example simple, I added a static array of fake people and a function findBy which simulates sending a query to a database, returning a promise.

When a promise is returned from a resolver function, Apollo will automatically wait for the promise to resolve or reject. If the promise resolves successfully, Apollo will try to use that data to resolve the return type of the query–in our case, a Person type.

The next step is to define the Person resolver:


const personResolver = {
  Person: {
    name: ({ name }) => name.toUpperCase(),
    height: ({ height }, { unit }) => unit === 'METER' ? height * 0.0254 : height
  },
};

Since name, age and gender are all scalar types (String) and the name in the data matches the name in the type definition, there is no need to define a resolver for those fields.

However, let’s say we want to do some conversion on the name–like capitalize the name–and then return the capitalized string. To implement this, we just need to define a name resolver that capitalizes the input name. We also need a resolver for the height field, since this field takes an argument. Given the source object (as returned from the root resolver) and the field arguments, this field resolver must convert the value to the desired units.

There is one additional piece of code that you need to write to actually “glue” the schema to its resolvers. You must call makeExecutableSchema and pass in the schema(s) and an object containing all resolvers.


const executableSchema = makeExecutableSchema({
  typeDefs: [schema],
  resolvers: merge(rootResolvers, personResolver), // merge using lodash, for example
});

Connecting to Express

Now that we have a functional schema and “backend,” we need to hook into a server framework such as Express. Apollo has a really nice integration with Express (along with a few others mentioned earlier), where all you really need to do is define the GraphQL endpoint and pass in your schema. Below is a simple example:


const app = express();
app.use(bodyParser.json());

app.use('/api/graphql', apolloExpress(req => {
  return {
    executableSchema,
    context: { user: 'matt' }
  };
}));

For every request to ‘/api/graphql’, Apollo will run through the entire query/mutation processing chain. The context key will be passed to every resolver, which can be very handy!

The Finished Product

Using GraphiQL, a utility to test queries and mutations live against a GraphQL server, we can see our Apollo Server in action!

In the image above, you’ll see all three of our queries, executing all the different functionality we implemented for our resolvers!

GraphQL Servers are a powerful tool that can be used in virtually any ecosystem. I’ve been really happy with the Apollo toolchain so far—what do you think?