A GraphQL directive is one of the most powerful tools to customize and add new functionality to the GraphQL API. It can support many use cases such as access control, input validation, caching, etc.
Like queries and mutation, a GraphQL directive is defined in GraphQL Schema Definition Language (SDL) and it can be used to enhance the behavior of either schema or operation.
In this article, we’ll understand GraphQL directives with code examples in Spring for GraphQL.
Let’s get started!
Want to know more about GraphQL? You can check my other posts:
Getting started with Spring Boot GraphQL service - Techdozo
Spring for GraphQL, which recently released version 1.0, provides a higher level abstraction for building Spring Boot…
Spring for GraphQL : @SchemaMapping and @QueryMapping
In the last article Getting started with Spring Boot GraphQL service, we discussed features of GraphQL and its…
Spring for GraphQL: How to solve the N+1 Problem? - Techdozo
GraphQL promises to solve some major shortcomings associated with REST API, namely over-fetching and under-fetching…
Spring for GraphQL Mutation
In earlier articles, we talked about GraphQL queries. But, GraphQL is not just about queries. Like any other API…
The working code example of this article is listed on GitHub. To run the example, clone the repository, and import graphql-spring-directive as a project in your favorite IDE as a
You can find more information in README.md of the repository.
What are Directives in GraphQL?
A GraphQL directive is a way to add an extra behavior by annotating parts of the GraphQL schema. We can define a directive starting with @ character followed by the name, argument (optional), and execution location.
GraphQL specification defines directive as
Descriptionopt directive @ Name ArgumentsDefinitionopt on DirectiveLocations
For example, a built-in directive
@deprecated is defined as:
reason: String = "No longer supported"
) on FIELD_DEFINITION | ENUM_VALUE
- The name of the directive is
@deprecateddirective takes an optional argument
reasonwith the default value "No longer supported".
- And, this directive can be applied to a field definition and enum values.
As the name suggests, using the
@deprecated directive we can mark a certain field of the API as deprecated.
For example, if you decide to introduce a more descriptive field called
bookName in place of the old
name field then you can use
@deprecated directive to mark
name as deprecated as:
GraphQL Directive Type
Based on where directives are applied, we can categorize directives as Schema and Operation directives.
A schema directive is applied to the schema of GraphQL (specified as TypeSystemDirectiveLocation in GraphQL specification). One example of a schema directive is
@deprectaed, which allows us to annotate an API field as deprecated.
A schema directive can be applied to one of the following.
An operation directive is applied to the operations (query and mutation) and therefore affects how an operation is processed by the GraphQL server. An example of a built-in operation directive is
@skip, defined as:
directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT on FIELD_DEFINITION
An operation directive can be applied to one of the following.
GraphQL built-in Directives
At the time of writing this article, GraphQL spec talks about three built-in directives — @skip, @include, and @deprecated.
You can use
@skip directive on fields, fragment spreads, and inline fragments. This directive allows for conditional exclusion during execution based on the 'if' argument.
directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
For example, to conditionally exclude the
comment field of
ratings for operation
GetBooks, you can use
$itTest is a GraphQL variable.
Variables need to be defined as arguments of the operation name before you can refer to them in the query.
In GraphiQL, you can set variable values in the variable section as shown below.
@include is an operation directive defined as :
directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
You can use
@include directive on fields, fragment spreads, and inline fragments and it allows for conditional inclusion during execution based on the 'if' argument.
@deprecated directive is a schema directive and it can be used to mark a field or enum value as deprecated.
directive @deprecated( reason: String = "No longer supported" ) on FIELD_DEFINITION | ENUM_VALUE
Directive Use Cases
The power of directive comes from the fact that the GraphQL specification doesn’t mandate any restrictions on its usage. Therefore, you are free to define your own directives to support as many use cases as you want.
Let’s look at a couple of use cases of GraphQL directives along with implementation.
Use case 1: Access Control
One of the most common use cases for any public API is to support access control and GraphQL-based public API is no different.
Let’s try to understand, how we can implement access control using directives.
Imagine the type Book has a sensitive field called
And, you want to show revenue information only to a user with a manager role.
To handle this use case, you can define a schema directive called
@auth and apply this directive to the field
When any user queries the revenue field, the service implementation can check if the user has the required role before returning a response back to the user. The role check can be done using claims of user tokens or some other mechanism. For simplicity, we can just assume that role is passed in the HTTP header in the API request.
Running in GraphiQL, with the role passed as an HTTP header:
Query without manager role:
Implementing @auth Directive
To implement any schema directive in Spring for GraphQL, we need to modify the original data fetcher by creating a custom data fetcher as a decorator. The role of the custom data fetcher is to do the authorization check before delegating calls to the original data fetcher.
For that, let’s first define a class
AuthDirective that extends
SchemaDirectiveWiring and override the
onField (as schema directive method
@auth is applied on the field) as:
Then, we define a new data fetcher
In the above code,
authDataFetcherfirst checks if the user has the required role by getting the role from the
- If the user has the required role then it calls the original data fetcher.
- If the user doesn’t have the required role then it returns
null. Thus, an unauthorized user sees a
nullvalue in the API response.
And the last step is to set
authDataFetcher as a new data fetcher for the field
revenue and parent type
In the above code, we have resolved role information from the
graphQlContext.get("role"). We can use
WebGraphQlInterceptor to set the role in the
Complete code for
Also, we need to make spring aware of the schema directives:
On application startup, GraphQL java engine calls
onFieldmethod and assigns
authDataFetcheras new Data Fetcher for the fields marked with directive
Use case 2: Input Validation
Another common use case supported by any API is to validate the user’s input and return helpful error messages. Let’s understand how GraphQL directives can be used to implement this feature.
Consider input type
What if you have business rules that say that the name, author, and publisher fields can be of minimum size 10 and max size 100?
One approach for implementing such business validation is to define validation in the
BookInput object (mostly using javax.validation) as:
The problem with the above approach is that a client will only discover such validation at runtime after it has made the request. A better approach is to define business validations in SDL, using directives, so that it becomes part of API documentation (and also discoverable by clients using introspection).
Implementing Input Validation Directive
You can implement input validation using graphql-java-extended-validation. For example, to validate the size of input fields we can use
@size on SDL (Schema Definition Language) as:
For this, first, add the dependency on
and then create
ValidationRules and wire that with
RuntimeWiring.Builder as :
Running in GraphiQL
Use case 3: Adding Functionality to the Query
Let’s try to understand how directives can be used to enhance the behavior of operation (queries and mutation) with an example.
Imagine the book catalog API always returns the price in default currency $.
What if a client wants to show the price in a different currency? How can you build an API allowing clients to dynamically request prices in other currencies?
To solve such use cases, you can define an operation directive
@currency that takes the target currency as an argument.
Then, the client can call this API by passing currency as an argument as:
Notice that the field
price @currency(currency: "INR") returns the price in the requested currency INR.
One obvious problem with the operation directive is that there is no stopping client from applying the
@currency directive on fields other than the price field.
Implementing Operation Directive
Compared to schema directives, operation directives are complicated to implement as they are supplied by the client with instructions to change the behavior of the operation.
Therefore, the only option we have is to use Data Fetcher call back and add custom behavior based on the directive.
In the default implementation, when the directive
@currency is supplied to the field price, the GraphQL engine simply ignores this directive and returns the price based on the default implementation
graphql.schema.PropertyDataFetcher(as discussed in the earlier article).
We can override this behavior by defining a Data Fetcher for the
And then add additional behavior based on the directive supplied by the client as:
In the above code,
- We read the applied directive as
- Then read the argument of the applied directive as
maybeAppliedDirective.get(0).getArgument("currency").getValue(). Here we have made the assumption then there is only one directive is applied to the field price.
- Then call
CurrencyConversionServiceby passing target currency information.
Rather than hardcoding default value in code, you can also define the default value of the currency in the directive definition as:
directive @currency( currency: String! = "$" ) on FIELD
and read the default value in the code.
Documentation is the first‐class feature of GraphQL and all GraphQL types, fields, arguments and other definitions should provide a description unless they are considered self-descriptive.
We can document directives as:
Then, a client can also fetch all directives supported from the server as:
- A GraphQL directive is a way to add an extra behavior by annotating parts of the GraphQL schema. We can define a directive starting with @ character followed by the name, argument (optional), and execution location.
- Based on where directives are applied, it can be used to either customize the behavior of operation (mutation or queries — known as Operation directive) or schema (known as Schema directive).
- There can be many use cases of directives — such as authorization, input validation, caching, etc.