Converting a REST endpoint to GraphQL
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/urlparams
, which represents all of the params that the user is passing to the mutation (as specified above in theargs
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.