Custom Scalars
The GraphQL specification includes default scalar types Int
, Float
, String
, Boolean
, and ID
. Although these scalars cover the majority of use cases, some applications need to support other atomic data types (such as Date
) or add validation to an existing type. To enable this, you can define custom scalar types.
Defining a custom scalar
To define a custom scalar, add it to your schema like so:
1scalar MyCustomScalar
You can now use MyCustomScalar
in your schema anywhere you can use a default scalar (e.g., as the type of an object field, input type field, or argument).
Including scalar specification
Updated in the October 2021 version of the GraphQL spec, you can include a @specifiedBy
directive as metadata for schema consumers to understand what format the scalar is using. This directive does not provide automatic validation, but can provide useful context for humans reading the schema.
1scalar MyCustomScalar @specifiedBy(url: "https://specs.example.com/rfc111")
However, Apollo Server still needs to know how to interact with and generate values of this new scalar type.
Defining custom scalar logic
After you define a custom scalar type, you need to define how Apollo Server interacts with it. In particular, you need to define:
How the scalar's value is represented in your backend
This is often the representation used by the driver for your backing data store.
How the value's back-end representation is serialized to a JSON-compatible type
How the JSON-compatible representation is deserialized to the back-end representation
You define these interactions in an instance of the GraphQLScalarType
class.
For more information about the
graphql
library's type system, see the official documentation.
Example: The Date
scalar
The following GraphQLScalarType
object defines interactions for a custom scalar that represents a date (this is one of the most commonly implemented custom scalars). It assumes that our backend represents a date with the Date
JavaScript object.
1import { GraphQLScalarType, Kind } from 'graphql';
2
3const dateScalar = new GraphQLScalarType({
4 name: 'Date',
5 description: 'Date custom scalar type',
6 serialize(value) {
7 if (value instanceof Date) {
8 return value.getTime(); // Convert outgoing Date to integer for JSON
9 }
10 throw Error('GraphQL Date Scalar serializer expected a `Date` object');
11 },
12 parseValue(value) {
13 if (typeof value === 'number') {
14 return new Date(value); // Convert incoming integer to Date
15 }
16 throw new Error('GraphQL Date Scalar parser expected a `number`');
17 },
18 parseLiteral(ast) {
19 if (ast.kind === Kind.INT) {
20 // Convert hard-coded AST string to integer and then to Date
21 return new Date(parseInt(ast.value, 10));
22 }
23 // Invalid hard-coded value (not an integer)
24 return null;
25 },
26});
This initialization defines the following methods:
serialize
parseValue
parseLiteral
Together, these methods describe how Apollo Server interacts with the scalar in every scenario.
serialize
The serialize
method converts the scalar's back-end representation to a JSON-compatible format so Apollo Server can include it in an operation response.
In the example above, the Date
scalar is represented on the backend by the Date
JavaScript object. When we send a Date
scalar in a GraphQL response, we serialize it as the integer value returned by the getTime
function of a JavaScript Date
object.
Note that Apollo Client cannot automatically interpret custom scalars (see issue), so your client must define custom logic to deserialize this value as needed.
parseValue
The parseValue
method converts the scalar's JSON value to its back-end representation before it's added to a resolver's args
.
Apollo Server calls this method when the scalar is provided by a client as a GraphQL variable for an argument. (When a scalar is provided as a hard-coded argument in the operation string, parseLiteral
is called instead.)
parseLiteral
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). Apollo Server calls the parseLiteral
method to convert the value's AST representation to the scalar's back-end representation.
In the example above, parseLiteral
converts the AST value from a string to an integer, and then converts from integer to Date
to match the result of parseValue
.
Providing custom scalars to Apollo Server
In the examples below, we use top-level
await
calls to start our server asynchronously. Check out our Getting Started guide to see how we configured our project to support this.
After you define your GraphQLScalarType
instance, you include it in the same resolver map that contains resolvers for your schema's other types and fields:
1import { ApolloServer } from '@apollo/server';
2import { startStandaloneServer } from '@apollo/server/standalone';
3import { GraphQLScalarType, Kind } from 'graphql';
4
5const typeDefs = `#graphql
6 scalar Date
7
8 type Event {
9 id: ID!
10 date: Date!
11 }
12
13 type Query {
14 events: [Event!]
15 }
16`;
17
18const dateScalar = new GraphQLScalarType({
19 // See definition above
20});
21
22const resolvers = {
23 Date: dateScalar,
24 // ...other resolver definitions...
25};
26
27const server = new ApolloServer({
28 typeDefs,
29 resolvers,
30});
31
32const { url } = await startStandaloneServer(server);
33
34console.log(`🚀 Server listening at: ${url}`);
Example: Restricting integers to odd values
In this example, we create a custom scalar called Odd
that can only contain odd integers:
1import { ApolloServer } from '@apollo/server';
2import { startStandaloneServer } from '@apollo/server/standalone';
3import { GraphQLScalarType, Kind, GraphQLError } from 'graphql';
4
5// Basic schema
6const typeDefs = `#graphql
7 scalar Odd
8
9 type Query {
10 # Echoes the provided odd integer
11 echoOdd(odd: Odd!): Odd!
12 }
13`;
14
15// Validation function for checking "oddness"
16function oddValue(value: unknown) {
17 if (typeof value === 'number' && Number.isInteger(value) && value % 2 !== 0) {
18 return value;
19 }
20 throw new GraphQLError('Provided value is not an odd integer', {
21 extensions: { code: 'BAD_USER_INPUT' },
22 });
23}
24
25const resolvers = {
26 Odd: new GraphQLScalarType({
27 name: 'Odd',
28 description: 'Odd custom scalar type',
29 parseValue: oddValue,
30 serialize: oddValue,
31 parseLiteral(ast) {
32 if (ast.kind === Kind.INT) {
33 return oddValue(parseInt(ast.value, 10));
34 }
35 throw new GraphQLError('Provided value is not an odd integer', {
36 extensions: { code: 'BAD_USER_INPUT' },
37 });
38 },
39 }),
40 Query: {
41 echoOdd(_, { odd }) {
42 return odd;
43 },
44 },
45};
46
47const server = new ApolloServer({
48 typeDefs,
49 resolvers,
50});
51
52const { url } = await startStandaloneServer(server);
53
54console.log(`🚀 Server listening at: ${url}`);
Importing a third-party custom scalar
If another library defines a custom scalar, you can import it and use it just like any other symbol.
For example, the graphql-type-json
package defines the GraphQLJSON
object, which is an instance of GraphQLScalarType
. You can use this object to define a JSON
scalar that accepts any value that is valid JSON.
First, install the library:
1$ npm install graphql-type-json
Then import the GraphQLJSON
object and add it to the resolver map as usual:
1import { ApolloServer } from '@apollo/server';
2import { startStandaloneServer } from '@apollo/server/standalone';
3import GraphQLJSON from 'graphql-type-json';
4
5const typeDefs = `#graphql
6 scalar JSON
7
8 type MyObject {
9 myField: JSON
10 }
11
12 type Query {
13 objects: [MyObject]
14 }
15`;
16
17const resolvers = {
18 JSON: GraphQLJSON,
19 // ...other resolvers...
20};
21
22const server = new ApolloServer({
23 typeDefs,
24 resolvers,
25});
26
27const { url } = await startStandaloneServer(server);
28
29console.log(`🚀 Server listening at: ${url}`);