Implementing GraphQL Using Apollo On an Express Server

John Kariuki
πŸ‘οΈ 8,470 views
πŸ’¬ comments

Introduction

GraphQL is a data query language for APIs and runtime, a specification that defines a communication protocol between a client and server. At its core, GraphQL enables flexible, client-driven application development in a strongly typed environment. It provides a complete and understandable description of the data in your API, gives the clients the power to ask for exactly what they need.

GraphQL Home Page

GraphQL enables declarative data fetching where a client can specify exactly what data it needs from an API. Instead of multiple endpoints that return fixed data structures, a GraphQL server only exposes a single endpoint and responds with precisely the data a client asked for.

It is a new API standard that provides a more efficient, powerful and flexible alternative to REST and is not tied to any backend or database and is instead backed by your existing code and data.

Client appllications using GraphQL are fast and stable because they control the data they get, not the server, providing predictable results.

To successfully complete this tutorial, we will require Node JS to be setup on our computers.

Concepts

Schema

The server defines the schema which defines the objects that can be queried, along with corresponding data types. A sample simple project schema may be defined as follows:

type Project {
  id: ID!                                            //"!" denotes a required field
  name: String
  tagline: String
  contributors: [User]
}

type User {
 id: ID!
 name: String
 age: Int
}

The schema above defines the shape of a project with a required id, name, tagline an contributors which is an array of type User.

Queries and Mutations

GraphQL clients communicate with GraphQL servers via queries and mutations. A query also defines the shape of the resulting data, allowing the client to explicitly control the shape of data being consumed. Queries correspond to GET requests in normal REST applications while mutations correspond to POST, PUT and other http verbs used to make chnages to data stored on the server. For instance, to retrieve a list of projects, including name and tagline only, the following query may be used.

query {
    projects {
        name
        tagline
    }
}

An equivalent of querystrings in REST may be used as follows:

query {
  project(id: "1"){  //Fetch a project whose id is 1
    name
    tagline
  }
}

Result:

 {
  "data": {
    "project": {
      "name": "React JS",
    "tagline": "js"
    }
  }
}

As you can see, the client has the liberty to specify exactly what fields they want. This prevents fetching of unnecessay data and speeds up the fetching process. A resolver will be responsible for implementing the rest of the logic after getting the arguments.

Resolvers

Resolvers are the link between the schema and your data. They provide the functionality that may be used to interact with databases through different operations.

Apollo

Apollo Server is a tool that provides set of GraphQL server tools from Apollo that work with various Node.js HTTP frameworks (Express, Connect, Hapi, Koa etc). 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 GraphQL.js type constructors directly.

Setting up

We will begin by setting up the following folder structure:

app/
β”œβ”€β”€ src/
    └── resolvers.js
    └── schema.js
β”œβ”€β”€server.js
β”œβ”€β”€.babelrc
β”œβ”€β”€ package.json
β”œβ”€β”€ node_modules/

Creating the Schema

We can finally create a working implementation. Lets start by creating a GraphQL schema.

// app/src/schema.js
import {  makeExecutableSchema } from 'graphql-tools';

import { resolvers } from './resolvers'; // Will be implemented at a later stage.

const typeDefs = `
    type Channel {
      id: ID!                # "!" denotes a required field
      name: String
      messages: [Message]!
    }

    type Message {
      id: ID!
      text: String
    }
    # This type specifies the entry points into our API. 
    type Query {
      channels: [Channel]    # "[]" means this is a list of channels
      channel(id: ID!): Channel
    }

    # The mutation root type, used to define all mutations.
    type Mutation {
      # A mutation to add a new channel to the list of channels
      addChannel(name: String!): Channel
    }
    `;

const schema = makeExecutableSchema({ typeDefs, resolvers });
export { schema };

Notice const schema = makeExecutableSchema({ typeDefs, resolvers }); You must call makeExecutableSchema and pass in the schema(s) and an object containing all resolvers to actually β€œglue” the schema to its resolvers. We then export the schema to use it later in our server.

Explanation

The queries are similar to the ones explained above. Lets focus on the mutation:

type Mutation {
      # A mutation to add a new channel to the list of channels
      addChannel(name: String!): Channel
}

This defines a muattion called addChannel that takes an argument/variable name and returns a Channel as a result. A sample mutation looks as follows:

mutation {
  addChannel(name:"lacrose") {
    name
  }
}

As we can see, as much as the schema defines a Channel as the result, the client has the ability to request for the name only.

Creating 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:

// app/src/resolvers.js
const channels = [{
  id: 1,
  name: 'soccer',
}, {
  id: 2,
  name: 'baseball',
}];

let nextId = 3;

export const resolvers = {
  Query: {
    channels: () => {
      return channels;
    },
    channel: (root, { id }) => {
      return channels.find(channel => channel.id == id);
    },
  },
  Mutation: {
    addChannel: (root, args) => {
      const newChannel = { id: nextId++, name: args.name };
      channels.push(newChannel);
      return newChannel;
    },
  },
};

For now, we declare an in memory array to store our data. This array can be replaced by a real database in a real-world application.

The resolver function provide two sections, the Query and Mutation parts. Resolver functions take three optional arguments, root, args and context. The args part is the important part that caries variables/query strings from the client. Notice the functions used are the same as the functions declared in the schema. Lets focus on a single query resolver that returns a single channel depending on the id provided.

  channel: (root, { id }) => {
      return channels.find(channel => channel.id === id);
    },

The {id} part is the args that takes advantage of es6 object destructuring, otherwise the id could be accessed using args.id if args was used as the second argument.

Connecting to Express

After designing a schema and resolver functions, we need to hook it up to a server. Apollo provides implementations for different servers such as Koa, hapi, Express and restify. We are going to use Express.

import express from 'express';
import cors from 'cors';
import {
  graphqlExpress,
  graphiqlExpress,
} from 'graphql-server-express';
import bodyParser from 'body-parser';

import { schema } from './src/schema';

const PORT = 7700;
const server = express();
server.use('*', cors({ origin: 'http://localhost:8000' })); //Restrict the client-origin for security reasons.

server.use('/graphql', bodyParser.json(), graphqlExpress({
  schema 
}));

server.use('/graphiql', graphiqlExpress({
  endpointURL: '/graphql'
}));

server.listen(PORT, () =>
  console.log(`GraphQL Server is now running on http://localhost:${PORT}`)
);

Explanation Lets go through the above code and analyze what each piece does. We are utilizing Apollos GraphQL Server instead of the express-server because of the following features.

  • GraphQL Server has a simpler interface and allows fewer ways of sending queries, which makes it a bit easier to reason about what’s going on.
  • GraphQL Server serves GraphiQL on a separate route, giving you more flexibility to decide when and how to serve it.
  • GraphQL Server supports query batching which can help reduce load on your server.
  • GraphQL Server has built-in support for persisted queries, which can make your app faster and your server more secure.

In the above code, the imports make available the required libraries and the schema. We the define our endpoints and pass in the schema.

server.use('/graphql', bodyParser.json(), graphqlExpress({
  schema 
}));

For every request to β€˜/graphql’, Apollo will run through the entire query/mutation processing chain and return the result depending on the query/mutation from the client.

For development and testing purposes, we are provided with GraphiQL, a graphical interactive in-browser GraphQL IDE that presents a React component responsible for rendering the UI, which should be provided with a function for fetching from GraphQL and can be accessed through the following endpoint.

server.use('/graphiql', graphiqlExpress({
  endpointURL: '/graphql'
}));

Visiting /graphiql in our browser gives us the following interface where we can experiment with queries and mutations. The figure demonstrates how to fetch all channels from our server.

We could similarly carry out a mutation and request for the name only as the result:

Conclusion

GraphQL provides a different view to designing of API's which is fast and more flexible compared to REST. Apollo provides better ways of implementation both on the server and client side. Clients determine what the want and avoid unnecessary data reducing the size of requests and amount of operations required to normalize data from the server.

Next up we will explore the use of Apollo on the client in a React application in comparison to using Redux and try to answer the question by Dan Abramov, the creator of Redux, GraphQL/Relay: The End of Redux? ... Perhaps but will be using Apollo in place of Relay.

John Kariuki

20 posts

Software developer at Andela. Proficient in PHP with Laravel and Codeigniter.

Conversant with MEAN(MongoDB, Express.js, AngularJS, Node.js) and currently learning Python and Go.

Avid blog reader and fascinated by drones.

I play basketball, swim and jog in my free time.