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
directive @uppercase on 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

Let’s implement uppercase directive! This directive will be used for attribute title of Movie. This means that attribute title will be returned in uppercase.

resources/graphql/graphql.schema

directive @uppercase on FIELD_DEFINITION

type Movie {
    "Name of the movie"
    title: String! @uppercase
}

Now, we must re-generate code make generate

We must modify the handler definition to add directive implementation

main.go

func main(){
    ...

    config := exec.Config{Resolvers: resolver}
    
    // Here we define directive behavior
    config.Directives.Uppercase = func(ctx context.Context, obj interface{}, next graphql.Resolver) (res interface{}, err error) {
        movie := obj.(*model.Movie)
        return strings.ToUpper(movie.Movie.Title), nil
    }
    ...
    handler := c.Handler(handler.GraphQL(
        exec.NewExecutableSchema(config),
        handler.WebsocketUpgrader(websocket.Upgrader{
            CheckOrigin: func(r *http.Request) bool {
                return true
            },
        })))
    r.Handle("/query", &ContextInjector{ctx, handler})
    ...
}

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
    }
  }
}