A comparison of GraphQL and REST

Okay so that poor attempt of a description of GraphQL will probably still not make any sense, so lets run through some code examples and spin up our own little API to compare the two. I have created a small Node application which you can fork from GitHub. In this app we will recreate the examples shown in the above images. All of which closely follow the brilliant introduction guide by the team at howtographql.com.

First thing we shall do is create and spin up a local mongoDB server. After that, using mongoose define a model for our User data.

I have also created a simple Database manager class which can start and stop the local MongoDB server and insert a bunch of dummy data for us to run queries against.

Next we shall create a very simple REST API using express which will have roughly the same endpoints as shown in the images above. Each will make a call to database to get a bunch of mocked data.

Finally lets create our GraphQL endpoint and schema. For this we will be done using express-graphql which will serve up our GraphQL API over HTTP and to build and define our schema we will be using graphql-tools. There are many other ways to represent your GraphQL schema but for this example I wanted to try and stay as close to GraphQL SDL model as possible. I intended to separate out the type definitions from the resolver functions, to hopefully make it easier to understand. For other ways take a look here.

First we create our type definitions, which layout the structure of our data. We shall map this to the mongoose model we defined earlier. The first two type definitions here are specials types. Every GraphQL service must have a query type (mutation is not required by default). These types are the same as a regular object type, but they are unique because they define the entry point of every GraphQL query.

So with the ‘Query’ type we will define a user function which will take in a string and return a user object, the ‘!’ denotes that what we return cannot be a null object it must be a object of the ‘User’ type which we define later.

user(name: String!): User!

The other types are plain object types (they are not special to GraphQL), which within them use a series of supported scalar types, these types represent the actual type of the data (String, Int, Date). So here we want to create types that are based upon the structure of the data we store in mongo and will return to the client. The ‘User’, ‘Post’ and ‘Comments’ type definitions will resemble that of our mongoose schema definitions.

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

See how we have made the above GraphQL type definition for the ‘User’ object to resemble the below ‘User’ mongoose schema.

let userSchema = new mongoose.Schema({
name: { type: String },
age: { type: Number },
posts: [postSchema],
followers: [{type: mongoose.Schema.Types.ObjectId, ref: 'User'}] });

GraphQL comes with the following types:

  • Int: A signed 32‐bit integer.
  • Float: A signed double-precision floating-point value.
  • String: A UTF‐8 character sequence.
  • Boolean: true or false.
  • ID: The ID scalar type represents a unique identifier, often used to refetch an object or as the key for a cache. The ID type is serialized in the same way as a String; however, defining it as an ID signifies that it is not intended to be human‐readable.

GraphQL also allows you to build and create your own scalar types, so while they support only basic scalar types out of the box you can rather easily construct your own type, for more on GraphQL types see here.

So with the formal type definitions of our Query and Mutation methods written, the next step is to use these definitions to build the corresponding function/resolver which will be called to get or update data.

Each resolver can take in four arguments:

  • parent: The previous object, which for a field on the root Query type is often not used.
  • args: Is your inputs which you register in your type definitions and the arguments provided to the field in the GraphQL query.
  • context: A value which is provided to every resolver and holds important contextual information like the currently logged in user, or access to a database.
  • info: A value which holds field-specific information relevant to the current query as well as the schema details

In this case we will be passing in our database connection in the context. The arguments that we deconstruct are the same as what we previously defined in our type definitions for these methods. We also defined which arguments are mandatory and which are optional by using the ‘!’ symbol, those with the symbol are mandatory arguments for the resolver.

So to go back to our ‘user’ type definition from our ‘Query’ type.

user(name: String!): User!

We have defined that this ‘user’ query takes in one argument called name which must be a non-null string. Now we know that our ‘args’ parameter to our resolver will be an object of the following shape.

args: {
name: 'John'
}

All we do then with each of our resolvers is take in the given arguments and use them to execute a query against our mongoose model, to either get or update a data record.

And that’s it ! I guess I have not gone into too much individual detail about all the components of a GraphQL schema, but that is because there are so many other great descriptions and tutorials out there and I wanted to encourage people to go and run the code for themselves and use it as a base to learn from and improve upon. One main point to note here though, is not only how simple it is to create a type definitions for even a nested schema’s, but how little code is needed to make a query method which has more versatility than that of its REST counterparts.

For details on how to install and run the application see the README file on the GitHub project.