GraphQL Tutorial
Setting up a GraphQL Interface

Ruben Taelman

Olaf Hartig

ISWC 2019, Auckland, 26/27 October 2019

Setting up
a GraphQL Interface

GraphQL Tutorial

1Ghent University – imec – IDLab, Belgium

2Department of Computer and Information Science (IDA) — Linköping University, Sweden

GraphQL interface:
API that handles GraphQL queries

In:

{
  me {
    name
  }
}

Out:

{
  "data": {
    "me": {
      "name": "Luke Skywalker"
    }
  }
}

Notes

Minimal GraphQL interface requirements

GraphQL Schemas
describe structure of data

Resolvers retrieve data for specific fields

Query Engines execute full queries

Query Execution Step 1: Parsing

GraphQL Query:

query {
  hero {
    name
    friends {
      name
    }
  }
}

AST:

Query Execution Step 2: Validation

GraphQL Schema:

type Query {
  hero: Character
}

type Character {
  name: String!
  friends: [Character]
}

Validation steps:

AST:

Query Execution Step 3: Execution

Resolvers:

{
 Query: {
  hero: () =>
    db.findAll('hero'),
 },
 Character: {
  name: (char) =>
    db.find('name', char),
  friends: (char) =>
    db.findAll('friend', char),
 }
}

AST:

Query Execution Step 3: Execution - L1

Resolvers:

{
 Query: {
   hero: () =>
     db.findAll('hero'),
  },
 Character: {
  name: (char) =>
    db.find('name', char),
  friends: (char) =>
    db.findAll('friend', char),
 }
}

AST:

[1,2]

Query Execution Step 3: Execution - L2

Resolvers:

{
 Query: {
   hero: () =>
     db.findAll('hero'),
  },
 Character: {
  name: (char) =>
     db.find('name', char),
  friends: (char) =>
    db.findAll('friend', char),
 }
}

AST:

[{name:'Luke',friends:[2]},{name:'Leia',friends:[1]}]

Query Execution Step 3: Execution - L3

Resolvers:

{
 Query: {
   hero: () =>
     db.findAll('hero'),
  },
 Character: {
  name: (char) =>
     db.find('name', char),
  friends: (char) =>
    db.findAll('friend', char),
 }
}

AST:

[{name:'Luke',friends:[...]},{name:'Leia',friends:[...]}]

GraphQL.js
is the official reference implementation

GraphQL.js example

Schema and resolvers are combined in a single datastructure.

const schema = new GraphQLSchema({
  query: new GraphQLObjectType({
    name: 'RootQueryType',
    fields: {
      hello: {
        type: GraphQLString,
        resolve() {
          return 'world';
        }
      }
    }
  })
});

const result = await graphql(schema, '{ hello }');

express-graphql
offers a GraphQL HTTP server

Express example:

app.use('/graphql', graphqlHTTP({
  schema: MyGraphQLSchema,
  graphiql: true
}));

Apollo: a fully-featured GraphQL platform

Apollo

Main features:

(+): Team plan; (++): Enterprise plan

Setting up an interface with Apollo Server

const typeDefs = gql`
  type Book {
    title: String
    author: String
  }
  type Query {
    books: [Book]
  }
`;
const resolvers = {
  Query: {
    books: () => ...,
  },
};

const server = new ApolloServer({ typeDefs, resolvers });
server.listen().then(({ url }) => {
  console.log(`🚀  Server ready at ${url}`);
});

The Apollo Server is extensible

data sources, caching, authentication, subscriptions, testing, ...

Apollo Server can abstract data fetching
with Data sources

Data sources are abstractions for data fetching from specific services.

Built-in support for caching, result deduplication, and error handling.

class MoviesAPI extends RESTDataSource {
  constructor() {
    super();
    this.baseURL = 'https://movies-api.example.com/';
  }

  async getMovie(id) {
    return this.get(`movies/${id}`);
  }
}

Data sources can be used in resolvers

Data sources must be configured in server.

Resolvers can access data sources from context.

const server = new ApolloServer({
  ...
  dataSources: () => { return { moviesAPI: new MoviesAPI() }; },
});

const resolvers = {
  Query: {
    movie: async (source, { id }, { dataSources }) => {
      return dataSources.moviesAPI.getMovie(id);
    },
  },
};

Apollo Server has built-in caching

Resources are by default cached with an in-memory LRU cache.

Alternative caches can be configured, such as Memcached or Redis

const { MemcachedCache } = require('apollo-server-cache-memcached');

const server = new ApolloServer({
  ...
  cache: new MemcachedCache(
    ['memcached-server-1', 'memcached-server-2'],
    { retries: 10, retry: 10000 }, // Options
  ),
});

Apollo Server can handle
authenticated requests

HTTP authorization headers can be placed into the context.

const server = new ApolloServer({
  ...
  context: ({ req }) => {
     const token = req.headers.authorization || '';
     const user = getUser(token);
     return { user };
   },
});

Apollo Server event subscriptions

Different publish and subscribe primitives: PubSub, WebSocket

const typeDefs = gql`
  type Subscription {
    postAdded: Post
  }
  type Post {
    author: String
    comment: String
  }`
const resolvers = {
  Subscription: {
    postAdded: {
      subscribe: () => pubsub.asyncIterator(['POST_ADDED']),
      // emit with pubsub.publish('POST_ADDED', { postAdded:args });
    },
  },
};

Apollo Server provides testing utilities

Mocks the request pipeline and HTTP server

Allows testing of schema, resolvers and datasources

it('fetches single launch', async () => {
  const { query } = createTestClient(server);
  const res = await query({ query: '...', variables: { id: 1 } });
  expect(res).toMatchSnapshot();
}

When to use Apollo over GraphQL.js?

Apollo has

(+): Team plan; (++): Enterprise plan

GraphQL Server Alternatives

Apollo is the most popular platform, used by Airbnb, Twitch, Medium, ...

In Summary

Sources