Our website is made possible by displaying online advertisements to our visitors. Please consider supporting us by disabling your ad blocker.

Maintain Data Relationships Through Resolvers With GraphQL In A Golang Application

TwitterFacebookRedditLinkedInHacker News

I recently wrote about getting started with GraphQL in a Golang application, where I discussed the creation of schemas, executing queries, and mutating data, even though it was all mock data. In this example there were queries for related data, but they were constructed in a very independent form.

We’re going to see how to query for related data, similar to what you’d find in a JOIN operation on a relational database, but using GraphQL and the Go programming language.

If you haven’t read my previous tutorial, I recommend you take a moment to familiarize yourself with it. We’re going to be using the same example of musical albums and the songs they contain as well as a lot of the code.

In the previous example, I wrote a GraphQL query like the following:

{
    album(id: "ts-fearless") {
        title
    }
    songs(album: "ts-fearless") {
        title
    }
}

The above query would get the album title for a particular id as well as all song titles for a particular album id. There isn’t anything wrong with using the above approach, but ideally you wouldn’t want to pass an id around more than once, if at all.

If you’ll remember our previous example, the GraphQL model for any particular song included an album. In a realistic example, we’d probably want to use that to our advantage and construct a query like the following:

{
    songs(album: "ts-fearless") {
        title,
        album {
            title
        }
    }
}

There are a few ways to accomplish the above, and in the end it is more your preference.

The first is by being more strict with the data that is requested from the database. To be clear, you’d create some kind of query that would get song information as well as album information for that song, rather than just passing around id values in string format.

To do this, we’d first need to update our songType GraphQL object to the following:

songType := graphql.NewObject(graphql.ObjectConfig{
    Name: "Song",
    Fields: graphql.Fields{
        "id": &graphql.Field{
            Type: graphql.String,
        },
        "album": &graphql.Field{
            Type: albumType,
        },
        "title": &graphql.Field{
            Type: graphql.String,
        },
        "duration": &graphql.Field{
            Type: graphql.String,
        },
    },
})

Notice that instead of a string for the album field, we’re now referencing the albumType which is another GraphQL object. Then inside our song query, we could do something like this:

"song": &graphql.Field{
    Type: songType,
    Args: graphql.FieldConfigArgument{
        "album": &graphql.ArgumentConfig{
            Type: graphql.NewNonNull(graphql.String),
        },
    },
    Resolve: func(params graphql.ResolveParams) (interface{}, error) {
        albumId := params.Args["album"].(string)
        var currentSong Song
        for _, song := range songs {
            if song.Album == albumId {
                currentSong = song
            }
        }
        for index, album := range albums {
            if album.ID == albumId {
                currentSong.Album = album
            }
        }
        return currentSong, nil
    },
},

Alright realistically you would have something a lot simpler than the above because in our example we’re just using some mock data. You would probably have a query that looks something like this:

SELECT *
FROM songs
JOIN album ON songs.album = album.id
WHERE songs.album = {album_id}

Then you’d massage that query response so it fits with the GraphQL schema. In using this approach, you might need to change the native Go data structure to use an Album rather than a string in the Song data structure.

Writing some intense database queries may not be such a good idea in the long run. For example, if your data model changes, you’d need to update your SQL query making it potentially more complex, slower, and return more of a payload than the end user actually wants.

This leads us to the second approach to data relationships in GraphQL.

Instead of doing complex database querying to compose your GraphQL object, why not make the GraphQL object modular and query on demand? Let’s modify the songType model to the following:

songType := graphql.NewObject(graphql.ObjectConfig{
    Name: "Song",
    Fields: graphql.Fields{
        "id": &graphql.Field{
            Type: graphql.String,
        },
        "album": &graphql.Field{
            Type: albumType,
            Resolve: func(params graphql.ResolveParams) (interface{}, error) {
                song := params.Source.(Song)
                for _, album := range albums {
                    if album.ID == song.Album {
                        return album, nil
                    }
                }
                return nil, nil
            },
        },
        "title": &graphql.Field{
            Type: graphql.String,
        },
        "duration": &graphql.Field{
            Type: graphql.String,
        },
    },
})

Notice that instead of just setting the album field as albumType, we’re also adding a Resolve function to it. Any field in a GraphQL object has access to the parent object information. Because of this, we can get the song information as a native Go data structure and use it to find the album information.

But wait a second, what does our native Go data structure for Song look like again?

type Song struct {
	ID       string `json:"id,omitempty"`
	Album    string `json:"album"`
	Title    string `json:"title"`
	Duration string `json:"duration"`
	Type     string `json:"type"`
}

In this circumstance, Album is a string again. However, in the GraphQL object it is an albumType. Your GraphQL models and your native Go models don’t need to match.

Because of this, we can use our id value for the album, do a lookup, and return the album. We’re using mock data, but it could easily be transformed into a query.

Conclusion

It was a little difficult to illustrate with mock data, but you just saw how to use resolvers to get related data in GraphQL. In my previous example, we used multiple GraphQL queries based on a parameter to get the related data that we needed. However, we were able to simplify it by either doing a complex database query that returned both the song data and the album data, or by requesting the album data on demand with the resolver function directly inside the model.

If you’d like to see a similar example of this, but with a NoSQL database, check out my tutorial titled, Data Relationships with GraphQL and NoSQL in a Golang Application.

Nic Raboy

Nic Raboy

Nic Raboy is an advocate of modern web and mobile development technologies. He has experience in C#, JavaScript, Golang and a variety of frameworks such as Angular, NativeScript, and Unity. Nic writes about his development experiences related to making web and mobile development easier to understand.