Chapter 7 GraphQL: Directives

7.1 Introduction

A GraphQL schema describes directives which are used to annotate various parts of a GraphQL document as an indicator that they should be evaluated differently by a validator, executor, or client tool such as a code generator. GraphQL implementations should provide the @skip and @include directives. GraphQL implementations that support the type system definition language must provide the @deprecated directive if representing deprecated portions of the schema. Directives must only be used in the locations they are declared to belong in. In this example, a directive is defined which can be used to annotate a field: facebook.github.io/graphql

Authorization is a good and common scenario in which we usually will make use of directives. We could control what users are allowd to fetch an object (or even an attribute) from the server.


directive @isAuthenticated on FIELD | FIELD_DEFINITION
directive @hasRole(role: String) on FIELD | FIELD_DEFINITION

or for clients tools as It was mentioned on the above paragraph.

directive @deprecated(
  reason: String = "No longer supported"
) on FIELD_DEFINITION | ENUM_VALUE


type ExampleType {
  newField: String
  oldField: String @deprecated(reason: "Use `newField`.")
}

7.2 Code

In the below example we will implement a directive that can be assigned to fields This directive transform to lowercase the string attribute.

com.wesovilabs.workshops.graphql.directive.LowercaseDirective.java

@Component
public class LowercaseDirective implements SchemaDirectiveWiring {

    @Override
    public GraphQLFieldDefinition onField(SchemaDirectiveWiringEnvironment<GraphQLFieldDefinition> env) {
        GraphQLFieldDefinition field = env.getElement();
        DataFetcher dataFetcher = DataFetcherFactories.wrapDataFetcher(field.getDataFetcher(), (((dataFetchingEnvironment, value) -> {
            if (value instanceof String) {
                return ((String) value).toLowercase();
            }
            return value;
        })));
        return field.transform(builder -> builder.dataFetcher(dataFetcher));
    }
}

Once we’ve defined our directive we just need to register the bean.

com.wesovilabs.workshops.graphql.Application.java

@SpringBootApplication
public class Application {
    
    ...
    
    @Bean
    @Autowired
    public SchemaDirective lowerCaseDirective(LowercaseDirective directive) {
        return new SchemaDirective("lower", directive);
    }
}

7.3 Challenges

  1. Create a directive @multiply with an attribute factor. The directive declaration should look like this
directive @multiply (
    factor: Int!
) on FIELD_DEFINITION

And this directive will be used for attribute rate in type Movie. The behavior will be the following: If factor is 2 the returned budget will be the real value multiply by 2. If the factor is 3 the returned value will be the budget multiplied by 3…

input Movie {
    ...
    rate:Float @multiply(factor:5)
    ...
}

To verify run this query

query {
  rate: getMovieRate(movieId:1){
    realRate: rate
  }
  movie: getMovie(movieId:1){
    customRate: rate
    
  }
}

and verify that customRate is the value of realRate multiplied by 5

{
  "data": {
    "rate": {
      "realRate": 3
    },
    "movie": {
      "customRate": 15
    }
  }
}