GraphQL — the Better REST

Aakash Sorathiya
Geek Culture
Published in
8 min readFeb 15, 2021

This article focuses on plain GraphQL functionality.

Source: graphql.org

What is GraphQL?

GraphQL is a query language that provides a complete and understandable description of how to load data from a server. GraphQL enables declarative data fetching where a client can specify exactly what data it needs from an API.

GraphQL vs. REST

REST has been a popular way to expose data from a server. But as the applications became more complex, people started facing problems with the REST architectural style. Let’s consider an example of a Movie Review System to understand this more.

Suppose we have three resources — Movies, Ratings, and Reviews. Our requirement is to fetch the names of all the movies, their reviews, and the number of likes on each review. The API endpoints will be /v1/movies, /v1/movies/reviews/{movieId} and /v1/movies/reviews/{reviewId}.

Problems with the above scenario
Multiple Endpoints: In REST different resources are fetched using different endpoints. So developers have to literally maintain a list of these endpoints.
Over-Fetching: It means that a client downloads more information than is actually required in the app. In the above example, we have required only the name of a movie but the response will contain other information like the cast of the movie or release date which is useless for the client because it only needs the name of the movie.
Under-Fetching and n+1-requests: It means that a specific endpoint doesn’t provide enough of the required information and the client will have to make additional requests to fetch everything it needs. In the above example, we wanted to display reviews of all movies so we first fetched the movies and then their reviews by making a call to another endpoint. This caused the n+1-requests problem.

The solution to the above problems: GraphQL

GraphQL was developed to cope with the need for more flexibility and efficiency! It acts as a layer between the client and one or more data sources, receiving client requests and fetching the necessary data according to the given instructions.

Consider the above example and let’s write a GraphQL query for the given requirement.

{
movies {
movieId
name
reviews {
reviewId
likes
}
}
}

The query sent by the client specifies the shape of the data and the server responds back with the exact same shape. This solves the problem of over-fetching and under-fetching. It is not the job of the client to fetch data from different endpoints. The client just specifies the shape of the data it needs and then it is the responsibility of the server to get work done. This is how the problem of handling multiple endpoints is also solved. There is only one endpoint in GraphQL and the query is passed as a string to the server.

Another main characteristic of GraphQL is that it uses strong type system to describe data and validate queries sent by the client. GraphQL also allows gaining insightful analytics about the data that’s requested on the backend. With GraphQL, we can also do low-level performance monitoring of the requests that are processed by the server.

Core Concepts

GraphQL has three main building blocks: the schema, queries and resolvers.

Schema

The schema is a model of the data that can be fetched through the server. It defines what queries clients are allowed to make, what types of data can be fetched from the server, and what the relationships between these types are. The syntax for writing the schemas is called Schema Definition Language (SDL). Let’s consider an example of how we can use SDL to define a simple schema.

type Book {
id: Int!
title: String!
published: Date
author: Author
}
type Author {
id: Int!
name: String
books: [Book]!
}
  • Book and Author are of GraphQL Object Type, which means a type with some fields.
  • id, title, published, author are fields on the Book type. That means these are the only fields that can appear in any query that operates on Book type.
  • Int, String, Date are built-in scalar types.
  • String! means that fields is non-nullable, meaning that we will always get a value when we query this filed.
  • [Book]! represents an array of Book objects. The ! means that list in non-empty.

There are some special types within a schema:

schema {
query: Query
mutation: Mutation
subscription: Subscription
}
  • query type is used to fetch the data from the database.
  • mutation type is used to make changes to the existing data.
  • subscription type is used to have a real-time connection to the server in order to get immediately informed about important events.

Every GraphQL service has a query type and may or may not have mutation or subscription types. These types define the entry point of every GraphQL query. We will see in the next topic how to define entry points for queries.

Queries

GraphQL lets the client decide what data is actually needed by sending some information to the server, this information is called a query. Let’s take a look at an example query that a client can send to the server.

{
author {
name
}
}

Response would look like

{
"data": {
"author": [
{ "name": "William Shakespeare" },
{ "name": "Siddhartha Mukherjee" },
...
]
}
}

You can see immediately that the query has exactly the same shape as the result. This means that we always get back what we expect and the server also knows what the client is asking for.

This query will be executed if there is an entry point defined for it in a schema. Let’s define an entry point for the above query.

type Query {
author: [Author]
}

In query, fields can also refer to Objects. In that case we can make sub-selection of fields for that Object. Let’s look at an example for such case.

{
author {
name
book {
title
}
}
}

Response would look like:

{
"data": {
"author": [
{
"name": "William Shakespeare",
"books": [
{ "title": "The Tragedy of Macbeth" },
{ "title": "Measure for Measure" },
...
]
},
...
]
}
}

GraphQL also provides the ability to pass arguments to the fields. For example:

{
author(id: "101") {
name
}
}

Response would look like:

{
"data": {
"author": {
"name": "Siddhartha Mukherjee"
}
}
}

In GraphQL, every field and nested object can get its own set of arguments, making GraphQL a complete replacement for making multiple API fetches.

Up until now, we have been using a shorthand syntax where we omit both the query keyword and the query name, but it's useful to use these to make our code less ambiguous. Let’s look at an example that includes the keyword query as operation type and AuthorNameAndBooksTitle as operation name.

query AuthorNameAndBooksTitle {
author(id: "101") {
name
book {
title
}
}
}
  • The operation type is either query, mutation, or subscription and describes what type of operation you’re intending to do.
  • The operation name is a meaningful and explicit name for your operation.

In the above example we have passed the value of id inside the query string. But it is not a good idea to pass these dynamic arguments directly in the query string. Instead, GraphQL provides a better way to factor dynamic values out of the query, and pass them separately. These values are called variables. Let’s look at an example:

query AuthorNameAndBooksTitle($id: Int) {
author(id: $id) {
name
book {
title
}
}
}

This is a good practice for denoting which arguments in our query are expected to be dynamic.

Resolvers

Now that we have defined the structure of a GraphQL server — its schema, next comes the concrete implementation that determines the server’s behavior. Key components for the implementation are called resolver functions.

The sole purpose of a resolver function is to fetch the data for its field. In the GraphQL server implementation, each field corresponds to exactly one resolver function. So when the server receives the query, it will call all the functions for the fields that are specified in the query’s payload. It thus resolves the query and retrieve the correct data for each field.

GraphQL resolve functions can contain arbitrary code, which means GraphQL server can talk to any kind of backend, even other GraphQL servers. For example, the Author type can be stored in a SQL Database and the Book type can be stored in MongoDB, or even handled by a microservice.

Let’s write a resolve functions for our above schema with types Author and Book.

Query: {
book(root, args, context, info) {
return fetchBookById(args.id);
}
}
  • root argument in each resolver call is simply the result of the previous call and initial value is null if not otherwise specified.
  • args argument carries the parameters for the query.
  • context argument is provided to every resolver and holds important contextual information like access to a database.
  • info argument holds field specific information relevant to the current query and the schema.

Assuming a function fetchBookById is defined and returns a instance of Book, the resolve function enables the execution of the schema.

Here we are putting resolver on Query because we want to query for book directly on the root level. But we can also have resolvers for sub-fields such as a book’s author field:

Book: {
author(book) {
return fetchAuthorByBook(book); //hit the database
}
}

Resolvers can also be used to modify the contents of a database and in that case they are known an mutation resolvers.

Query Execution

At a high-level view the server responds to the query in three steps

  1. Parsing the query
    First, the server parses the string and turns it into Abstract Syntax Tree. If there are any syntax error the execution will be terminated.
  2. Validation
    This stage makes sure that the query is valid given the schema before the execution starts.
  3. Execution
    After being validated, a GraphQL query is executed by a GraphQL server which returns a result that mirrors the shape of the requested query.

Let’s write the execution flow for the above mentioned query for fetching the author and its book.

1: run Query.author
2: run Author.name and Author.books (for Author returned in 1)
3: run Book.title (for Book returned in 2)

Conclusion

GraphQL might seem complex at first because it is an API technology and it can be used in any context where an API is used. But as you can see, once we dive into it GraphQL is pretty easy to understand.

In most cases, building an app with GraphQL is better choice than REST since it delivers a standard for one-trip relational queries rather than multiple round trip queries, reduces the amount of code to write, is less error prone and provides built-in documentation.

GraphQL’s standardized architecture is ultimately better, cheaper and faster than the REST alternative.

References

[1] https://www.howtographql.com/
[2] https://graphql.org/learn/
[3] https://www.apollographql.com/docs/

--

--

Aakash Sorathiya
Geek Culture

A software developer with a strong passion for self-improvement.