DEV Community

Roelof Jan Elsinga
Roelof Jan Elsinga

Posted on • Originally published at roelofjanelsinga.com on

1 1

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.

schema, err := graphql.NewSchema(graphql.SchemaConfig{
Query: graphql.NewObject(graphql.ObjectConfig{
Name: "Query",
Fields: graphql.Fields{
"users": queries.UsersField,
"user": queries.UserField,
},
}),
})
view raw main.go hosted with ❤ by GitHub

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:

// UsersField is a field that returns all users
var UsersField = &graphql.Field{
Type: graphql.NewList(userObject),
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
return fetchUsers(), nil
},
}
// Fetch all users from the REST API
func fetchUsers() []models.User {
response, err := http.Get("https://jsonplaceholder.typicode.com/users")
if err != nil {
log.Fatal(err)
}
responseData, err := ioutil.ReadAll(response.Body)
if err != nil {
log.Fatal(err)
}
var users []models.User
err = json.Unmarshal(responseData, &users)
if err != nil {
log.Fatal(err)
}
return users
}
view raw users.go hosted with ❤ by GitHub

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:

query FetchUsers {
users {
id
name
username
company {
name
}
}
}
view raw users.graphql hosted with ❤ by GitHub

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

{
"data": {
"users": [
{
"company": {
"name": "Romaguera-Crona"
},
"id": 1,
"name": "Leanne Graham",
"username": "Bret"
},
{
"company": {
"name": "Deckow-Crist"
},
"id": 2,
"name": "Ervin Howell",
"username": "Antonette"
}
]
}
}
view raw response.json hosted with ❤ by GitHub

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:

// UserField is a field that returns a single user
var UserField = &graphql.Field{
Type: userObject,
Args: graphql.FieldConfigArgument{
"user_id": &graphql.ArgumentConfig{
Description: "The ID of the user you want to retrieve",
Type: graphql.NewNonNull(graphql.Int),
},
},
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
return fetchSingleUser(p.Args["user_id"].(int)), nil
},
}
// Find a single user for the given userID
func fetchSingleUser(userID int) models.User {
response, err := http.Get("https://jsonplaceholder.typicode.com/users/" + strconv.Itoa(userID))
if err != nil {
log.Fatal(err)
}
responseData, err := ioutil.ReadAll(response.Body)
if err != nil {
log.Fatal(err)
}
var user models.User
err = json.Unmarshal(responseData, &user)
if err != nil {
log.Fatal(err)
}
return user
}
view raw user.go hosted with ❤ by GitHub

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:

query FetchUser($user_id:Int!) {
user(user_id: $user_id) {
id
name
username
company {
name
}
}
}
view raw user.graphql hosted with ❤ by GitHub

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:

{
"data": {
"user": {
"company": {
"name": "Romaguera-Crona"
},
"id": 1,
"name": "Leanne Graham",
"username": "Bret"
}
}
}
view raw response.json hosted with ❤ by GitHub

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.

Billboard image

Deploy and scale your apps on AWS and GCP with a world class developer experience

Coherence makes it easy to set up and maintain cloud infrastructure. Harness the extensibility, compliance and cost efficiency of the cloud.

Learn more

Top comments (0)

Retry later
👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay