A few weeks ago, this question appeared on GraphQL Reddit. The question was whether it is possible to alias nested fields like this.

In other words, is it possible to tell the GraphQL server to flatten the shape of the response? And, if so, how to do it? I thought this issue is quite interesting, so I'd like to explore if this is possible in GraphQL or not.

I have:

allMdx {
  edges {
    node {
      frontmatter {
        date(formatString: "MMMM DD, YYYY")
      }
    }
  }
}

I need frontmatter.date to be publishedAt:

allMdx {
  edges {
    node {
      publishedAt: frontmatter{date(formatString: "MMMM DD, YYYY")}
    }
  }
}

Problem is, when I do this, I end up with:

{
  "publishedAt": {
    "date": "February 06, 2021"
  }
}

Instead of (which is what I need):

{
  "publishedAt": "February 06, 2021"
}

Is it even possible to alias nested fields like this?

Flat chain syntax

There is an elegant solution for the problem, which is to use the flat chain syntax, a proposed new syntax construct that allows to print nested relationships as a flat field in the output.

The flat chain syntax is useful when we need to fetch just a single field from a nested relation, such as field shortName from programs in this query:

{
  clients {
    id
    programs {
      shortName
    }
  }
}

Extracting the shortName data in the client is verbose:

data.clients.forEach((client) => {
  const shortNames = client.programs.map((program) => program.shortName);
});

The flat chain syntax removes the nested relationship, simplifying the response, and making it easier to retrieve the values in the client. The query can be written like this:

{
  clients {
    id
    programShortNames: programs.shortName
  }
}

Now, the response to the query will be:

{
  "data": {
    "clients": [
      {
        "id": 2,
        "programShortNames": ["ABC", "DEF"]
      },
      {
        "id": 3,
        "programShortNames": ["ABC", "XYZ"]
      }
    ]
  }
}

And extracting the shortName data in the client becomes simpler:

const programShortNames = data.clients.map(
  (client) => client.programShortNames
);

Use the flat chain syntax in the original query, it becomes:

{
  allMdx {
    edges {
      node {
        publishedAt: frontmatter.date(formatString: "MMMM DD, YYYY")
      }
    }
  }
}

Unfortunately, the proposal to introduce the flat chain syntax into the GraphQL spec has not seen much progress. It was first formulated in May 2016, and hasn't received any input in the last 2 years (and counting).

Since it's not part of the spec yet, and it may never be (unfortunately its prospects do not look very promising), we need to find out if GraphQL servers out there implement the flat chain syntax as a custom feature. Duck Duck Going "flat chain syntax" doesn't show any server having implemented it.

That's a pity! This feature should, in my opinion, see the light of the day. It will make the GraphQL schema simpler, more compact, more elegant.


Can We Make it Work Without Flat Chain Syntax?

Here are some of the tricks and solutions I tried.

Using a Custom directive

Another possibility is to code a custom directive that prints the response in the expected format. We could create a directive @moveFieldToParent that removes the nested relationship, and moves the field one level up. A simpler version would be to provide a custom directive @copyToParentNode that copies the field value to its parent node, but does not remove the nested relationship. Then, this query:

{
  foo {
    bar @copyToParentNode
  }
}

...will produce this response:

{
  "data": {
    "foo": {
      "bar": "baz"
    },
    "bar": "baz"
  }
}

Using this directive in the original query:

{
  allMdx {
    edges {
      node {
        frontmatter {
          publishedAt: date(formatString: "MMMM DD, YYYY") @copyToParentNode
        }
      }
    }
  }
}

...would produce this response:

{
  "data": {
    "allMdx": {
      "edges": [
        {
          "frontmatter": {
            "publishedAt": "February 06, 2021"
          },
          "publishedAt": "February 06, 2021"
        }
      ]
    }
  }
}

Now, this directive is modifying the shape of the response, so that it does not match the shape of the query anymore, and this violates the GraphQL spec, which states in its design principles:

The request is shaped just like the data in its response.

Hence, this solution is a hack, and we'd rather avoid it unless there is no alternative.

Using GraphQL Lodash

If using a JS server, instead of coding a custom directive, we can already use GraphQL Lodash, whose goal is to modify the shape of the response to fit whatever shape we need.

Using GraphQL Lodash, the original query becomes:

{
  allMdx {
    edges {
      node {
        publishedAt: frontmatter @_(map: "date") {
          date(formatString: "MMMM DD, YYYY")
        }
      }
    }
  }
}

However, the same warning as above also stands: changing the shape of the response is forbidden by the GraphQL spec, so this library should be used with caution.

Dynamic variable declaration

Another solution is to use an @export directive to synchronize the values of variables across operations, as proposed as Dynamic variable declaration for the GraphQL spec.

With @export, we can have a first query operation export some result to a variable, and then declare a second query operation that will read this variable and print it on the expected location in the response.

Using this directive, the original query becomes:

query ExportDate {
  allMdx {
    edges {
      node {
        frontmatter {
          date(formatString: "MMMM DD, YYYY") @export(as: "_date")
        }
      }
    }
  }
}

query ImportDate($_date: String = "") {
  allMdx {
    edges {
      node {
        publishedAt: echo(value: $_date)
      }
    }
  }
}

...and then executing both queries together in the same request, it will produce this response:

{
  "data": {
    "allMdx": {
      "edges": [
        {
          "frontmatter": {
            "publishedAt": "February 06, 2021"
          },
          "publishedAt": "February 06, 2021"
        }
      ]
    }
  }
}

This solution has certain disadvantages. For one, it is certainly bloated. For another, the proposal for the spec has not been approved yet (and it may never be, unfortunately), so not all GraphQL servers support executing multiple queries in a single request. And if any of them does, they will need to slightly overrule the GraphQL spec to answer: How does the server know that it must execute all the operations in the document? The spec defines the operationName parameter (in the dictionary passed in the body of the request) to indicate which operation to execute, but this name is singular; to fully support this feature, it should be operationNames, passing an array of operation names (or some other similar solution).

However, this solution has an advantage over the previous ones: it is not modifying the shape of the response, so that it still matches the query. Then, it can already be implemented without violating the GraphQL spec in a meaningful way. For instance, the GraphQL server can use a hack that, whenever it receives operationName: __ALL, that means it must execute all the operations in the document.

When using StepZen, we can also benefit from its @sequence directive, which streamlines the process of setting multiple queries and share state across them:

frontmatterDate(format: String): Date
  @sequence(
    steps: [
      { query: "exportDate" }
      { query: "importDate" }
    ]
  )

Re-mapping the path of a field - StepZen's approach

When fetching data from an external data source, such as a REST endpoint, then we can also create a new type in our schema that contains a field which is mapped to some external deeply-nested field, thus providing a shortcut to access its value.

For instance, when using the @rest directive to retrieve data from a REST endpoint in StepZen, we can use parameter setters to define the path to the value within the REST endpoint's response.

In this query, field imperial_weight from the Breed type provides access to the value under path weight.field:

type Breed {
  id: String!
  imperial_weight: String
}

type Query {
  breedById(id: String!): Breed
    @rest(
      endpoint: "https://api.thecatapi.com/v1/breeds/search?q=$id"
      setters: [
        {
          field: "imperial_weight",
          path: "weight.imperial"
        }
      ]
    )
}

Then, our queries can fetch a flattened response:

{
  breedById(id: 1) {
    imperial_weight # Extracting value from `weight.imperial`
  }
}

Conclusion

It is rewarding to ask if something can be done with GraphQL, and attempt to find a solution to it. A few weeks ago I explored how to check if a value is empty, and today I'm exploring how to move the value of a field one level up the relationship hierarchy.

From the solutions explored in this article, some depend on some feature not yet merged into the spec, which may or may not already be implemented by GraphQL servers as a custom feature. Some other solutions already work, but may break the GraphQL specor may be bloated, and not fully spec-compatible. No solution is perfect.

Possibly the most interesting solution to the problem is the one we have not explored in this article: avoid the problem in the first place. More likely than not, printing an extra relationship in the response is no big deal; it will make the code in the client longer than it could be, so it's not ideal. However, unless the "flat chain syntax" proposal is merged into the spec (if that ever happens), "no solution" appears to be the best solution.


Editor’s note: Our thanks to Leo for another great dive into the possibilities with GraphQL. Check out the StepZen docs for more on How to Connect to a REST Service, including working with nested JSON as referenced in this blog. We’d love to hear what you’re building and answer any questions over on the Discord Community!