Apollo GraphQL Custom Scalars

A Series Blog for Deep Dive into Apollo GraphQL from Backend to Frontend

Photo by Eiliv-Sonas Aceron on Unsplash

This is a 1st of the series blogs on deep dive into Apollo GraphQL from backend to frontend. A lot of information is Apollo GraphQL Doc or GraphQL Doc as well as their source code on Github — all tributes go to them. For my part, I would like to give you my “destructuring” of the original knowledge and my reflection on it, analysis on the code examples/source code as well as some extra examples.

For people who start to learn about GraphQL, they know that every type definition in a GraphQL schema belongs to one of the following categories:

And GraphQL’s default scalar types:

For someone who is experienced in a strongly typed language like TypeScript, this is not difficult to understand.

Custom Scalar

From time to time, you will have the need to create your own scalar. While there is an amazing npm package for that: Graphql-scalars, which should satisfy most of your needs, but still, you may need to create something of your own.

You define these interactions in an instance of the GraphQLScalarType class.

class GraphQLScalarType<InternalType> {
constructor(config: GraphQLScalarTypeConfig<InternalType>)
}
type GraphQLScalarTypeConfig<InternalType> = {
name: string;
description?: ?string;
serialize: (value: mixed) => ?InternalType;
parseValue?: (value: mixed) => ?InternalType;
parseLiteral?: (valueAST: Value) => ?InternalType;
}

Let’s see an end to end implementation example on the GraphQL JS (Also on Apollo Server Doc). Note that after we define the scalar type we need to include it in the resolver map.

const { ApolloServer, gql } = require('apollo-server');
const { GraphQLScalarType, Kind } = require('graphql');
const typeDefs = gql`
scalar Odd # define it
type MyType {
oddValue: Odd
}
`;
const resolvers = {
Odd: new GraphQLScalarType({
name: 'Odd',
serialize: oddValue,
parseValue: oddValue,
parseLiteral(ast) {
if (ast.kind === Kind.INT) {
return oddValue(parseInt(ast.value, 10));
}
return null;
}
});
}
function oddValue(value) {
return value % 2 === 1 ? value : null;
}
const server = new ApolloServer({ typeDefs, resolvers });server.listen().then(({ url }) => {
console.log(`🚀 Server ready at ${url}`)
});

Another example with Date scalar more on the details:

const { GraphQLScalarType, Kind } = require('graphql');const dateScalar = new GraphQLScalarType({
name: 'Date',
description: 'Date custom scalar type',
serialize(value) {
return value.getTime();
},
parseValue(value)
{
return new Date(value);
},
parseLiteral(ast) {
if (ast.kind === Kind.INT) {
return new Date(parseInt(ast.value, 10));
}
return null; // Invalid hard-coded value (not an integer)
},
});

You can see there are three important methods that describe how Apollo Server interacts with the scalar.

The value as argument is the Date scalar in a GraphQL response, and we serialize it as the integer value returned by the getTime function of a JavaScript Date object.

The value as argument comes from the variables field in the incoming query as a JSON value and we need to convert to its back-end representation of Date type before it's added to a resolver's args. This happens when the scalar is provided by a client as a GraphQL variable for an argument.

When an incoming query string includes the scalar as a hard-coded argument value, that value is part of the query document’s abstract syntax tree (AST). In the example above, parseLiteral converts the AST value from a string to an integer, and from integer to Date .

Also note the differences on parseValue and parseLiteral is that parseValue gets a plain JS object, and parseLiteral gets a value AST. The differences are because in GraphQL there are two ways of reading input from client, one is inline in the query where 10 is the inline value for first argument.

query {
allUsers(first:10) {
id
}
}

The other way of reading input from clients is through variables:

query ($first: Int) {
allUsers(first: $first) {
id
}
}
variables: {
"frist": 10
}

Note that the Kind from ParseLiteral above is an enum that describes the different kinds of AST nodes. The helper Kindcontains all graphql types in more human-readable format and we can use it to determine the type of AST we want to check, for example with boolean:

import { Kind } from 'graphql';

function parseLiteral(value) {
return ast.kind === Kind.BOOLEAN ? ast.value : undefined;
}

Lastly, note that the DataScalar and the OddScalar above are using the GraphQLScalarType class directly, but you overwrite the class as the example shows below for MaxLength .

import { GraphQLScalarType, GraphQLScalarTypeConfig } from "graphql";

export class MaxLength extends GraphQLScalarType {
constructor(
type: Readonly<GraphQLScalarTypeConfig<any, any>>,
limit: number
) {
super({
name: `MaxLength_${limit}`,
description: "Scalar type on max length",
serialize(value) {
const serialized = type.serialize(value);
if (typeof value === `string` && value.length <= limit) {
return serialized;
}
if (typeof value === `number` && !isNaN(value) && value <= limit) {
return serialized;
}
throw new TypeError(`Value above limit: ${limit}`);
},
parseValue(value) {
return type.parseValue(value);
},
parseLiteral(ast) {
return type.parseLiteral(ast, {});
},
});
}
}

We can use the Scalar above with custom Directive which we will talk about in the next blog.

That’s it!

Happy Reading!