mirror of
https://github.com/vale981/apollo-server
synced 2025-03-06 18:21:40 -05:00

* Fix Typo resovers -> resolvers * Fix typo migh -> might * Fix typo resover -> resolver again
327 lines
8.9 KiB
Markdown
327 lines
8.9 KiB
Markdown
---
|
|
title: Custom scalars and enums
|
|
description: Add custom scalar and enum types to a schema.
|
|
---
|
|
|
|
The GraphQL specification includes the following default scalar types: `Int`, `Float`, `String`, `Boolean` and `ID`. While this covers most of the use cases, some need to support custom atomic data types (e.g. `Date`), or add validation an existing type. To enable this, GraphQL allows custom scalar types. Enumerations are similar to custom scalars with the limitation that their values can only be one of a pre-defined list of strings.
|
|
|
|
<h2 id="custom-scalars">Custom scalars</h2>
|
|
|
|
To define a custom scalar, add it to the schema string with the following notation:
|
|
|
|
```js
|
|
scalar MyCustomScalar
|
|
```
|
|
|
|
Afterwards, define the behavior of a `MyCustomScalar` custom scalar by passing an instance of the [`GraphQLScalarType`](http://graphql.org/graphql-js/type/#graphqlscalartype) class in the [resolver map](https://www.apollographql.com/docs/graphql-tools/resolvers.html#Resolver-map). This instance can be defined with a [dependency](#Using-a-package) or in [source code](#graphqlscalartype).
|
|
|
|
For more information about GraphQL's type system, please refer to the [official documentation](http://graphql.org/graphql-js/type/) or to the [Learning GraphQL](https://github.com/mugli/learning-graphql/blob/master/7.%20Deep%20Dive%20into%20GraphQL%20Type%20System.md) tutorial.
|
|
|
|
Note that [Apollo Client does not currently have a way to automatically interpret custom scalars](https://github.com/apollostack/apollo-client/issues/585), so there's no way to automatically reverse the serialization on the client.
|
|
|
|
### Using a package
|
|
|
|
Here, we'll take the [graphql-type-json](https://github.com/taion/graphql-type-json) package as an example to demonstrate what can be done. This npm package defines a JSON GraphQL scalar type.
|
|
|
|
Add the `graphql-type-json` package to the project's dependencies :
|
|
|
|
```shell
|
|
$ npm install --save graphql-type-json
|
|
```
|
|
|
|
In code, require the type defined by in the npm package and use it :
|
|
|
|
```js
|
|
const { ApolloServer, gql } = require('apollo-server');
|
|
const GraphQLJSON = require('graphql-type-json');
|
|
|
|
const schemaString = gql`
|
|
|
|
scalar JSON
|
|
|
|
type Foo {
|
|
aField: JSON
|
|
}
|
|
|
|
type Query {
|
|
foo: Foo
|
|
}
|
|
|
|
`;
|
|
|
|
const resolveFunctions = {
|
|
JSON: GraphQLJSON
|
|
};
|
|
|
|
const server = new ApolloServer({ typeDefs: schemaString, resolvers: resolveFunctions });
|
|
|
|
server.listen().then(({ url }) => {
|
|
console.log(`🚀 Server ready at ${url}`)
|
|
});
|
|
```
|
|
|
|
Remark : `GraphQLJSON` is a [`GraphQLScalarType`](http://graphql.org/graphql-js/type/#graphqlscalartype) instance.
|
|
|
|
<h3 id="graphqlscalartype" title="GraphQLScalarType">Custom `GraphQLScalarType` instance</h3>
|
|
|
|
Defining a [GraphQLScalarType](http://graphql.org/graphql-js/type/#graphqlscalartype) instance provides more control over the custom scalar and can be added to Apollo server in the following way:
|
|
|
|
```js
|
|
const { ApolloServer, gql } = require('apollo-server');
|
|
const { GraphQLScalarType, Kind } = require('graphql');
|
|
|
|
const myCustomScalarType = new GraphQLScalarType({
|
|
name: 'MyCustomScalar',
|
|
description: 'Description of my custom scalar type',
|
|
serialize(value) {
|
|
let result;
|
|
// Implement custom behavior by setting the 'result' variable
|
|
return result;
|
|
},
|
|
parseValue(value) {
|
|
let result;
|
|
// Implement custom behavior here by setting the 'result' variable
|
|
return result;
|
|
},
|
|
parseLiteral(ast) {
|
|
switch (ast.kind) {
|
|
case Kind.Int:
|
|
// return a literal value, such as 1 or 'static string'
|
|
}
|
|
}
|
|
});
|
|
|
|
const schemaString = gql`
|
|
|
|
scalar MyCustomScalar
|
|
|
|
type Foo {
|
|
aField: MyCustomScalar
|
|
}
|
|
|
|
type Query {
|
|
foo: Foo
|
|
}
|
|
|
|
`;
|
|
|
|
const resolverFunctions = {
|
|
MyCustomScalar: myCustomScalarType
|
|
};
|
|
|
|
const server = new ApolloServer({ typeDefs: schemaString, resolvers: resolveFunctions });
|
|
|
|
server.listen().then(({ url }) => {
|
|
console.log(`🚀 Server ready at ${url}`)
|
|
});
|
|
```
|
|
|
|
<h2 id="examples">Custom scalar examples</h2>
|
|
|
|
Let's look at a couple of examples to demonstrate how a custom scalar type can be defined.
|
|
|
|
### Date as a scalar
|
|
|
|
The goal is to define a `Date` data type for returning `Date` values from the database. Let's say we're using a MongoDB driver that uses the native JavaScript `Date` data type. The `Date` data type can be easily serialized as a number using the [`getTime()` method](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getTime). Therefore, we would like our GraphQL server to send and receive `Date`s as numbers when serializing to JSON. This number will be resolved to a `Date` on the server representing the date value. On the client, the user can simply create a new date from the received numeric value.
|
|
|
|
The following is the implementation of the `Date` data type. First, the schema:
|
|
|
|
```js
|
|
const typeDefs = gql`scalar Date
|
|
|
|
type MyType {
|
|
created: Date
|
|
}
|
|
`
|
|
```
|
|
|
|
Next, the resolver:
|
|
|
|
```js
|
|
const { GraphQLScalarType } = require('graphql');
|
|
const { Kind } = require('graphql/language');
|
|
|
|
const resolvers = {
|
|
Date: new GraphQLScalarType({
|
|
name: 'Date',
|
|
description: 'Date custom scalar type',
|
|
parseValue(value) {
|
|
return new Date(value); // value from the client
|
|
},
|
|
serialize(value) {
|
|
return value.getTime(); // value sent to the client
|
|
},
|
|
parseLiteral(ast) {
|
|
if (ast.kind === Kind.INT) {
|
|
return parseInt(ast.value, 10); // ast value is always in string format
|
|
}
|
|
return null;
|
|
},
|
|
}),
|
|
};
|
|
|
|
const server = new ApolloServer({ typeDefs, resolvers });
|
|
|
|
server.listen().then(({ url }) => {
|
|
console.log(`🚀 Server ready at ${url}`)
|
|
});
|
|
```
|
|
|
|
### Validations
|
|
|
|
In this example, we follow the [official GraphQL documentation](http://graphql.org/docs/api-reference-type-system/) for the scalar datatype, which demonstrates how to validate a database field that should only contain odd numbers in GraphQL. First, the schema:
|
|
|
|
```js
|
|
const typeDefs = gql`scalar Odd
|
|
|
|
type MyType {
|
|
oddValue: Odd
|
|
}
|
|
`
|
|
```
|
|
|
|
Next, the resolver:
|
|
|
|
```js
|
|
const { ApolloServer, gql } = require('apollo-server');
|
|
const { GraphQLScalarType } = require('graphql');
|
|
const { Kind } = require('graphql/language');
|
|
|
|
function oddValue(value) {
|
|
return value % 2 === 1 ? value : null;
|
|
}
|
|
|
|
const resolvers = {
|
|
Odd: new GraphQLScalarType({
|
|
name: 'Odd',
|
|
description: 'Odd custom scalar type',
|
|
parseValue: oddValue,
|
|
serialize: oddValue,
|
|
parseLiteral(ast) {
|
|
if (ast.kind === Kind.INT) {
|
|
return oddValue(parseInt(ast.value, 10));
|
|
}
|
|
return null;
|
|
},
|
|
}),
|
|
};
|
|
|
|
const server = new ApolloServer({ typeDefs, resolvers });
|
|
|
|
server.listen().then(({ url }) => {
|
|
console.log(`🚀 Server ready at ${url}`)
|
|
});
|
|
```
|
|
|
|
<h2 id="enums">Enums</h2>
|
|
|
|
An Enum is similar to a scalar type, but it can only be one of several values defined in the schema. Enums are most useful in a situation where the user must pick from a prescribed list of options. Additionally enums improve development velocity, since they will auto-complete in tools like GraphQL Playground.
|
|
|
|
In the schema language, an enum looks like this:
|
|
|
|
```graphql
|
|
enum AllowedColor {
|
|
RED
|
|
GREEN
|
|
BLUE
|
|
}
|
|
```
|
|
|
|
An enum can be used anywhere a scalar can be:
|
|
|
|
```graphql
|
|
type Query {
|
|
favoriteColor: AllowedColor # As a return value
|
|
avatar(borderColor: AllowedColor): String # As an argument
|
|
}
|
|
```
|
|
|
|
A query might look like this:
|
|
|
|
```graphql
|
|
query {
|
|
avatar(borderColor: RED)
|
|
}
|
|
```
|
|
|
|
To pass the enum value as a variable, use a string of JSON, like so:
|
|
|
|
```graphql
|
|
query MyAvatar($color: AllowedColor) {
|
|
avatar(borderColor: $color)
|
|
}
|
|
```
|
|
|
|
```js
|
|
{
|
|
"color": "RED"
|
|
}
|
|
```
|
|
|
|
Putting it all together:
|
|
|
|
```js
|
|
const { ApolloServer, gql } = require('apollo-server');
|
|
|
|
const typeDefs = gql`
|
|
enum AllowedColor {
|
|
RED
|
|
GREEN
|
|
BLUE
|
|
}
|
|
|
|
type Query {
|
|
favoriteColor: AllowedColor # As a return value
|
|
avatar(borderColor: AllowedColor): String # As an argument
|
|
}
|
|
`;
|
|
|
|
const resolvers = {
|
|
Query: {
|
|
favoriteColor: () => 'RED',
|
|
avatar: (root, args) => {
|
|
// args.favoriteColor is 'RED', 'GREEN', or 'BLUE'
|
|
},
|
|
}
|
|
};
|
|
|
|
const server = new ApolloServer({ typeDefs, resolvers });
|
|
|
|
server.listen().then(({ url }) => {
|
|
console.log(`🚀 Server ready at ${url}`)
|
|
});
|
|
```
|
|
|
|
<h3 id="internal-values">Internal values</h3>
|
|
|
|
Sometimes a backend forces a different value for an enum internally than in the public API. In this exmple the API contains `RED`, however in resolvers we use `#f00` instead. The `resolvers` argument to `ApolloServer` allows the addition custom values to enums that only exist internally:
|
|
|
|
```js
|
|
const resolvers = {
|
|
AllowedColor: {
|
|
RED: '#f00',
|
|
GREEN: '#0f0',
|
|
BLUE: '#00f',
|
|
}
|
|
};
|
|
```
|
|
|
|
These don't change the public API at all and the resolvers accept these value instead of the schema value, like so:
|
|
|
|
```js
|
|
const resolvers = {
|
|
AllowedColor: {
|
|
RED: '#f00',
|
|
GREEN: '#0f0',
|
|
BLUE: '#00f',
|
|
},
|
|
Query: {
|
|
favoriteColor: () => '#f00',
|
|
avatar: (root, args) => {
|
|
// args.favoriteColor is '#f00', '#0f0', or '#00f'
|
|
},
|
|
}
|
|
};
|
|
```
|
|
|
|
Most of the time, this feature of enums isn't used unless interoperating with another library that expects its values in a different form.
|