Configuring Scalar Types
nitrogql supports configuring the mapping between GraphQL scalar types and TypeScript types. This is useful when you want to use custom scalar types in your GraphQL schema. The mapping is configured with the generate.type.scalarTypes
option.
This page explains how to configure scalar types for different scenarios.
πΆβπ«οΈ This page does not cover the nitrogql:graphql-scalars-plugin. If you want to use custom scalar from GraphQL Scalars, you should read that page instead.
Four Different Situations
nitogql supports specifying different TypeScript types for one GraphQL scalar type. They are used in different situations:
Name | Location | Situation | Example (ID ) |
---|---|---|---|
resolverInput | server | as an argument of a resolver | string |
resolverOutput | server | as a return value of a resolver | string | number |
operationInput | client | as an argument passed to an operation | string | number |
operationOutput | client | as a return value of an operation | string |
The first two types are used in the server, and the last two types are used in the client.
An example of a scalar type that uses different types in different situations is the ID
scalar type. According to the GraphQL spec, an ID
value is always serialized as a string. However, it can be represented as a string or an integer in the server. Above table shows how the ID
scalar type is configured for different situations by default.
resolverInput
The resolverInput
type is used in a GraphQL server when executing a resolver. Whenever a resolver receives an argument of the scalar type, it will be of this type.
Example:
type Query {
getUser(id: ID!): User!
}
const getUser: Resolvers<Context>["Query"]["getUser"] = async (
_,
{ id },
) => {
// `id` is typed as its `resolverInput` variant (string)
// ...
};
resolverOutput
The resolverOutput
type is used in a GraphQL server when executing a resolver. Whenever a resolver returns a value of the scalar type, it will be of this type.
Example:
type Query {
me: User!
}
const me: Resolvers<Context>["Query"]["me"] = async () => {
return {
// `id` is typed as its `resolverOutput` variant (string | number)
id: 1,
// ...
};
};
operationInput
The operationInput
type is used in a GraphQL client when executing an operation. Whenever an operation requires an argument of the scalar type, you need to pass a value of this type.
Example:
query GetUser($id: ID!) {
getUser(id: $id) {
# ...
}
}
const result = await yourClient.query({
query: GetUserQuery,
variables: {
// `id` is typed as its `operationInput` variant (string | number)
id: 1,
},
});
operationOutput
The operationOutput
type is used in a GraphQL client when executing an operation. Whenever an operation returns a value of the scalar type, it will be of this type.
Example:
query Me {
me {
id
# ...
}
}
const result = await yourClient.query({
query: MeQuery,
});
// `id` is typed as its `operationOutput` variant (string)
const id = result.data.me.id;
Specifying scalarTypes
in Configuration
When you configure the TypeScript types for a scalar type, you can use one of the following three forms. In addition to one that specifies all four types, there are two convenient shorthand forms. Actually, you rarely need to specify all four types, so the shorthand forms are usually enough.
Single Form
The easiest way to configure a scalar type is to use the single form. This form specifies to use one TypeScript type for all four situations.
Example:
scalarTypes:
String: string
With this setting, the String
scalar type will be mapped to the string
type in all four situations.
Fundamental scalar types such as String
, Int
, Float
and Boolean
are configured with the single form by default. These types always map to the same TypeScript types (i.e. string
, number
, number
and boolean
, respectively).
Send/Receive Form
The send/receive form is another shorthand form that specifies two TypeScript types for a scalar type: one for send situations, and the other for receive situations. They map to the four situations as follows:
Name | Send/Receive |
---|---|
resolverInput | receive |
resolverOutput | send |
operationInput | send |
operationOutput | receive |
Among the built-in scalar types, the ID
scalar type is configured with the send/receive form by default.
scalarTypes:
ID:
send: string | number
receive: string
With this setting, the ID
scalar type will be mapped to the string | number
type in the send situations, and to the string
type in the receive situations.
This setting reflects the fact that an ID
value is always serialized as a string, but the server can also accept an integer and coerce it to a string.
The coercion takes place when you receive a value from the network; namely, when you receive an operation output and when you receive a resolver input. Hence the receive type used for these situations.
In contrast, when you send a value to the network, you can specify a value before coercion. This applies to when you generate a resolver output (that will be sent through the network) and when you specify an operation input.
Separate Form
The separate form allows you to specify four different types separately. This form is not used by built-in scalars.
You can use this form if you want to have full control on what type is used in each situation.
This form is supported for completeness, but we are not aware of any use case. If you know one, please get in touch!
Example:
scalarTypes:
Date:
resolverInput: string
resolverOutput: Date | string
operationInput: string
operationOutput: string
Notes on GraphQL Code Generator Compatibility
If you know or trying to migrate from GraphQL Code Generator, you may wonder about compatibility with GraphQL Code Generator's scalars
option.
Particularly, GraphQL Code Generator supports specifying a scalar's type as a pair of input
and output
types.
You should note that the input/output configuration is not equivalent to nitrogql's send/receive configuration mode. The following table shows the correspondence between the two:
Name | input/output | send/receive |
---|---|---|
resolverInput | input | receive |
resolverOutput | output | send |
operationInput | input | send |
operationOutput | output | receive |
We have chosen to diverge from GraphQL Code Generator here because we found that the send/receive model better reflects the reality than the input/output model.
When you are migrating from GraphQL Code Generator, we recommend to translate the input/output setting to the send/receive setting.
If you need to keep the behavior as-is, you can use the separate form instead.