The following question was recently posted on GraphQL Discord's #tech-help channel:

Hey all. Can I return a boolean in place of an id? For example:

followed_by_pk(followed_id: $followedId, user_id: $userId) {
  result: !!id
}

I just need a true or false to determine if there's a result

This is an interesting problem to solve, so in this article I'll explore what alternatives we have in GraphQL to tackle it.

What is the problem?

In the proposed example, users can follow, and be followed by, other users. As it was modeled in the database, the "user follows user" relationships are stored in a table, with each row containing three values:

  • Relationship ID (which is the primary key)
  • Followed user ID
  • Following user ID

The root field followed_by_pk receives the two user IDs, and finds if there is a relationship between them. If there is, this field returns an entity of type FollowedByRelationship, containing the IDs as properties. If there is none, it returns null:

type Query {
  followed_by_pk(followed_id: ID!, user_id: ID!): FollowedByRelationship
}

type FollowedByRelationship {
  id: ID!
  followed: User!
  following: User!
}

In order to explore the different solutions more thoroughly, I'll initially have field followed_by_pk return the ID of the corresponding relationship (instead of an object), or null if there is none:

type Query {
  followed_by_pk(followed_id: ID!, user_id: ID!): ID
}

Now, the developer is only interested in finding out if the relationship exists or not, and has no use for the actual relationship ID. In other words, he expects the field to be resolved to a Boolean with either values true or false, instead of an Int or null.

He asks if it's possible to prepend the field with !!, as done in JavaScript to convert a value to boolean. But no, this is not possible, because GraphQL does not have a ! operator to prepend to fields (there is a proposal to append ! to the field, but it's for a different purpose).

Let's explore the different solutions.

Converting the type on the client

The first solution involves no changes on the GraphQL schema and query:

{
  followed_by_pk(followed_id: $followedId, user_id: $userId) {
    result: id
  }
}

Then, we can use the value of type ID to represent a Boolean value:

  • If an ID is returned, there is a relationship and the integer/string value represents true
  • Otherwise there is none and the null value represents false

However, this will produce an error in strongly typed clients (as when using TypeScript), since we cannot assign an integer or string value to a variable declared as boolean. To solve it, after executing the GraphQL query, the client will need to convert the result from Int to Boolean:

// `data.result` is int/string, `result` is boolean
const result = !! data.result;

This method is the simplest one, but has a few disadvantages:

  • Extra logic is required in the client
  • If multiple clients consume the API (possibly in different languages), this extra logic will be replicated across them, which avoids DRY (Don't Repeat Yourself) and increases the likelihood of bugs
  • We can't directly use the types produced by codegen from the GraphQL schema

Duplicating the field

The next solution is not to use field followed_by_pk, which returns an ID, but a new field is_followed_by which produces the response as a Boolean:

type Query {
  followed_by_pk(followed_id: ID!, user_id: ID!): ID

  # Add a new field
  is_followed_by(followed_id: ID!, user_id: ID!): Boolean!
}

This solution works perfectly. However, by adding yet another field, the GraphQL schema will become a bit less elegant, and we need to maintain extra code in the resolvers. If this duplication of fields happens repeatedly, we may eventually end up bloating the GraphQL schema.

Organizing the data under a new entity

With the previous solution, the root Query type may end-up containing plenty of additional fields, becoming unruly. To avoid this, we can collect all related fields and place them as properties under a custom Payload type, including field exists of type Boolean:

type Query {
  followed_by(followed_id: ID!, user_id: ID!): FollowedByPayload!
}

type FollowedByPayload {
  id: ID
  exists: Boolean!
}

Please notice how this schema is slightly different to the original one:

  • Original schema: field followed_by_pk, of type FollowedByRelationship, is nullable
  • This schema: field followed_by, of type FollowedByPayload, is not nullable

Because the field is non-null, we can always query its property exists, which has the required type Boolean, to find out if the relationship exists or not.

This method offers a neat way of adding as many properties as we need. For instance, we can also retrieve the User entities, or the date when the relationship was created:

type FollowedByPayload {
  id: ID
  exists: Boolean!

  # Can add yet more properties
  followed: User
  following: User
  created: Date
}

This solution may be the most appropriate one, in that it allows to retrieve a property as ID or Boolean, which solves the challenge, while making the GraphQL schema a bit tidier.

Changing the type via a directive (don't do it!)

We might be tempted to use a query-type directive @isNotEmpty, which would change the value of the field from the original ID to true, and from null to false:

directive @isNotEmpty on FIELD

query {
  followed_by_pk(followed_id: $followedId, user_id: $userId) {
    result: id @isNotEmpty
  }
}

The beauty of this directive is that it would work anywhere, on any field. Then, there would be no need to add multiple extra fields to the GraphQL schema whenever this problem arises; a single directive can take care of them all.

Unfortunately, having the directive change the type of the result will produce errors with strongly-typed clients, because field id is expected to return a value of type ID, not Boolean.

Hence, we can't use this solution with strongly-typed clients. Indeed, this solution should be shunned upon: query-type directives modifying the type or value of the field in the response are not fully supported by the GraphQL spec, so use them carefully, and only if you really have to.

Nesting multiple queries

A logical solution to the problem of converting from ID to Boolean is to create a field isNotEmpty that takes an ID as input, and produces a Boolean as output:

type Query {
  isNotEmpty(id: ID): Boolean! 
}

So now we have a new challenge to solve: how can we have the value of field followed_by_pk be an input to field isNotEmpty? When using StepZen, we can sequence a series of queries to produce a single result, via a custom directive @sequence. Otherwise, there is not straightforward way in GraphQL to do this. (Some time ago I proposed the composable fields feature, but it has been rejected).

A generic way to solve it (which has been proposed for the spec) is by using an @export directive, having a first query export the value to some dynamic variable, and a second query inject the dynamic variable into the target field:

query ExportResult {
  followed_by_pk(followed_id: $followedId, user_id: $userId) {
    id @export(as: "followedByPkID")
  }
}

query ImportResult($followedByPkID: ID) {
  result: isNotEmpty(id: $followedByPkID)
}

This solution works, but it just looks so bloated. Couldn't GraphQL natively offer a similar feature in a more elegant manner?

Using field references

Ok, there is no "field references" feature in the GraphQL spec, so right now this is science fiction. But since GraphQL could perfectly support it, I'll demonstrate how this hypothetical new feature could work.

Let's imagine that a field could reference the value of another field from the same entity, via a new syntax operator &. For instance, in the query below, &me references the value of field with alias me, queried immediately above it:

{
  me: loggedInUserID
  user(id: &me) {
    name
  }
}

This feature would enable having the value of a field be input into another field. In the query above, if loggedInUserID returns value 3, then the subsequent field will be executing user(id: 3).

By repeating this operation multiple times, we can also chain field values to be used as inputs:

{
  me: loggedInEmployeeID
  myBoss: bossID(employeeID: &me)
  user(id: &myBoss) {
    name
  }
}

For the sake of simplicity, the & can only reference the value of a field queried on that same entity. However, if paired with the flat chain syntax proposal (that is, if it ever becomes accepted for the GraphQL spec) and adding a link back to the root Query in the different types, we can then have entities reference values from other entities too:

{
  posts {
    generalDateFormat: query.settings.dateFormat
    date(format: &generalDateFormat)
  }
}

If "field references" ever became a reality, we could then provide field isNotEmpty to convert field followed_by_pk from ID to Boolean (as explained in the section above). This solution respects GraphQL's strong typing (unlike the @isNotEmpty directive) and it's way simpler than exporting/importing values across queries (via @export):

query {
  followed_by_pk(followed_id: $followedId, user_id: $userId) {
    id
    result: isNotEmpty(&id)
  }
}

Should GraphQL have this feature? Even though we can do the same via @export, and the GraphQL spec favors no change, I think it could be justified. Should I submit a proposal for the spec, to have it debated there? If you think so, please send me a DM and let me know.

Conclusion

GraphQL is easy to use, but it could still be easier. As we've just seen in this article, for the simple task of converting a field value from integer to boolean, which in JavaScript can be achieved just by prepending !! to the variable, we may need to do one of the following:

  • Produce extra logic in the client
  • Bloat the schema or the query
  • Use directives that illegally modify the type
  • Use a non-existing feature

Nevertheless, even if the chosen solution is not 100% ideal, it works. And as the GraphQL spec keeps being improved, these kinks are being ironed out.