October 20th 2017 (2 years ago)

GraphQL scalars and their input and result coercion

Introduction

When designing GraphQL schema we have to define the so-called primitive values of each query. In GraphQL the primitive values are represented by scalars and enums. The GraphQL specification has different built-in scalars. In this first part of this series we will concentrate only on the built-in scalars. For the purpose of demonstrating real applications of the built-in scalars we will apply them in a modeling query for retrieving tasks from the existing “in-memory” database and also for adding new tasks to a database.

In GraphQL we deal with 8 different types:

In the article Input object type as an argument for GraphQL mutations and queries we were focusing on objects and input objects. In this series we are moving to scalars and enums. In this first part we deal only with built-in scalars; enums and custom scalars will be topic of the next part.

When we query or mutate data using our GraphQL schema, we first have to understand two operations that occur in GraphQL servers. These are:

  • result coercion — upholding the contract of a type which we receive from the server (basically upholding the primitive values or object type)
  • input coercion — upholding the contract of a type for input

arguments that we pass into the GraphQL query or mutation To fully understand the algorithm behind the coercion we have to go into the GraphQL specification. For us, the simple explanation as above is enough to get started. It is important to recognize that the rules for each scalar type are different, therefore we have to discuss these rules for each type and for each result and input coercion.

GraphQL scalars

Scalars are primitive values in GraphQL. This means that if we imagine each GraphQL response as a hierarchical tree graph, we can call the primitive values leaves in terms of graph theory. To imagine, check out the visualized hierarchy tree graph of our response for our query getTasks and mutation addTask, which we will code in this short tutorial series

In this first part we will deal only with id, name, completed, progress and taskPriorityas these are represented by built-in types. In the second part we then extend the Task type with the fields updatedAt, createdAt, and state.

Bult-in scalars

In this list we will go through each default scalar type. We will also discuss rules for result and input coercion and where this scalar should be used.

ID

The ID scalar type represents a unique identifier, often used to refetch an object or as a key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as "4") or integer (such as 4) input value will be accepted as an ID.

If you use, for example, some caching client like Apollo, each type should have at least one ID. This allows us to perform a normalization of queries, making it possible for us to update things in Apollo internal redux store automatically based on the unique id (edit: Apollo 2.0 does not use redux as its store anymore). This is also performed in Relay by the global identifier at each node. Even that ID is often numeric or in another format such as base64 encoded value. It is always serialized as a string because we want to achieve uniformity across different formats.

Input coercion
  • 5 is parsed into a string as "5"
  • However, if we pass true, it is parsed as "true". GraphQL will raise the following error
Argument "input" has invalid value {id: true}.
In field "id": Expected type "ID", found true

the same case is even for the Float type and etc.

Argument "input" has invalid value {id: 3.00}.
In field "id": Expected type "ID", found 3.00.
Result coercion

If possible the ID is serialized into a string.

  • 6 is serialized into “6”
  • true is serialized into “true”
  • 3.00 to “3”

Int

The Int scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2³¹) and 2^31 - 1.
Input coercion
  • string “10” will raise an error when passing it as a Integer argument. Also 10.00 will raise an error. Only pure Integer values are accepted.
  • The string “3” is serialized into 3 and float 3.00 is also serialized to 3.
Result coercion
  • The string “3” is serialized into 3 and float 3.00 is also serialized to 3.

Float

Input coercion
  • String “3” or “3.00” will raise the error in the same way as with GraphQLInt. When we pass 3 as an integer it is parsed into 3.00.
Result coercion
  • 3 is serialized into 3.00, “3.00” is serialized into 3.00, etc.
The Float scalar type represents signed double-precision fractional values as specified by IEEE 754.

String

The String scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.
Input coercion

The rules are similar for GraphQLID. The input 5 will raise an error etc.

Result coercion

1 is serialized into "1", 1.00 is serialized into "1" , true is serialized into "true" and so on.

Boolean

Input coercion

When we pass "true" as an argument, it raises an error.

Result coercion

If possible, all non boolean value are coerced into boolean values. The example can be 0 and 0.00 coerced into false as well as 1 and 1.00 serialized into true. However, it is important to emphasize that string "true" or "True" is not coerced into true. GraphQL schema will allways return boolean true value for every string with the length greater than 0.

The Boolean scalar type represents true or false.

An application of built-in scalars

Now let’s go right into implementation. You can follow the code snippets in this article or clone the git repository

git clone https://github.com/a7v8x/express-graphql-demo.git -b feature/3-graphql-scalars

If you are new to GraphQL, it may also be helpful to check out previous articles in GraphQL mastery publication, especially the one on creating mutations as we use them as prerequisites for this article in terms of understanding GraphQL. We will also use parts of the code that we built in previous articles. However, everything can be obtained from the mentioned repository. In the GitHub project we do not use a real database. The in-memory database is enough to get started.

To demonstrate each type of built-in scalar, let’s design a Task object type in the following way.

type Task {
  id: ID!
  name: String!
  completed: Bool!
  progress: Float!
  taskPriority: Int!
}

In our code we use graphql-js library. Then we can describe Task type as follows.

import {
  GraphQLString,
  GraphQLID,
  GraphQLObjectType,
  GraphQLNonNull,
  GraphQLBoolean,
  GraphQLFloat,
  GraphQLInt,
} from 'graphql';

const taskType = new GraphQLObjectType({
  name: 'Task',
  fields: () => ({
    id: {
      type: new GraphQLNonNull(GraphQLID),
    },
    name: {
      type: new GraphQLNonNull(GraphQLString),
    },
    completed: {
      type: new GraphQLNonNull(GraphQLBoolean),
    },
    progress: {
      type: new GraphQLNonNull(GraphQLFloat),
    },
    taskPriority: {
      type: new GraphQLNonNull(GraphQLInt),
    },
  }),
});

export {
  taskType as default,
};

Now let’s create a simple array where we will store our seeded tasks. Then we can make the simple getTasks query and the addTask mutation, which we can call from our resolver function in GraphQL schema. The getTasks query is used to query all the tasks from the memory database. The addTask mutation is then used for creating a new task. In getTasks we can demonstrate result coercion. However, it does not have any arguments in our code, so we also needed to create an addTaskmutation.

import { random } from 'faker';

const tasks = [{
  id: '7e68efd1',
  name: 'Test task',
  completed: false,
  createdAt: '2017-10-06T14:54:54+00:00',
  updatedAt: '2017-10-06T14:54:54+00:00',
  state: 1,
}];

const addTask = (input) => {
  const newTask = {
    id: random.uuid(),
    ...input,
  };
  tasks.unshift(newTask);
  return new Promise((resolve) => {
    setTimeout(() =>
      resolve(newTask), 50);
  });
};

const getTasks = () => new Promise((resolve) => {
  setTimeout(() =>
    resolve(tasks), 50);
});

export {
  getTasks,
  addTask,
};

The corresponsing GraphQL schema for retrieving tasks from memory db is the following

import {
  GraphQLList,
} from 'graphql';
import { getTasks } from '../../db/tasksDb';
import Task from './taskType';

const taskQueries = {
  tasks: {
    type: new GraphQLList(Task),
    resolve: async () => getTasks(),
  },
};

export {
  taskQueries as default,
};

If we used the same implementation of GraphQL server as we discussed in the article on basic setup of GraphQL in express, we have everything we need to perform query for retrieving tasks against the schema. Just go to /graphiql and paste the following query

query getTasks {
  tasks {
    id
    name
    completed
    taskPriority
    progress
  }
}

If you use the Github project code you should get this result

First, let’s create an input object type for adding a task. We will use some defaultValues for each field

import {
  GraphQLString,
  GraphQLInputObjectType,
  GraphQLNonNull,
  GraphQLBoolean,
  GraphQLInt,
  GraphQLFloat,
} from 'graphql';

const addTaskInputType = new GraphQLInputObjectType({
  name: 'AddTaskInput',
  fields: () => ({
    name: {
      type: new GraphQLNonNull(GraphQLString),
    },
    completed: {
      type: GraphQLBoolean,
      defaultValue: false,
    },
    state: {
      type: TaskEnumType,
      defaultValue: 'unassigned',
    },
    taskPriority: {
      type: GraphQLInt,
      defaultValue: 1,
    },
    progress: {
      type: GraphQLFloat,
      defaultValue: 0,
    },
  }),
});

export {
  addTaskInputType as default,
};

Now we can move on to designing the addTask mutation

import {
  GraphQLNonNull,
} from 'graphql';
import { addTask } from '../../db/tasksDb';
import Task from './taskType';
import addTaskInput from './addTaskInput';

const taskMutations = {
  addTask: {
    type: new GraphQLNonNull(Task),
    args: {
      input: {
        type: new GraphQLNonNull(addTaskInput),
      },
    },
    resolve: async (source, { input }) => addTask(input),
  },
};

export {
  taskMutations as default,
};

We can execute this mutation and add a new task into the “in-memory” database. We can also use variables as we discussed in this GraphQL quick tip or just pass it as an inline argument.

mutation addTask {
  addTask(input: {name: "New task"}) {
    id
    taskPriority
    progress
    completed
  }
}

The result should be something like this

{
    "data": {
        "addTask": {
        "id": "c0eb5c7c-11e8-4b7e-9168-5fefc95b7eab",
        "taskPriority": 1,
        "progress": 0,
        "completed": false
        }
    }
}

For the sake of demonstration, we can now test some rules for result or input coercion. For example, let’s take a result coercion for GraphQLID and test the rule that an integer is serialized into a string.

6 is serialized into “6”

We can change the value of id to 6 in order to test a task in taskDb.js. By calling the getTasks query in GraphiQL in the same way as in the previous case, we will get the expected result, that will confirm this rule

{
  "data": {
    "tasks": [
      {
        "id": "6",
        "progress": 55.5,
        "name": "Test task",
        "completed": false,
        "taskPriority": 1
      }
    ]
  }
}

In the similar way we can confirm some rule for input coercion. Let’s take for example rule for input coercion of GraphQLInt.

string “10” will raise the error, when passing it as an GraphQLInt argument

We can demonstrate it on the addTask mutation,

mutation addTask {
  addTask(input: {name: "New task", taskPriority: "10"}) {
    id
    taskPriority
    progress
    completed
  }
}

which raises the following error


{
  "errors": [
    {
      "message": "Argument "input" has invalid value {name: "New task", taskPriority: "10"}.
In field "taskPriority": Expected type "Int", found "10".",
      "locations": [
        {
          "line": 2,
          "column": 18
        }
      ]
    }
  ]
}

Conclusion

All built-in scalars can be used as fields (primitive values) for input types as well as for output types. Each built-in GraphQL scalar has different rules for input and result coercion which are useful to know. We can also use enum and custom scalars as we will find out in the next part of this series.

Did you like this post? The repository with the examples and project setup can be cloned from this branch . Feel free to send any questions about the topic to david@atheros.ai. You can also check out our free upcoming GraphQL language course or subscribe for new articles on GraphQL Mastery with the form below.

Join thousands of others and be occasionally notified about new articles and our upcoming GraphQL courses

* Signing up for GraphQL Mastery newsletter indicates you agree with Terms and Conditions and Privacy Policy including our Cookie Policy.