Comparing GraphQL Directives: Type System Vs. Executable Directive Locations
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
:
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:
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.