Converting a REST endpoint to GraphQL

Photo by Clint Adair on Unsplash

When people hear about GraphQL, I think that they aren’t sure what this new technology offers them, or exactly how they would go about changing a REST endpoint to a GraphQL endpoint. I’ve found it hard to communicate to fellow JavaScript developers why building your application to use GraphQL is a good idea. The idea of strict typing is often unfamiliar to JavaScript developers, and can be hard to wrap your head around for developers who only have experiences with dynamic programming languages.

At CrowdRiff we are in the process of converting some older REST endpoints to use GraphQL. I recently converted a particular endpoint which handled the uploading of an image to some kind of datastore. In doing this, I realized that it was a great example for how GraphQL is helpful for assisting in endpoints which do something based off of the requester’s input.

Note: This article aims to act as a light tutorial. I will assume that you have a knowledge of REST conventions, and experience working with promise chains or asynchronous code, and maybe some minor database interactions via node with an ORM (mongoose for MongoDB or sequelize for Postgres).

The Original REST Endpoint

Most endpoints have specific ways they want to receive information requiring the data to look a certain way and have certain types ( number or string fields on specific keys of an object, for example). Since we should probably validate an incoming request and make sure we’re getting the right data from the requester, the endpoint might look like …

We have an endpoint which takes data from the body of a request, and saves it in our database under the Record model. This code is perfectly fine, and I wouldn’t say that anything is bad or inefficient about it. As the request grows/changes it will require updating the verifyBody function.

In this example, the service which we are sending the data to is strict about what that data needs to look like. In order to get the correct information from our requester, it would be helpful to them if we responded with communicative errors. It is common to process and validate every request and as such we’ve written the verifyBody function.

As we create more endpoints we will have to write a separate verifyBody function. Depending on how many key/value pairs, that is a lot of validation to maintain manually. What if we need to inspect an object literal with even more key/value pair inside of it? We will have to verify and respond with a specific error for every case of incorrect data type that comes in.

In this case GraphQL is extremely useful in helping us return errors to our requester about the information they are sending to our service. GraphQL doesn’t abstract error handling, but rather abstracts responding to the requester. This will make it easier to maintain and creates a consistent response amongst many developers who are touching the codebase.

Replacing REST Methods

In GraphQL there is no notion of GET/PUT/POST/DELETE or any other previous methods one might be familiar with when building out an API. Instead, all requests get sent through one POST endpoint and use the GraphQL HTTP Handler like so…

Above we have added a new endpoint to our server. When a request is made to our server at this endpoint, it will examine the post body and evaluate it expecting it to be a GraphQL query.

There are two methods for interacting with a GraphQL endpoint— mutation, or query. It is safe to think of queries as GET requests, and mutations as PUT/POST/DELET requests. When a request hits our new endpoint, the requester will specify in their query that the want to make use of our createRecord mutation (which we will be writing shortly).

Defining a Type

GraphQL requires making a type for every piece of data coming in, and going out. In order to respond to the requester with any data, we need to create a type for that data to adhere to.

When the requester hits this mutation, they will expect to receive a record back after creating it. Making use of GraphQL we can define exactly what data the requester is allowed to request back from communicating with this mutation. This record must look like this recordType object which we’ve defined above. We specify what the return value of a query/mutation is on the type field of the GraphQL mutation. A type has a few fields;

Name

This is a unique identifier for this type. You cannot reuse this name on another type declaration, even if it is another file or part of your application.

Description

This is a short description about the type. This is especially useful when combined with GraphQL’s self-documenting software, GraphiQL .

Fields

fields accepts an object literal where the keys represent the data being returned. Each of these accepts another object with type and description fields. The type specifies what the incoming data must be ( string for example), while description adds context for GraphiQL.

Note: A field is able accept another type that you’ve defined.

Now that we have more context about what record data looks like, let’s take a look at the endpoint itself.

The New GraphQL Endpoint

When converting the endpoint to GraphQL, we can use the power of GraphQL’s input type checking to abstract returning errors to our requester. All we need to do is specify what each argument has to look like.

There are two type of ‘endpoints’ in GraphQL; a query and a mutation. When converting this endpoint, we’re going to shape it into a mutation because the purpose is ‘mutating’ a piece of data and creating something new.

Converting our REST endpoint into a GraphQL mutation might look like this. This will be a lot to parse at first, but we will break down each part of what’s happening…

We have many fields on this GraphQL object which tells GraphQL how to react when this mutation is called.

Type

The type field will let us tell GraphQL what the structure of the data we’re returning to the requester will look like. In this case, we are saying that the returning data will look like another object we created, recordType. More on that later.

Description

A short description about the query/mutation. This is especially useful when combined with GraphQL’s self-documenting software, GraphiQL .

Args

args expects an object of keys which represent the various arguments that the query/mutation can expect to receive. Each of these accepts another object with type and description fields. The type specifies what the incoming data must be ( string for example), while description adds context for GraphiQL.

Resolve

The resolve field is set to a function which passes in 2 arguments;

  • root, which represents any information about the actual request coming into the GraphQL, and data which can be extracted from the headers/url
  • params, which represents all of the params that the user is passing to the mutation (as specified above in the args field)

The resolve is where we specify what happens when the the query/mutation is called, and where we define the value(s) that are going to be returned.

Top Level Error Handling in GraphQL

The graphQLHTT which we explored earlier wraps your GraphQL schema and gives you access to a ‘top level’ error handler for all of your queries/mutations. This means that any errors returned or thrown from a GraphQL query/mutation resolve will get thrown to top level of the handler, and can be returned to the requester in a consistent way.

Let’s add a simple error handler to our GraphQL HTTP Handler…

With the formatError field we are able to tell GraphQL how to respond to errors. In the above example we are just returning the error directly to the requester, but you can implement any system you’d like — including error logging via a 3rd party application like Sentry.

It’s important to note that you don’t have to specify a callback function for formatting errors, as graphQLHTTP will default to handle any error/rejection thrown in a resolve just like we’ve written in the above example.

Convert ’Em All (like if you want) 💁

In my experience I (so far) haven’t encountered an endpoint which would make ‘more sense’ as REST versus the benefits that GraphQL offers. In this example, we make use of GraphQL’s runtime type checking which really helps with providing users with meaningful, consistent feedback to their request.

GraphQL also wraps the entire endpoint in an error handler. This helps in communicating reliable errors to your user while preventing uncaught errors(👻) from crashing your entire app. With GraphQL, you’ll be able to send errors back to the user no matter the error.

I recommend playing around with a few endpoints in an existing application you may have and creating a GraphQL versions of them. a strength of GraphQL is that it can be integrated gradually without having to convert every single endpoint in your application at the same time. Once you begin working with GraphQL, you will be won over by it’s error handling, it’s type checking, and the ease of creating new mutations and queries. If you are looking to be convinced further on why GraphQL is great, check out my other article on why I loved learning to use GraphQL.