Home Blog GraphQL: Centralize existing REST API endpoints for easier development

Golang with GraphQL

GraphQL: Centralize existing REST API endpoints for easier development

API gateways are great for development teams because they expose the data you need for all kinds of different purposes in a central location. There are a few great REST API gateways out there, like KrakenD, but what if you wanted to go in a different direction and choose GraphQL for your API infrastructure? Well, that works out perfectly, as it's one of the goals of GraphQL: Abstracting many different services into a single place and allowing the developers very fine-grained control over the data they need.

In this post, we're going to look over a GraphQL implementation, which keeps the previous sentence in mind: Abstracting existing REST API Endpoints into a fast GraphQL server. To build the GraphQL server, we're going to use Golang: It's fast, it's memory efficient, and provides just enough tools, but not too many. The GraphQL package we'll use is github.com/graphql-go/graphql. This package is very closely aligned with the JavaScript implementation graphql-js. This makes it a perfect candidate because you'll be able to follow JavaScript tutorials and be able to port this to Go.

The entry point

To show how you can abstract an existing REST API Endpoint in GraphQL, we're going to need an example project. I've created an example project at github.com/roelofjan-elsinga/graphql-rest-abstraction. You can use this to follow along in this post, as I will go over different parts of the GraphQL server and explain what's going on.

The entry point of our GraphQL server is main.go. Here we specify two resources in our GraphQL server: users and user.

We intend to use a dummy REST API service to fetch JSON data for all users and also a single user. The "users" resources will be used to fetch all users at https://jsonplaceholder.typicode.com/users, while the "user" resource will be used to fetch a single user by ID from https://jsonplaceholder.typicode.com/users/1 or any other user available to us.

Fetching all users

Now that we have a REST API we can use, we can create a resource to be able to fetch this data through a GraphQL resource. You can find this resource in queries/users.go:

Here you'll find a method "fetchUsers", where we call the REST API endpoint and convert the data into a Go struct, which is located in models/user.go. Our field "Users", will return the User slice from "fetchUsers".

In the "users" field declaration we specified the type we expect to receive from this GraphQL resource: graphql.NewList(userObject). We told GraphQL we're returning multiple users. The userObject is one of our GraphQL resources and you can view it in full here. It's too much code to inline here, so I've linked it up to the exact line you need in the source code. The userObject itself also contains fields and nested objects (address and company). These nested objects are linked to the exact line as well. As you can see, objects can be nested within nested objects.

Now that we've specified all fields and we can retrieve data from the REST API, it's time to give our new GraphQL resource a try. Follow the setup steps (there are only 4, and they're easy) and try to execute the following GraphQL query:

You should now see all of your users appear in the response, but only the fields we've specified in our query:

I've redacted the rest of the users to not make this snippet too long. As you can see, only the requested fields we're returned, as we expect from GraphQL.

Fetching a single user

Now that we've seen we can retrieve all users, we'll also go into retrieving a single user. The userObject is the same as we've looked at before, so I won't go over that again, but the field declaration for "user" has changed a little bit compared to "users" and so has the query. Let's look a the field declaration first. It's located at queries/user.go and looks like this:

There are three main differences:

  1. The type we expect is now userObject instead of graphql.NewList(userObject). We only expect 1 user.
  2. Our field declaration has an Args key. We use this to tell the server that we need a user ID for this query and that it cannot be empty: graphql.NewNonNull(graphql.Int)
  3. We pass the user ID to the fetchSingleUser method and append it to the REST endpoint

I mentioned that the GraphQL query now has changed as well, so let's look at what it looks like:

This query needs a user_id to be submitted (of type Int!), so we can do that using {"user_id": 1}, or whichever user_id you want to retrieve from the API endpoint.

This query results in the following response:

As you can see, we now only have the user with ID of 1.

Conclusion

This guide has shown you how to can create an API Gateway using GraphQL, to create an abstraction layer in front of your existing REST API endpoints. There are a few things missing, like Authentication, a DataLoader for efficient data fetching, but this is a simple example to show how this works. Using this method, you can piece by piece expand your API Gateway in GraphQL and Go to cover your entire list of REST API endpoints, without disturbing your existing customers. Your existing customers will still be able to fetch data from your REST API, but over time you can help them migrate to your easy-to-use GraphQL API Gateway.

Posted on: August 26th, 2020

Stay up-to-date on my blog posts