Declarative Data Fetching with GraphQL

Declarative Data Fetching with GraphQL The following is a guest post by Nilan Marktanner from Graph.cool. I don’t

Declarative Data Fetching with GraphQL

The following is a guest post by Nilan Marktanner from Graph.cool. I don’t know about y’all but I’ve spent plenty of time in my career dealing with REST API’s. It’s a matter of always trying to figure out what URL to hit, what data to expect back, and how you can control that data. A quick glance at GraphQL makes it seem like it simplifies things both for the creators and consumers of the API. Let’s let Nilan explain. 

When it comes to standards for designing the API architecture for web services of all kinds, REST has been the state of the art for many years already. With the widespread popularity of REST, GraphQL’s claim to replace it as the API bridge between front end and back end has received a lot of scepticism. But GraphQL is already very well established, as evidenced by Facebook’s announcement of GraphQL being production ready and GitHub’s reveal of their GitHub GraphQL API.

The Infrastructure Required for GraphQL

Like with REST, several components are needed before we can use a GraphQL API.

  • A GraphQL Client – This allows us to fetch or modify data that is stored in the back end. Plain HTTP requests do a wonderful job if you work on smaller projects, as requests and responses are both encoded in JSON.
  • A GraphQL Back End – We have to expose a GraphQL schema that describes our API as a type system. A type system consists of different methods that are called queries (to fetch data) and mutations (to modify data). Here the real work happens, as we have to implement a mapping between these methods and our data layer. For example, a query allUsers to fetch all users might be mapped to a SQL query like SELECT * FROM USERS.

In this article we will explore a possible GraphQL API for an Instagram clone highlighting some of the benefits that GraphQL brings to the table.

The GraphQL Schema

The endpoints of a REST API are often reminiscent of the actual schema of the database. Endpoints like /users or /posts that enable access to the database tables User and Post are common.

The GraphQL Schema however needs to be built as a type system in a two step process.

Step 1

Object types are composed using primitive types like String and Boolean. In our case, we will build the User and the Post object types described in IDL syntax:

type User {
  id: String!
  name: String
  posts: [Post]
}

type Post {
  id: String!
  imageUrl: String
  description: String
  author: User
}

We have a User object type that consists of the fields name of type String and posts of list type Post (denoted by [Post]) and the Post object type that consists of a imageUrl and a description field of type String and an author field of type User.

Additionally to the fields mentioned above, both object types have the id field which is a required String (denoted by String!).

Step 2

These types are then used to define actual queries, like the allUsers query that can be used to fetch all existing users in the database. You can basically expose whatever you come up with, but most popular are queries to fetch all or one item of a specific type and so called mutations to create, update and delete items of a specific type.

Tooling

The type system encoded in the GraphQL schema paves the way for powerful tools like GraphiQL for example, which is maintained by Facebook. It allows the playful exploration of a GraphQL API.

Features like auto completion (as shown in the gif) and an automatically generated documentation increase developer experience a lot and are usually enough to get started with consuming an GraphQL API. Let’s now take a closer look at some queries and mutations!

GraphQL Queries

Queries in GraphQL are declarative and hierarchical. We’ll see in a moment what this means exactly, but rapidly changing data requirements in front end applications make this one of GraphQL’s biggest selling points.

Query Basics

Let’s assume that we are only interested in the id and name of all our users. Declarative means that we declare precisely what we are interested in:

query {
  allUsers {
    id
    name
  }
}

The query response contains only fields that we just declared:

{
  "data": {
    "allUsers": [
      {
        "id": "some-id",
        "name": "Nilan"
      },
      {
        "id": "another-id",
        "name": "Chris"
      }
    ]
  }
}

We can see that the structure of the query response follows the structure of the query closely. If we want to fetch more data with a query, we just include more fields:

query {
  allUsers {
    id
    name
    posts {
      imageUrl
    }
  }
}

Here we see what hierarchical means. The query follows the relationship hierarchy of our schema and we can select the fields we are interested in for all the posts as well. The response could be:

{
  "data": {
    "allUsers": [
      {
        "id": "some-id",
        "name": "Nilan",
        "posts": [
          {
            "imageUrl": "https://unsplash.it/200/300?image=31"
          },
          {
            "imageUrl": "https://unsplash.it/200/300?image=38"
          }
        ]
      },
      {
        "id": "another-id",
        "name": "Chris",
        "posts": [
          {
            "imageUrl": "https://unsplash.it/200/300?image=99"
          }
        ]
      }
    ]
  }
}

It’s also important to note that we just changed the query without touching our GraphQL backend and it still worked! Once we thought of a solid type system and exposed that in our GraphQL schema, we can change queries on the fly in our frontends and our GraphQL server will give back the correct response just like that. That also means no more hassle with multiple endpoints or API versions as is common in REST.

Combining fields hierarchically like we did also reduces the amount of HTTP requests between the frontend and the backend. Fetching a user and all of its friends typically needs at least two requests with REST, while we only needed one.

Advanced Query Features

GraphQL queries accept query parameters that are usually used to offer advanced features like filtering or pagination. We won’t go into depth here, but let’s see a few quick examples.

Filtering is pretty flexible and powerful. For example we can only show users with the name Chris:

query {
  allUsers(filter: {name: "Chris"}) {
    id
    name
  }
}

Unsurprisingly, the query response only contains one user:

{
  "data": {
    "allUsers": [
      {
        "id": "another-id",
        "name": "Chris"
      }
    ]
  }
}

With pagination we can express how many consecutive data items we are interested in. This is especially useful if we want to build a feed with several pages similar to Google search results.

Let’s only query the first of our users. We can use the first argument to accomplish that.

query {
  allUsers(first: 1) {
    id
    name
  }
}

And as expected we only get back the first user:

{
  "data": {
    "allUsers": [
      {
        "id": "some-id",
        "name": "Nilan"
      }
    ]
  }
}

Now if we want to query the second user, we can skip the first user and fetch only the next user by combining first with skip:

query {
  allUsers(first: 1, skip: 1) {
    id
    name
  }
}

This returns the second user:

{
  "data": {
    "allUsers": [
      {
        "id": "another-id",
        "name": "Chris"
      }
    ]
  }
}

These are only some of the ways we can use query arguments. If we wanted to provide another similar functionality, like sorting the query response by a field, we would have to define a new query argument in the GraphQL schema and implement that feature in the backend accordingly.

Mutations

Mutations are the counterpart to queries. While we can fetch data with queries, mutations allow us to create new data or update or delete existing data items. The respective mutations could be called createPost, updatePost and deletePost.

The values for the fields of the new data item are supplied with query arguments. Mutations require the selection of fields that will be returned as a query response as well. A mutation to create a new post could look like this:

mutation {
  createPost(imageUrl: "https://unsplash.it/200/300?image=27", description: "#random", authorId: "some-id") {
    id
  }
}

The response will contain the newly created id:

{
  "data": {
    "createPost": {
      "id": "some-newly-generated-id"
    }
  }
}

Conclusion

In this article, we got an overview of GraphQL as an alternative to REST.

We saw that GraphQL clients are needed to connect the front end with the backend and how a GraphQL schema for an Instagram clone can look like. We learned how we can use queries and mutations to fetch and modify data and the GraphQL API allows us to select exactly the fields we need in a declarative and hierarchical way. With filtering and pagination, we glanced at two powerful concepts while GraphiQL showed off the fantastic possibilities of tools that rely on the defined type system.

If you want to learn more about GraphQL, you should take a look at learn section of the GraphQL website and Learn GraphQL which are two very accessible resources.

Relay and Apollo Client are two popular GraphQL clients for JavaScript. If you are interested in Relay head over to Learn Relay for an interactive and comprehensive introduction to Relay.

graphql-js and Sangria are two popular libraries to build a GraphQL backend in JavaScript and Scala, respectively.

To get quickly started with GraphQL, you can checkout Graphcool which allows you to graphically define your models and fields to automatically generate a GraphQL backend and provides additional features like an advanced permission system and file management out of the box.


Declarative Data Fetching with GraphQL is a post from CSS-Tricks

Source: Declarative Data Fetching with GraphQL

Pin It

Leave a Comment