Article

GraphQL directives overview

Everything you need to know about GraphQL directives

By

Roy Derks

GraphQL directives are an incredibly powerful aspect of the GraphQL specification. You might not be aware of these directives or had the opportunity to use them because the GraphQL specification only requires a small number of directives for an API to be spec compliant, and, even then, they aren't necessarily ones you'd encounter on a regular basis. However, GraphQL directives provide extensibility to a GraphQL API and server in a way that makes them incredibly important, especially to tools like IBM API Connect Essentials.

In this article, we'll look at what GraphQL directives are, how they are used, and review some examples of directives.

What are GraphQL directives?

Basically, a GraphQL directive is a way to annotate a GraphQL schema to indicate that the item being annotated needs to be evaluated differently. Directives in GraphQL offer a means to change runtime execution and type validation in a GraphQL document.

GraphQL directives allow you to modify the behavior of GraphQL's execution by providing options that are not available through field arguments. For example, you can conditionally include or exclude a field using directives.

You can use built-in or custom directives when building or consuming a GraphQL API. Built-in directives are defined by the GraphQL specification, while custom directives are defined by the GraphQL service or tool that you use.

Built-in GraphQL directives

The GraphQL specification defines a set of built-in directives. Directives have a specific name and can accept values of any input type as arguments. GraphQL directives can be applied to, for example, types, fields, fragments, and operations.

The following list has all the built-in directives that are defined by the GraphQL specification:

  • @skip: This directive can be used to exclude fields from a query operation conditionally.
  • @include: Does the opposite of @skip and can be used to include fields in a query operation conditionally.
  • @deprecated: This directive can mark a field or an enum value as deprecated and can provide a reason for deprecation to the client.
  • @specifiedBy: This directive can be used to provide a URL for the specification of a custom scalar.

As GraphQL evolves, new execution capabilities might be introduced and exposed through directives. For example, the directives@defer and @stream have been announced by the GraphQL Working Group but aren't listed in the latest draft of the GraphQL specification.

GraphQL services and tools can also provide custom directives beyond those already mentioned.

Custom GraphQL directives

Custom directives can be used to extend the functionality of GraphQL and are the preferred way to add custom behavior to a GraphQL API. Different GraphQL server and client implementations are already using custom directives to add additional functionality to GraphQL.

For example, with GraphQL APIs built with API Connect Essentials, custom directives are defined to connect with your data sources, such as the @rest, @dbquery, and @graphql directives. When using API Connect Essentials to develop your GraphQL API, you can use these directives to connect to REST APIs, databases, and other GraphQL APIs. Additionally, we have defined the @materializer and @sequence directives to mix and match data from multiple data sources in a single type.

Different locations of directives

Another distinction between directives is the location they are used in. Let's examine the difference between directives applied to type system and executable locations in GraphQL.

Directives in GraphQL can be applied to different locations, where the GraphQL Specification makes a distinction between type system directive locations and executable directive locations. Directives applied to either of these locations have the same syntax; therefore, their location determines how a GraphQL implementation handles them. However, while a directive might support both type system and executable locations, a single directive typically supports only one location.

For example, both @include and @skip can be included in a query that is being passed to the GraphQL server and will affect how the query is processed, by either skipping or including a part of the query based upon a given argument. However, @deprecated would only ever be added to a schema definition.

Directives that apply to the type system

Directives that apply to the type system are used to annotate a schema, object type, or field definition written in GraphQL SDL (schema definition language) when building a GraphQL server. Both built-in and custom directives can be used in type system directive locations, and GraphQL server implementations can then use these annotations to take additional actions. Therefore, type system directive locations are also called "schema directives" as they only exist on the GraphQL schema itself.

The following locations in a GraphQL schema are valid type system directive locations:

  • SCHEMA
  • SCALAR
  • OBJECT
  • FIELD_DEFINITION
  • ARGUMENT_DEFINITION
  • INTERFACE
  • UNION
  • ENUM & ENUM_VALUE
  • INPUT_OBJECT & INPUT_FIELD_DEFINITION

Examples of directives that apply to the type system include @deprecated, a built-in directive that can mark a field as deprecated. Let's see what it looks like in a schema:

type User {
  id: ID!
  name: String! @deprecated(reason: "Use the firstName and lastName field instead")
  firstName: String!
  lastName: String!
  email: String!
}

In the example above, the @deprecated directive marks the name field as deprecated. The reason argument provides a reason for deprecation available to services that introspect the schema. The client could then, for example, warn the user that the field name is deprecated and shouldn't be used anymore.

When you would look up the User type in the documentation generated by GraphiQL, you should see warnings about using the field name:

Using a deprecated directive in GraphiQL

Another example of a directive that can be applied to the type system is @rest, a custom directive only available in API Connect Essentials implementations. The data for the User type in the example above is fetched from a REST API using the @rest directive, which is defined in the following way on a query field in a GraphQL schema:

type User {
  id: ID!
  name: String!
  email: String!
}

type Query {
  user(id: ID!): User
    @rest(url: "https://jsonplaceholder.typicode.com/users/$id")
}

When you run an operation that includes the user field, the API Connect Essentials GraphQL API fetches the data from the REST API and returns it to the client. The @rest directive is applied to a type system location because it annotates the user field in the schema. It lets you declaratively define how the data for the user field should be fetched, rather than having to write a resolver function, as you might expect from other GraphQL server implementations.

Directives that apply to execution

You can use directives that apply to execution to modify the behavior of an operation, field, or fragment in runtime execution. For example, directives that apply to execution can include or exclude fields or perform additional data processing before the response is returned.

Executable directive locations in GraphQL are:

  • QUERY
  • MUTATION
  • SUBSCRIPTION
  • FIELD
  • FRAGMENT_DEFINITION & FRAGMENT_SPREAD
  • INLINE_FRAGMENT
  • VARIABLE_DEFINITION

Similar to directives that apply to the type system directives, both built-in and custom directives can be applied to executable locations. Most built-in directives are executable, such as @skip and @include, which you can use to include or exclude fields in an operation conditionally.

Let's see what the @include directive looks like in a query operation:

query me($showName: Boolean!) {
  me {
    id
    firstName @include(if: $showName)
    lastName @include(if: $showName)
    email
  }
}

The @include directive conditionally includes the firstName and lastName fields in the response. The if argument specifies a boolean value determining whether to include the field in the response. In the example above, the if argument is set to a variable $showName, which is defined in the operation variables. The variable's value can be set to true or false to include or exclude the fields in the response.

When you pass this operation to a GraphQL API, the response should include the fields firstName and lastName in the response if the value of the variable $showName is set to true as you can see in the screenshot below:

Using built-in directives in GraphQL

Another example of an executable directive is @sort, a custom directive only available in IBM API Connect Essentials implementations.

With the @sort directive, you can sort the data returned by a field in a GraphQL operation. You can use the @sort directive on a field that returns either a list of leaf fields or a list of objects.

For example, let's say you have a products field that returns a list of tags. You can use the @sort directive to sort the tags by alphabetical order, even when the data source doesn't suppport this sorting order:

query {
  products {
    tags # ['c', 'b', 'a', null]
  }
}

The list of tags will be transformed to this when the @sort directive is used:

query {
  products {
    tags @sort # [null, 'a', 'b', 'c']
  }
}

Next to a list of leaf fields, the same @sort directive can be applied to a field that returns a list of objects. You can find more information on using the @sort directive in the documentation.

Summary and next steps

If you're new to GraphQL, seeing directives can be disorienting and confusing, especially since you won't always encounter them. In this article, you learned about built-in and custom directives and how they can be applied to different locations in GraphQL. These locations are either type system or executable locations. Directives applied to the type system directives are used in GraphQL Schema Definition Language (SDL) only. At the same time, directives applied to executable locations are used to modify the response of GraphQL in runtime execution.

GraphQL directives can be quite powerful, but you might not have occassion to use them unless you come across a system that supports them in queries or you are implementing your own GraphQL API, whether using a tool like API Connect Essentials or manually writing the code yourself. What do you think? Join our Discord to stay updated with our community.