Chapter 4 GraphQL: Objects

4.1 Introduction

The GraphQL specification includes the following default scalar types: Int, Float, String, Boolean and ID. While this covers most of the use cases, often you need to support custom atomic data types (e.g. Date), or you want a version of an existing type that does some validation. To enable this, GraphQL allows you to define custom scalar types. Enumerations are similar to custom scalars, but their values can only be one of a pre-defined list of strings.

The way to define new scalars or enums in the schema is shown below:

scalar MyCustomScalar

enum Direction {
  NORTH
  EAST
  SOUTH
  WEST
}

type MyType {
    myAttribute: MyCustomScalar
    direction: Direction
    ...
}

Fields can take arguments as input. These can be used to customize the return value (eg, filtering search results). This is known as field argument.

If you have a look at our schema.graphql you can find an example of usage of a field argument for attribute actors in type Movie. The total argument is used to define the max number of actors returned from the server.

4.2 Code

4.2.1 Enum

In the introduction we learnt how to define a enum type in the schema. To code it we just need to create an enum type with the same name.

com.wesovilabs.workshops.graphql.domain.Direction.java

public enum Direction {
    NORTH,
    EAST,
    SOUTH,
    WEST
}

4.2.2 Resolvers

Let’s imagine that we have an operation that returns a Employee type and this type contains an attribute details of type SocialDetails whose information needs to be taken from an external API. And this attribute won’t be always required by the API consumers. Server should not waste time on obtaining something that clients do not need.

com.wesovilabs.workshops.graphql.resolver.PersonResolver.java

@Component
public class PersonResolver implements GraphQLResolver<Person> {
    
    public SocialDetails details(Person person) {
        /** 
        *  We calls Linkedin API to obtain the info.
        **/
        SocialDetails details = linkedinAPI.getDetals(person.getId());
        return details;
    }
    
}

Now, image that SocialDetails can be taken from more than one social network and we want to permit the consumers to decide which social network must be used.

schema.graphqls.

enum Source{
    Linkedin
    Facebook
}
type Person {

  details(source:Source=Linkedin):SocialDetails
}

The code should be like:

com.wesovilabs.workshops.graphql.domain.Source.java

public enum Source {
    Facebook,
    Linkedin
}

com.wesovilabs.workshops.graphql.resolver.PersonResolver.java

@Component
public class PersonResolver implements GraphQLResolver<Person> {
    
    public SocialDetails details(Person person, Source source) {
        if (source==Source.Linkedin){
            SocialDetails details = linkedinAPI.getDetals(person.getId());
            return details;
        }
        if (source==Source.Facebook){
            SocialDetails details = facebookAPI.getDetals(person.getId());
            return details;
        } 
        return null;
    }
    
}

4.2.3 Scalars

We need to create a class that extendsGraphQLScalarType inr order to define custom scalars.

Scalar documentation can be found here

Keep in mind that our application works with Spring, so the code could be a little bit different from the examples in the link.

The below example shows a custom scalar type that will be used for odd numbers.

com.wesovilabs.workshops.graphql.scalar.OddScalar.js

@Component
public class OddScalar extends GraphQLScalarType {


    public OddScalar() {
        /**
        * args[0] Scalar name: It must be the one defined in the GraphQL schema
        * args[1] Scalar description: A brief description for our scalar type
        * args[2] A Coercing instance that we define below
        */
        super("Odd", "Odd scalar", coercing);
    }

    private static final Coercing coercing = new Coercing<Object, Object>() {
        @Override
        public Object serialize(Object input) {
             if (input instanceof Integer) {
                if (result % 2 !=0){
                    throw new CoercingSerializeException(
                        "It's not a valid odd number."
                    );             
               }
               return result;
            }
            throw new CoercingSerializeException(
                     "Expected type 'Int' but was other."
            );
        }

        @Override
        public Object parseValue(Object input) {
            return serialize(input);
        }

        @Override
        public Object parseLiteral(Object input) {
           if (!(input instanceof IntValue)) {
               throw new CoercingParseLiteralException(
                   "Expected AST type 'IntValue' but was other'."
               );
           }
           Integer value = ((IntValue) input).getValue();
           if (result % 2 !=0){
                throw new CoercingSerializeException(
                    "It's not a valid odd number."
                );             
            }
            return value;
        }
    };
    
}

4.3 Challenges

  1. Define an enum type Genre whose values are Drama and SciFi (add as many other as you want) and use it for attribute genre in type Movie and MovieRequest.

Run this query to verify your implementation works as expected

mutation {
  addMovie (request:{
    title: "Corpse Bride"
    year: 2005
    budget: 35000000
    directorId: 1
    genre: SciFi
    trailer: "https://www.youtube.com/watch?v=o5qOjhD8j08"
  }){
    id
    director{
      fullName
      country
    }
    genre
  }
}
  1. Define an enum Gender and use it for attribute gender in type Actor.

Run this query to verify your implementation works as expected

query {
  listActors{
    fullName
    gender
  }
}
  1. Define a scalar type Url and use it in attribute trailer of Movie and MovieRequest. Only valid url’s should be permitted.

Run this query to verify that only valid url’s are permitted. Actually, the movie should not be saved into the database.

mutation {
  addMovie (request:{
    title: "Gran Torino"
    year: 2009
    budget: 28000000
    directorId: 6
    genre: Drama
    trailer: ".http"
  }){
    id
    director{
      fullName
      country
    }
    genre
    trailer
  }
}

Run this query to verify that your scalar Url works as expected.

mutation {
  addMovie (request:{
    title: "Gran Torino"
    year: 2009
    budget: 28000000
    directorId: 6
    genre: Drama
    trailer: "https://www.youtube.com/watch?v=9ecW-d-CBPc"
  }){
    id
    director{
      fullName
      country
    }
    genre
    trailer
  }
}
  1. Define an enum type Currency whose possible values are Euro and Dollar. Our API must permit the API consumers to decide in which currency they want to obtain attribute budget in type Movie. 1€ => 1.14$

Run this query

query {
  getMovie(movieId:1){
    budgetInEuros: budget(currency:Euro)
    budgetInDollars: budget(currency:Dollar)
  }
}

and verify that the output should be this:

{
  "data": {
    "getMovie": {
      "budgetInEuros": 20,
      "budgetInDollars": 22.799999999999997
    }
  }
}