This article was published on Tuesday, June 27, 2023 by Eddy Nguyen @ The Guild Blog
Scalar type is one of the most important GraphQL concepts to understand. Knowing how scalar works
and using the right tools can help us build type safe, robust and extendable schemas. In this
article, we cover various essential scalar topics:
- What Is GraphQL Scalar?
- Create Custom GraphQL Scalar
- GraphQL Scalar Tools for Type Safety
What Is GraphQL Scalar?
GraphQL scalar type is a primitive type that represents a value in the input or output field's
return type. GraphQL comes with a few native scalars: ID
, Int
, Float
, String
, Boolean
.
However, custom scalars can be declared in schemas to represent more complex data types.
Scalar Value Coercion
One important concept of GraphQL scalar is coercion. This happens when a scalar is used as
either input or output. Coercion is the process of taking the incoming value that could be one or
many types, and turning it into one outgoing type.
Let's take the native ID
scalar as an example:
- When used as input: a client can send either
string
ornumber
value and the value is coerced intostring
before it gets to a resolver on the server. - When used as output: a server can return
string
ornumber
and it is coerced tostring
before it is sent to the client.
Here's the full list of native scalar input and output TypeScript types:
Scalar | Client can send | Resolver receives | Resolver can return | Client receives |
---|---|---|---|---|
ID | string |
string |
string |
string |
number |
string |
number |
string |
|
Int |
number (32-bit signed integer) |
number |
string |
number (32-bit signed integer) |
number (32-bit signed integer) |
number (32-bit signed integer) |
|||
boolean |
1 if true, 0 if false |
|||
Float |
number (Float or number) |
number |
string |
number |
number |
number |
|||
boolean |
1 if true, 0 if false |
|||
String | string |
string |
string |
string |
boolean |
"trueβ if true, "false" if false | |||
number |
string (numeric value converted into string) |
|||
Boolean | boolean |
boolean |
boolean |
boolean |
number |
false if incoming value is 0, true if incoming value is not 0 |
Custom GraphQL Scalar
As our schemas evolve, we may need to present more complex data types with custom validation rules.
We can create custom GraphQL scalars to solve this use case.
There are many examples of popular custom scalars that are used in a lot of projects:
-
DateTime
: string representing an exact point in time -
BigInt
: representing values which are too large to be represented bynumber
. Similar to JavaScript'sbigint
type -
EmailAddress
: string representing valid email formats
There are 3 steps to create a custom GraphQL Scalar:
1. Declare Custom Scalar in Schema
We can declare custom GraphQL Scalars using the scalar
keyword:
```graphql filename="schema.graphql"
scalar CustomScalar
### 2. Create Custom Scalar Resolver
Now, we can create a resolver for the scalar using `GraphQLScalarType` from the `graphql` package:
```shell
# Install `graphql` package if it's not installed already
yarn add graphql
```tsx filename="resolvers/CustomScalar.ts"
import { GraphQLScalarType } from 'graphql'
export const CustomScalar = new GraphQLScalarType({
name: 'CustomScalar',
description: 'Custom Scalar description',
parseValue(inputValue: unknown) {
// (1) Used for input
},
parseLiteral(ast) {
// (2) Used for input
},
serialize(outputValue: unknown) {
// (3) Used for output
}
})
There are 3 main functions to keep in mind when creating a custom scalar:
1. `parseValue`: This function is called when the scalar is used as variable in an input. The
validated and returned value of this function is passed to resolvers. Below is an example of a
GraphQL operation that would trigger `parseValue`:
```graphql
query Example($var: CustomScalar!) {
example(arg: $var) # a variable is used so `parseValue` is called on the server
}
-
parseLiteral
: This function is called when the scalar is used as literal value in an input. Theast
parameter contains the GraphQL kind (e.g. int or string) and the value of the input. The validated and returned value of this function is passed to resolvers. Below is an example of a GraphQL operation that would triggerparseLiteral
:
query Example {
example(arg: "Hello") # a literal string is used so `parseLiteral` is called on the server
}
-
serialize
: This function is called on the output returned by resolvers, before the value is sent to the client. Since most GraphQL servers send results as JSON over HTTP, the return value ofserialize
function must turn the value intostring
,number
orboolean
.
Check out this visualisation of the flow of a scalar value from the client to the server, and then
back to the client:
- When a scalar is used as input, the variable or literal value is parsed using
parseValue
orparseLiteral
respectively. - The parsed value reaches the second param (usually called
args
) of the receiving resolver. - If the scalar is used as output, the value reaches the first param (usually called
parent
) of the receiving resolver. - Once ready to be returned to the client, the scalar value is sent to the scalar resolver's
serialize
function to be turned into JSON-compatible values. - The serialised scalar value is sent back to the client.
3. Add Custom Scalar to Resolvers Map
Finally, we can add the custom scalar resolver to the resolvers map in the GraphQL server. Here's an
example of how to do it with GraphQL Yoga:
# Install `graphql-yoga` to create a GraphQL server
yarn add graphql-yoga
import { createServer } from 'http'
import { createSchema, createYoga } from 'graphql-yoga'
// 1. Import the custom resolver
import { CustomScalar } from './resolvers/CustomScalar.ts'
const yoga = createYoga({
schema: createSchema({
typeDefs /* GraphQL typeDefs */,
resolvers: {
CustomScalar // 2. Put the custom scalar resolver into resolvers map
}
})
})
const server = createServer(yoga)
server.listen(4000, () => {
console.info('Server is running on http://localhost:4000/graphql')
})
Now, running the server allows us to use custom scalars in our project. π
GraphQL Scalar Tools for Type Safety
There are lots of tools in the GraphQL ecosystem to support working with both native and custom
scalars:
1. GraphQL Code Generator
GraphQL Code Generator is a CLI tool with an extensive
plugin ecosystem to make implementing both GraphQL client and server easier and safer.
For scalars, GraphQL Code Generator can generate input and output types correctly. On top of that,
we can customise the types for both native and custom types to fit our needs.
In the latest major versions of GraphQL Codegen plugins, the generated Scalar
type has changed to better support scalar input and output types. This change is necessary because it allows us to type scalars more expressively from now on.
Read on to see how to use Codegen to generate types for clients and
servers.
Codegen Config for Clients
typescript is the plugin to
generate the base TypeScript types, including the Scalars
type for both native and custom scalars.
This type is then used by other client plugins like
typescript-operations.
First, install GraphQL Code Generator CLI and typescript
plugin:
yarn add -D @graphql-codegen/cli @graphql-codegen/typescript
Here's an example config that generates native scalar types and CustomScalar
scalar for clients:
```tsx filename="codegen.ts"
import type { CodegenConfig } from '@graphql-codegen/cli'
const config: CodegenConfig = {
schema: '**/schema.graphql',
generates: {
'src/schema/types.generated.ts': {
plugins: ['typescript'],
config: {
scalars: {
// Recommended ID scalar type for clients:
ID: {
input: 'string | number',
output: 'string'
},
// Setting custom scalar type:
CustomScalar: {
input: 'string', // this means our server can take CustomScalar as string
output: 'number' // this means our server will return CustomScalar as number
}
}
}
}
}
}
export default config
Running `yarn graphql-codegen` generates the following Scalar type:
{/* prettier-ignore */}
```tsx filename="src/schema/types.generated.ts"
export type Scalars = {
ID: { input: string | number; output: string };
String: { input: string; output: string };
Boolean: { input: boolean; output: boolean };
Int: { input: number; output: number };
Float: { input: number; output: number };
CustomScalar: { input: string; output: number };
};
By default, the typescript
plugin generates string
for both ID input and output. This approach
allows the plugin to be used for both client and server type generation without extra configuration.
On the other hand, using the above recommended config to generate type that's closer to GraphQL's
native behaviour (i.e. clients can send either string
or number
as ID input) can make the
experience better. A lot of systems use number
as the type for ID of an object. If the recommended
config is used for these cases, we don't have to manually convert the ID to a string
before
sending it to the server.
Starting from typescript
and typescript-operations
plugin v4, all depedent client plugins
(e.g. Apollo Client, React Request, urql, etc.) must update to use the new input/output format. If
you find issues in these client plugins, create an issue in the community
repo.
Codegen Config for Servers
In typescript
and typescript-resolvers
plugin v4.0.0, the default ID Scalar input type was changed to string | number
from string
. This was done to match the expected GraphQL client type.
However, server plugins such as typescript-resolvers
also depend on the type generated by the
typescript
plugin. This causes a lot of friction when setting up config to generate server types.
In typescript
and typescript-resolvers
plugin v4.0.1, we have reverted the default ID Scalar
input type to string
. Read the
pull request for more details.
For GraphQL servers, we can use
typescript-resolvers
plugin with the same scalars
config as
typescript plugin. This
combination changes how scalar types are used:
- The scalar input is the type of coerced value that resolvers receive after
parseValue
orparseLiteral
functions are called. - The scalar output is the type that resolvers return before
serialize
is called.
First, install GraphQL Code Generator CLI, typescript
and typescript-resolvers
plugin:
yarn add -D @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-resolvers
Here's the recommended config server types:
import type { CodegenConfig } from '@graphql-codegen/cli'
const config: CodegenConfig = {
schema: '**/schema.graphql',
generates: {
'src/schema/types.generated.ts': {
plugins: ['typescript', 'typescript-resolvers'],
config: {
scalars: {
// Recommended ID scalar type for servers:
ID: {
input: 'string',
output: 'string | number'
},
// Setting custom scalar type:
CustomScalar: {
input: 'string', // this means our server can take CustomScalar as string
output: 'number' // this means our server will return CustomScalar as number
}
}
}
}
}
}
export default config
Running yarn graphql-codegen
generates the following Scalar type:
{/* prettier-ignore */}
export type Scalars = {
ID: { input: string; output: string | number };
String: { input: string; output: string };
Boolean: { input: boolean; output: boolean };
Int: { input: number; output: number };
Float: { input: number; output: number };
CustomScalar: { input: string; output: number };
};
The recommended server config makes it more convenient to handle ID scalar types. This approach is
particularly useful if you have objects with numeric IDs. Without using the recommended config, we
would have to create custom mappers or manually convert the number to string to satisfy TypeScript
typecheck.
The recommeded setup is suitable for the most common use cases. It does not cover some edge cases that may make writing resolvers awkward.
For example, Int
's output is technically string | number | boolean
but it is typed as number
by default because most resolvers would never return string
or boolean
.
2. GraphQL Scalars (graphql-scalars
)
GraphQL Scalars is a library that contains many commonly
used custom GraphQL Scalars such as DateTime
, BigInt
, etc.
Even if it is not too hard to create custom scalars, testing and publishing scalars to share between
GraphQL servers can quickly distract from building the core business logic. Therefore, it is usually
better to use a well-maintained and documented library like graphql-scalars
.
Here's how we can use DateTime
scalar from graphql-scalars
:
- Install
graphql-scalars
:
yarn add graphql-scalars
- Declare
DateTime
in the schema:
```graphql filename="schema.graphql"
scalar DateTime
3. Import `DateTime` resolver into resolvers map:
```tsx
import { createServer } from 'http'
// 1. Import scalar resolver
import { DateTimeResolver } from 'graphql-scalars'
import { createSchema, createYoga } from 'graphql-yoga'
const yoga = createYoga({
schema: createSchema({
typeDefs /* GraphQL typeDefs */,
resolvers: {
DateTime: DateTimeResolver // 2. Put resolver to resolvers map
}
})
})
const server = createServer(yoga)
server.listen(4000, () => {
console.info('Server is running on http://localhost:4000/graphql')
})
- Use GraphQL Code Generator to create types:
Most scalar resolvers from graphql-scalars
come with recommended server type that can be used in
Codegen config. We can import a resolver and use said type very easily:
```tsx filename="codegen.ts"
import { DateTimeResolver } from 'graphql-scalars'
// 1. Import scalar resolver
import type { CodegenConfig } from '@graphql-codegen/cli'
const config: CodegenConfig = {
schema: '**/schema.graphql',
generates: {
'src/schema/types.generated.ts': {
plugins: ['typescript', 'typescript-resolvers'],
config: {
scalars: {
ID: {
input: 'string',
output: 'string | number'
},
DateTime: DateTimeResolver.extensions.codegenScalarType // 2. Use scalar resolver's recommended server type
}
}
}
}
}
export default config
<Callout type="info">
What about recommended client types for `graphql-scalars`'s custom scalars?
Currently, there's no official package for this ( *yet*! ). However, there's an
[issue on the GraphQL Scalars repo](https://github.com/Urigo/graphql-scalars/issues/1996).
Here's the general tips for picking the right client type for `graphql-scalars`:
1. Check the `serialize` function's return type, then set the scalar `output` type in the codegen
config.
2. If your client converts the returned scalar value to another type, you can set the final
`output` type in the codegen config.
</Callout>
### 3. GraphQL Codegen Server Preset
GraphQL Code Generator and `graphql-scalars` libraries should be enough to manage our scalar
implementation and type requirements. However, Server Preset
([@eddeee888/gcg-typescript-resolver-files](https://www.npmjs.com/package/@eddeee888/gcg-typescript-resolver-files))
can simplify the scalar setup further:
* uses the recommended ID scalar server config by default
* detects if a custom scalar exists in the installed `graphql-scalars` package, then automatically
imports the scalar resolver to the resolvers map and sets up recommended type in Codegen config
* detects if a custom scalar does not exist in the installed `graphql-scalars` package (or if the
package is not installed), then automatically creates a custom scalar resolver and puts it into
resolvers map
1. Install GraphQL Code Generator CLI, Server Preset and (optionally) GraphQL Scalars:
```shell
yarn add -D @graphql-codegen/cli @eddeee888/gcg-typescript-resolver-files
# (Optional) Install `graphql-scalars` if you intend to use custom scalars from this library
yarn add graphql-scalars
- Use Server Preset in the Codegen config:
```ts filename="codegen.ts"
import { defineConfig } from '@eddeee888/gcg-typescript-resolver-files'
import type { CodegenConfig } from '@graphql-codegen/cli'
const config: CodegenConfig = {
schema: '**/schema.graphql',
generates: {
'src/schema': defineConfig()
}
}
export default config
Run `yarn graphql-codegen`, and... that's it! Now, every time a custom scalar like `DateTime` is
added to the schema, the implementation and type automatically come from `graphql-scalars`! π
<Callout type="info">
The Server Preset does more than just handling custom scalars. There are existing guides and blog posts with more details on its features and concepts:
* Read how to set up
[GraphQL Yoga / Apollo Server with Server Preset](https://the-guild.dev/graphql/codegen/docs/guides/graphql-server-apollo-yoga)
* Read how to build
[Scalable APIs with GraphQL Server Codegen Preset](https://the-guild.dev/blog/scalable-apis-with-graphql-server-codegen-preset)
</Callout>
## Summary
In this article, we explored the importance of GraphQL scalar types, how it works and how to create
custom scalar resolvers to extend our schemas. We also explored tools to help working with native
and custom scalars such as GraphQL Code Generator, GraphQL Scalars and Server Preset.
Top comments (0)