After reading this walkthrough in the official documentation:
http://graphql.org/graphql-js/object-types/
I am very confused about how to make custom scalar type resolvers without a third party library. Here is the sample code in the docs:
var express = require('express');
var graphqlHTTP = require('express-graphql');
var { buildSchema } = require('graphql');
// Construct a schema, using GraphQL schema language
var schema = buildSchema(`
type RandomDie {
numSides: Int!
rollOnce: Int!
roll(numRolls: Int!): [Int]
}
type Query {
getDie(numSides: Int): RandomDie
}
`);
// This class implements the RandomDie GraphQL type
class RandomDie {
constructor(numSides) {
this.numSides = numSides;
}
rollOnce() {
return 1 + Math.floor(Math.random() * this.numSides);
}
roll({numRolls}) {
var output = [];
for (var i = 0; i < numRolls; i++) {
output.push(this.rollOnce());
}
return output;
}
}
// The root provides the top-level API endpoints
var root = {
getDie: function ({numSides}) {
return new RandomDie(numSides || 6);
}
}
var app = express();
app.use('/graphql', graphqlHTTP({
schema: schema,
rootValue: root,
graphiql: true,
}));
app.listen(4000);
console.log('Running a GraphQL API server at localhost:4000/graphql');
I understand I can use graphql-tools
to make "executable schema" from string-based type definitions and a resolvers object. What I'm wondering is why there is no lower level / imperative graphql-js
API I can use to define and resolve custom scalar types? In other words, how does graphql-tools
even work?
Thanks in advance!
Edit:
Here is some example code outlining the problem. On line 4 you can see that I am importing GraphQLJSON but it is never used. I know what to do to make this work using graphql-tools
but I want to learn how it works. In other words, if graphql-tools
did not exist, what would I do to inject a custom scalar type while still authoring my schema using graphql
syntax? From what I can tell the only graphql-js
solution is to use the non-declarative approach to authoring schema (second example below)
import express from 'express';
import graphqlHTTP from 'express-graphql';
import { buildSchema } from 'graphql';
import GraphQLJSON from 'graphql-type-json'; // where should I inject this?
const schema = buildSchema(`
type Image {
id: ID!
width: Int!
height: Int!
metadata: JSON!
}
type Query {
getImage(id: ID!): Image!
}
scalar JSON
`);
class Image {
constructor(id) {
this.id = id;
this.width = 640;
this.height = 480;
}
metadata() {
// what do I need to do in order to have this return value parsed by GraphQLJSON
return { foo: 'bar' };
}
}
const rootValue = {
getImage: function({ id }) {
return new Image(id);
},
};
const app = express();
app.use(
'/graphql',
graphqlHTTP({
schema: schema,
rootValue: rootValue,
graphiql: true,
})
);
app.listen(4000);
Running this query:
{
getImage(id: "foo") {
id
width
height
metadata
}
}
Results in this error:
Expected a value of type \"JSON\" but received: [object Object]
The answer I'm seeking would help me to return the JSON type without using graphql-tools
. I have nothing against this library, but it seems bizarre to me that I must use a third party library for something so fundamental to the type resolution system in graphql-js
. I would like to know more about why this dependency is needed before adopting it.
Here is another way to make this work:
import { GraphQLObjectType, GraphQLInt, GraphQLID } from 'graphql/type';
const foo = new GraphQLObjectType({
name: 'Image',
fields: {
id: { type: GraphQLID },
metadata: { type: GraphQLJSON },
width: { type: GraphQLInt },
height: { type: GraphQLInt },
},
});
However this does not allow me to author my schema using the graphql
syntax, which is my goal.
UPDATE
After some clarification, it looks like you are trying to add an a custom scalar to a schema created with schema language. Since schemas built buildSchema
(or other client tools) do not have handler functions for serialize
, parseValue
, and parseLiteral
bound, you need to modify the built schema to include those. you can do something like
import { buildSchema } from 'graphql'
import GraphQLJSON from 'graphql-type-json'
const definition = `
type Foo {
config: JSON
}
scalar JSON
Query {
readFoo: Foo
}
schema {
query: Query
}`
const schema = buildSchema(definition)
Object.assign(schema._typeMap.JSON, GraphQLJSON)
Alternately you can also do the following which may be useful for renaming the scalar to something else
Object.assign(schema._typeMap.JSON, {
name: 'JSON',
serialize: GraphQLJSON.serialize,
parseValue: GraphQLJSON.parseValue,
parseLiteral: GraphQLJSON.parseLiteral
})
Original Answer
buildSchema
indeed creates a schema but that schema will have no resolve, serialize, parseLiteral, etc. functions associated with it. I believe graphql-tools only allows you to map resolver functions to fields which does not help you when you are trying to create a custom scalar.
graphql-js
has a GraphQLScalarType
you can use to build custom scalars. see official documentation and example at http://graphql.org/graphql-js/type/#graphqlscalartype
There are also several packages in npm that you can use as an example
one i find very useful is https://github.com/taion/graphql-type-json/blob/master/src/index.js
as an example if you wanted to create a base64 type that stores a string as base64 and decodes base64 strings before they are returned in the response you can create a custom base64 scalar like this
import { GraphQLScalarType, GraphQLError, Kind } from 'graphql'
const Base64Type = new GraphQLScalarType({
name: 'Base64',
description: 'Serializes and Deserializes Base64 strings',
serialize (value) {
return (new Buffer(value, 'base64')).toString()
},
parseValue (value) {
return (new Buffer(value)).toString('base64')
},
parseLiteral (ast) {
if (ast.kind !== Kind.STRING) {
throw new GraphQLError('Expected Base64 to be a string but got: ' + ast.kind, [ast])
}
return (new Buffer(ast.value)).toString('base64')
}
})
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With