Let’s talk about GraphQL which is a query language for an API. Meaning that contrary to normal REST APIS, with GraphQL APIs you can discover and query only the data you need form a single call.

To implement a GraphQL API, let’s use Apollo server (v2) which is an open-source, spec-compliant GraphQL server. We’ll have a look at the Apollo Server getting started.

GraphQL Types

Before making a Query, we need to create the objects’ definitions we will return. You have some standard ones like String, Int or ID, but you can also create your own:

type Book {
  title: String!
  author: Author
}

type Author {
  name: String
}

We created two objects, a Book 📘 and an Author 👽 The ! means that the attribute can’t be null.

GraphQL Query

Now you will need to create the queries that will return your books and authors:

type Query {
  allBooks: [Book]
  book(title: String!): Book
}

Let’s define two of theme, one that will retrieve all the books, and the other one that will get one book based on its title. The title is passed as a parameter of type String!.

Make a query

Check the how to query from the official GraphQL website for more examples. Before we start implementing anything, let’s look at how to use our defined queries:

  • To get all the books but only their title:
query AllBooks {
  allBooks {
    title
  }
}
  • To get one book, its title and its author:
query OneBook($title: String!) {
  book(title: $title) {
    title
    author {
      name
    }
  }
}

This one takes a parameter and put it in our book query.

Expected results

Let’s use this OneBook query to call our api:

query OneBook($title: String! = "Lord of the rings") {
  book(title: $title) {
    title
    author {
      name
    }
  }
}

Let’s ask for the title and author’s name or “Lord of the rings”. The yielded result will be:

{
  "data": {
    "book": {
      "title": "The lord of the rings",
      "author": {
        "name": "J.R.R. Tolkien",
        "__typename": "Author"
      },
      "__typename": "Book"
    }
  }
}

As you can see Apollo will encapsulate our book query in a data object in this json file. The __typename is a hidden attribute that shows the type of the queried object (it can be ignored on the client side). This field is useful when building the response classes for typed languages like Kotlin or Java.

Implementation

Let’s implement this book query in Typescript, for a javascript approach, check Apollo’s docs on resolvers. For the main steps to create the application, first we will need to create an ApolloServer:

const server = new ApolloServer({
  schema: makeExecutableSchema({
    typeDefs: TypeDefs,     // Where your put your GraphQL schema
    resolvers: Resolvers,   // Where you link your queries to your logic
  })
});

What we showed in the GraphQL Types and Query will go in a schema.graphql which will be used to feed the TypeDefs. The remaining part is to do the book’s resolver:

const Resolvers = {
  Query: {
    book: async (parent: any, { title }: { title: string }) => getBook(title)
  }
}

It’s an asynchronous function that takes the title of type { title: string } (because typescript). In return, we have getBook that will fetch the book and return the correct book from a database or, let’s say:

function getBook(title: string): Promise<Book> {
  return Promise.resolve({ title: "The lord of the rings", author: { name: "J.R.R. Tolkien" } })
}

This one that only returns The lord of the rings!! 🧙

My precious… test

Without a nice test, it means nothing. To do an end-to-end test, you would need a test client to make sure that the GraphQL query works all the way. At the unit size we’ll check the handling logic instead.

Let’s see how to unit test our resolver:

test('Query one book', async () => {
    const query = `
            query OneBook {
              book(title: "The lord of the rings") {
                title
                author {
                  name
                }
              }
            }
        `;
    return graphql(schema, query).then((result: any) => {
        expect(result.data.book.author.name).toBe('J.R.R. Tolkien');
    });
});

The schema is the schema attribute used in our application to build our ApolloServer.

We can pass our parameters directly in the variable with query OneBook($title: String! = "Atlanta") which is a bit silly for only one string. This will test that the resolver returns your expected value.

As usual, find some working example in sylhare/Apollo 🛰 and see you in Space!