First steps into learning GraphQL

Title: First steps into learning GraphQL

Excerpt: GraphQL is a new API standard to provide an alternative to REST, it has been gaining popularity along the community and this series is meant to learn what makes it so good over REST.

Categories:

GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data. GraphQL provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need and nothing more, makes it easier to evolve APIs over time, and enables powerful developer tools.

Features

Ask for what you need, get exactly that

Send a GraphQL query to your API and get exactly what you need, nothing more and nothing less. GraphQL queries always return predictable results. Apps using GraphQL are fast and stable because they control the data they get, not the server.

You can send this query:

{
  hero {
    name
    height
  }
}

And you get:

{
  "hero": {
    "name": "Luke Skywalker",
    "height": 0.172
  }
}

Get many resources in a single request

GraphQL queries access not just the properties of one resource but also smoothly follow references between them. While typical REST APIs require loading from multiple URLs, GraphQL APIs get all the data your app needs in a single request. Apps using GraphQL can be quick even on slow mobile network connections.

{
  hero {
    name
    friends {
      name
    }
  }
}

And you get:

{
  "hero": {
    "name": "Luke Skywalker",
    "friends": [{ "name": "Obi Wan Kenobi" }, { "name": "RD-D2" }]
  }
}

Describe what’s possible with a type system

GraphQL APIs are organized in terms of types and fields, not endpoints. Access the full capabilities of your data from a single endpoint. GraphQL uses types to ensure Apps only ask for what’s possible and provide clear and helpful errors. Apps can use types to avoid writing manual parsing code.

{
  hero {
    name
    friends {
      name
      homeWorld {
        name
        climate
      }
      species {
        name
        lifespan
        origin {
          name
        }
      }
    }
  }
}

The schema would look like:

type Query {
  hero: Character
}

type Character {
  name: String
  friends: [Character]
  homeWorld: Planet
  species: Species
}

type Planet {
  name: String
  climate: String
}

type Species {
  name: String
  lifespan: Int
  origin: Planet
}

Core Concepts

Schema Definition Language (SDL)

The following is an example of SDL to define a type called Person:

type Person {
  name: String!
  age: Int!
}

Where you can define the data type (String, Int, Bool) and you can add the ! to say its required.

You can also relate two types:

type Post {
  title: String!
  author: Person!
}

And you need to relate both types by modifying Person:

type Person {
  name: String!
  age: Int!
  posts: [Post!]!
}

We created a one to many relationship between Person and Post and made posts in Person an array of Post.

Fetching Data with Queries

Instead of providing several EP so you can fetch information from them like you did with REST, now with GraphQL you have one EP.

Basic Query

Lets check a basic query:

{
  allPersons {
    name
  }
}

Where allPersons is the root field of the query and everything that follows is the payload of the query, the response would be:

{

  "allPersons": [
    { "name": "Adrian" },
    { "name": "Alberto" },
    { "name": "Carlos" }
  ]
}

If you add age to the query you get the response with the age inside each object.

Queries with Arguments

{
  allPersons(last: 2) {
    name
  }
}

This time you add a last parameter to return a specific number of persons. This is defined in the schema (we will check this later).

CRUD with Mutations

For creating:

mutation {
  createPerson(name: "Bob", age: 20) {
    name
    age
  }
}

This time the root field is createPerson, and the response to the server would look like:

"createPerson": {
  "name": "Bob",
  "age": 20
}

For updating:

mutation {
  updatePerson(id: 1) {
    id
  }
}

For delete:

mutation {
  deletePerson(id: 1) {
    id
  }
}

Realtime updates with Subscription

Instead of the typical request-response cycle from mutations and queries, you can send streams of data at real time.

subscription {
  newPerson {
    name
    age
  }
}

When a new mutation is performed that creates a new Person, the server sends the information to the client.

Defining a Schema

A Schema specifies the capacity of the API and defines how the client can request the data, its a collection of GraphQL types.

To write them you need to define the root types:

type Query { ... }
type Mutation { ... }
type Subscription { ... }

To enable the codes we wrote before we define the schema:

type Query {
  allPersons(last: Int): [Person!]!
}
type Mutation {
  createPerson(name: String!, age: Int!): Person!
}
type Subscription {
  newPerson: Person!
}

type Person {
  name: String!
  age: Int!
  posts: [Post!]!
}
type Post {
  title: String!
  author: Person!
}

Architecture

There are three cases:

  1. GraphQL server with a connected database

When query arrives to server it gets read and fetches the required information from the database, this is called resolving the query. You can use a network protocol like TCP, WebSockets, etc. You can also use the database of your choice.

  1. GraphQL layer that integrates existing systems

You can connect a legacy system, microservice and third-party API to GraphQL, this way new clients can connect to this GraphQL server to fetch the data they need, it would connect those three systems to the client.

  1. Connected database and integration of existing system

When a query reach the server it will resolve and retrieve the required data from the database or the integrated API.

Resolver Functions

At the top level of every GraphQL server is a type that represents all of the possible entry points into the GraphQL API, it’s often called the Root type or the Query type.

Lets use these schemas as examples:

type Query {
  human(id: ID!): Human
}

type Human {
  name: String
  appearsIn: [Episode]
  starships: [Starship]
}

enum Episode {
  NEWHOPE
  EMPIRE
  JEDI
}

type Starship {
  name: String
}

In the following example, our Query type provides a field called human which accepts the argument id. The resolver function for this field likely accesses a database and then constructs and returns a Human object.

// Resolver
Query: {
  human(obj, args, context, info) {
    return context.db.loadHumanByID(args.id).then(
      userData => new Human(userData)
    )
  }
}

A resolver function receives four arguments:

Asynchronous resolvers

Let’s take a closer look at what’s happening in this resolver function.

human(obj, args, context, info) {
  return context.db.loadHumanByID(args.id).then(
    userData => new Human(userData)
  )
}

The context is used to provide access to a database which is used to load the data for a user by the id provided as an argument in the GraphQL query. Since loading from a database is an asynchronous operation, this returns a Promise. When the database returns, we can construct and return a new Human object.

Notice that while the resolver function needs to be aware of Promises, the GraphQL query does not. It simply expects the human field to return something which it can then ask the name of. During execution, GraphQL will wait for Promises, Futures, and Tasks to complete before continuing and will do so with optimal concurrency.

Trivial resolvers

Now that a Human object is available, GraphQL execution can continue with the fields requested on it.

Human: {
  name(obj, args, context, info) {
    return obj.name
  }
}

A GraphQL server is powered by a type system which is used to determine what to do next. Even before the human field returns anything, GraphQL knows that the next step will be to resolve fields on the Human type since the type system tells it that the human field will return a Human.

Resolving the name in this case is very straight-forward. The name resolver function is called and the obj argument is the new Human object returned from the previous field. In this case, we expect that Human object to have a name property which we can read and return directly.

In fact, many GraphQL libraries will let you omit resolvers this simple and will just assume that if a resolver isn’t provided for a field, that a property of the same name should be read and returned.

Scalar coercion

While the name field is being resolved, the appearsIn and starships fields can be resolved concurrently. The appearsIn field could also have a trivial resolver, but let’s take a closer look:

Human: {
  appearsIn(obj) {
    return obj.appearsIn // returns [ 4, 5, 6 ]
  }
}

Notice that our type system claims appearsIn will return Enum values with known values, however this function is returning numbers! Indeed if we look up at the result we’ll see that the appropriate Enum values are being returned. What’s going on?

This is an example of scalar coercion. The type system knows what to expect and will convert the values returned by a resolver function into something that upholds the API contract. In this case, there may be an Enum defined on our server which uses numbers like 4, 5, and 6 internally, but represents them as Enum values in the GraphQL type system.

List resolvers

We’ve already seen a bit of what happens when a field returns a list of things with the appearsIn field above. It returned a list of enum values, and since that’s what the type system expected, each item in the list was coerced to the appropriate enum value. What happens when the starships field is resolved?

Human: {
  starships(obj, args, context, info) {
    return obj.starshipIDs.map(
      id => context.db.loadStarshipByID(id).then(
        shipData => new Starship(shipData)
      )
    )
  }
}

The resolver for this field is not just returning a Promise, it’s returning a list of Promises. The Human object had a list of ids of the Starships they piloted, but we need to go load all of those ids to get real Starship objects.

GraphQL will wait for all of these Promises concurrently before continuing, and when left with a list of objects, it will concurrently continue yet again to load the name field on each of these items.

Let’s take one last look at the original query to see how all these resolving functions produce a result:

// Query
{
  human(id: 1002) {
    name
    appearsIn
    starships {
      name
    }
  }
}

And for the result:

{
  "data": {
    "human": {
      "name": "Han Solo",
      "appearsIn": [
        "NEWHOPE",
        "EMPIRE",
        "JEDI"
      ],
      "starships": [
        {
          "name": "Millenium Falcon"
        },
        {
          "name": "Imperial shuttle"
        }
      ]
    }
  }
}

More concepts

Lets review once again the schema and how resolvers execute each field:

type Query {
  author(id: ID!): Author
}

type Author {
  posts: [Post]
}

type Post {
  title: String
  content: String
}

Lets check the query we’re sending:

query {
  author(id: "abc") {
    posts {
      title
      content
    }
  }
}

Every field in the query can be associated with a type:

query: Query {
  author(id: "abc"): Author {
    posts: [Post] {
      title: String
      content: String
    }
  }
}

Reusability with Fragments

They’re a collection of fields on a specific type.

If we have the following type:

type User {
  name: String!
  age: Int!
  email: String!
  street: String!
  zipcode: String!
  city: String!
}

We can turn it into a fragment:

fragment addressDetails on User {
  name
  street
  zipcode
  city
}

You can now write this as:

{
  allUsers {
    ...addressDetails
  }
}

Parametrizing Fields with Arguments

Lets consider these Schemas:

type Query {
  allUsers: [User!]!
}

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

We can pass an argument like:

type Query {
  allUsers(olderThan: Int = -1): [User!]!
}

And the query would look like:

{
  allUsers(olderThan: 30) {
    name
    age
  }
}

Naming Query Results with Aliases

We can do:

{
  first: User(id: "1") {
    name
  }
  second: User(id: "2") {
    name
  }
}

Which translates to:

{
  "first": {
    "name": "Alice"
  },
  "second": {
    "name": "Sarah"
  }
}

Variables

So far, we have been writing all of our arguments inside the query string. But in most applications, the arguments to fields will be dynamic: For example, there might be a dropdown that lets you select which Star Wars episode you are interested in, or a search field, or a set of filters.

When we start working with variables, we need to do three things:

  1. Replace the static value in the query with $variableName
  2. Declare $variableName as one of the variables accepted by the query
  3. Pass variableName: value in the separate, transport-specific (usually JSON) variables dictionary
query HeroNameAndFriends($episode: Episode) {
  hero(episode: $episode) {
    name
    friends {
      name
    }
  }
}

Were the variable is:

{
  "episode": "JEDI"
}

And we get as result:

{
  "data": {
    "hero": {
      "name": "R2-D2",
      "friends": [
        {
          "name": "Luke Skywalker"
        },
        {
          "name": "Han Solo"
        },
        {
          "name": "Leia Organa"
        }
      ]
    }
  }
}

Now, in our client code, we can simply pass a different variable rather than needing to construct an entirely new query. This is also in general a good practice for denoting which arguments in our query are expected to be dynamic - we should never be doing string interpolation to construct queries from user-supplied values.

We can also add a default variable:

query HeroNameAndFriends($episode: Episode = JEDI) {
  hero(episode: $episode) {
    name
    friends {
      name
    }
  }
}

When default values are provided for all variables, you can call the query without passing any variables. If any variables are passed as part of the variables dictionary, they will override the defaults.

Conclusion

Today we learned about the theory related to GraphQL, a very interesting alternative to REST that has many advantages but it forces the client and server to work with GraphQL which might be something traditional backend developers with experience with REST won’t accept.

If you have a role as a fullstack developer in charge of the front and back of the application, then using GraphQL is a fantastic option.

See you on the next post.

Sincerely,

Eng. Adrian Beria