Comparing GraphQL Directives: Type System Vs. Executable Directive Locations

Comparing GraphQL Directives: Type System Vs. Executable Directive Locations

·

7 min read

Have you ever wondered why some directives have to be added to your GraphQL schemas while others you can only use in runtime? That's because there are two types of directive locations: type system directive locations and executable directive locations.

Earlier, we wrote about the different directives there are in GraphQL. This post will recapture what directives are and examine the difference between the different locations directives can be applied to.

Remind me, what is a GraphQL Directive?

Directives in GraphQL offer a means to change runtime execution and type validation in a GraphQL document. You can apply directives to many different locations in a GraphQL document, such as fields, fragments, and operations. They allow you to modify the behavior of GraphQL's execution by providing options that are not available through field arguments. For instance, you can conditionally include or exclude a field using directives, as you'll learn in this post.

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 you use. Let's look at some of the built-in directives.

Built-in 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. They 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.

Note: As GraphQL evolves, new execution capabilities may be introduced and exposed through directives. 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. We'll learn more about custom directives in the next section.

Custom 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, at StepZen, we have defined custom directives to connect with your data sources, such as the @rest, @dbquery, and @graphql directives. When using StepZen 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.

But next to built-in and custom directives, there is another distinction to be made between directives: the location they are used in. In the next section, we'll examine the difference between directives applied to type system and executable locations in GraphQL.

Type System Directive Locations vs. Executable Directive Locations

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, a directive may support both type system and executable locations, though typically, a single directive supports only one location. For example, @skip only supports executable directive locations.

Let's look at the locations where directives can be applied and see examples for both types.

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:

Deprecated field in GraphiQL

Another example of a directive that can be applied to the type system is @rest, a custom directive only available in StepZen GraphQL 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 StepZen 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'd 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:

Include fields with directives in GraphiQL

Another example of an executable directive is @sort, a custom directive only available in StepZen GraphQL 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:

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

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

In this blog post, 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.

Want to learn more about building and consuming GraphQL APIs? Follow StepZen on Twitter or join our Discord community to stay updated about our latest developments.


This post was originally published on stepzen.com. Reposted automatically with Reposted.io.