DEV Community

Aaron Powell for Microsoft Azure

Posted on • Originally published at aaron-powell.com on

GraphQL on Azure: Part 10 - Synthetic GraphQL Custom Responses

Continuing on from the last post in which we used Azure API Management’s (APIM) Synthetic GraphQL feature to create a GraphQL endpoint for my blog, I wanted to explore how to add a completely new field to our type - Related Posts.

Using the schema editor in APIM I added a new field to the Post type of related(tag: String): [Post!], so our type now looks like this:

type Post {
  id: ID!
  title: String!
  url: Url!
  date: Date
  tags: [String!]!
  description: String
  content: String!
  related(tag: String): [Post!]
}

Enter fullscreen mode Exit fullscreen mode

The way this field resolver will work is that if you provide a tag argument to related then it’ll return posts that also have that tag (while first validating that the tag is a tag of the Post), and if you don’t provide a tag argument, it’ll return all posts that have the same tags as the current Post.

Aside: I have updated the /api/tag endpoint that if you provide a comma-separated string, it’ll split those and return posts that match all those tags as it previously only supported a single tag.

Building a resolver

As this is an entirely fabricated field, we’re going to have to make a custom resolver in APIM using the set-graphql-resolver policy. The resolver is going to need two pieces of data, the tags of the current Post and the tag argument provided. As we learnt in the last post, we can get the arguments off the GraphQL request context as context.Request.Body.As<JObject>(true)["arguments"], but what about the Post?

In GraphQL, the resolver that’s being executed has access to the parent in the graph, and in our case the parent of related is the Post, and we can access that by context.ParentResult.

With that setup, we can write our resolver like so:

<set-graphql-resolver parent-type="Post" field="related">
    <http-data-source>
        <http-request>
            <set-method>GET</set-method>
            <set-url>@{
                var postTags = context.ParentResult.AsJObject()["tags"].ToObject<string[]>().ToList();
                var requestedTag = context.Request.Body.As<JObject>(true)["arguments"]["tag"].ToString();

                if (!string.IsNullOrEmpty(requestedTag)) {
                    if (postTags.IndexOf(requestedTag) < 0) {
                        return null;
                    }

                    return $"https://www.aaron-powell.com/api/tag/{requestedTag}";
                }

                return $"https://www.aaron-powell.com/api/tag/{string.Join(",", postTags)}";
            }</set-url>
        </http-request>
    </http-data-source>
</set-graphql-resolver>

Enter fullscreen mode Exit fullscreen mode

Notice that this time the parent-type is Post not Query, and we have a slightly more complex bit of C# code that generates the URL we’ll call, applying the logic that was stated above.

Let’s fire off the request and see what we get back:

query {
  post(id: "2022-08-16-graphql-on-azure-part-9-rest-to-graphql") {
    title
    tags
    related {
      title
      tags
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Response from the GraphQL request for title and tags, then related posts with title and tags

Great, it’s worked as expected… except we ended up with the post that we specified the ID of in the related posts. While that might be technically true that it’s related to itself, it’s not really what we’re expecting.

Cleaning our results

We’re going to want to do something that removes the current post from its related posts, and to do that we’re going to need to either make our REST API aware of the current Post and filter it out, or make our resolver smarter.

Going and rewriting the backend API doesn’t seem like the logical choice, after all, the point of Synthetic GraphQL is that we’re exposing non-graph data as a graph, so we probably don’t want to rework our API to be more “GraphQL ready”. Instead, we can do some post-processing in the data before sending it to the client, using the http-response part of our policy and defining a set-body transformation policy.

With set-body, we need to provide a template to execute, and this can be a Liquid template or C#. Since I’m not familiar with Liquid, but I am with C#, we’re going to stick with that. This template is going to need to get the id of the current post (which is the parent of the resolver), then iterate through all the posts from the /tags call, and remove the current post from the result set.

<http-response>
    <set-body>@{
        var parentId = context.ParentResult.AsJObject()["id"].ToString();
        var posts = context.Response.Body.As<JArray>();

        var response = new JArray();

        foreach (var post in posts) {
            if (post["id"].ToObject<string>() != parentId) {
                response.Add(post);
            }
        }

        return response.ToString();
    }</set-body>
</http-response>
Enter fullscreen mode Exit fullscreen mode

What we see here is that we used the context.ParentResult to find the id, then parsed the current response as a JArray (since we know that the REST call returned a JSON array), then using a foreach loop, we check the posts and create a new JArray containing the cleaned result set, which we finally return as a string.

This makes our whole resolver look like this:

<set-graphql-resolver parent-type="Post" field="related">
    <http-data-source>
        <http-request>
            <set-method>GET</set-method>
            <set-url>@{
                var postTags = context.ParentResult.AsJObject()["tags"].ToObject<string[]>().ToList();
                var requestedTag = context.Request.Body.As<JObject>(true)["arguments"]["tag"].ToString();

                if (!string.IsNullOrEmpty(requestedTag)) {
                    if (postTags.IndexOf(requestedTag) < 0) {
                        return null;
                    }

                    return $"https://www.aaron-powell.com/api/tag/{requestedTag}";
                }

                return $"https://www.aaron-powell.com/api/tag/{string.Join(",", postTags)}";
            }</set-url>
        </http-request>
        <http-response>
            <set-body>@{
                var parentId = context.ParentResult.AsJObject()["id"].ToString();
                var posts = context.Response.Body.As<JArray>();

                var response = new JArray();

                foreach (var post in posts) {
                    if (post["id"].ToObject<string>() != parentId) {
                        response.Add(post);
                    }
                }

                return response.ToString();
            }</set-body>
        </http-response>
    </http-data-source>
</set-graphql-resolver>
Enter fullscreen mode Exit fullscreen mode

Let’s make the GraphQL call again:

Postman request showing the right results

Fantastic, we’re now only getting the data that we expect.

Summary

This post builds on the last one in how to use Synthetic GraphQL to create a GraphQL endpoint from a non-GraphQL backend, but we took it one step further and created a field on our GraphQL type that doesn’t exist in our original backend model. And this is what makes Synthetic GraphQL really shine, that we can take our backend and model it in the way that makes the most sense for consumers of it in a graph design.

Yes, it might not be as optimised as if you were writing a true GraphQL server, given that with this particular example doesn’t optimise the sub-resolver calls, but that’s something for a future post. 😉

Top comments (0)