Search code examples
node.jsgraphqlapollographql-js

GraphQL not calling custom scalar methods


As many others, I'm trying to create my own Date scalar using GraphQL with JavaScript. I've read many examples and followed this guide from apollo in particular.

What I've done

  • Added scalar Date to my schema
  • Created an instance of GraphQLScalarType, with my implementations of serialize, parseValue and parseLiteral. I called that instance dateScalar
  • Added a new property to the resolvers map that is called Date and its value is dateScalar

This should make my Date ready to be used according to what I've read. However, the value I get every time I do a query is exactly the same value that I have stored in the database. If that was a string, I see a string, if that was a number, I see a number. It's not parsed or serialized at any moment.

This is how my files look like.

schema.js

const { buildSchema } = require('graphql');

const schema = buildSchema(`
  scalar Date

  # more schema
`);

module.exports = schema;

root.js

const { dateScalar } = require('./customScalars');

const root = {
  // queries

  // mutations

  // scalars
  Date: dateScalar
};

module.exports = root;

customScalars.js

const { GraphQLScalarType } = require('graphql');

const dateScalar = new GraphQLScalarType({
  name: 'Date',
  description: 'This is a scalar version of the js Date',
  serialize(value) {
    console.log('call serialize');
    return value.getTime();
  },
  parseValue(value) {
    console.log('call parseValue');
    return new Date(value).getFullYear();
  },
  parseLiteral(ast) {
    console.log('call parseLiteral');
    return;
  }
});

module.exports = {
  dateScalar
};

server.js

const express = require('express');
const graphqlHTTP = require('express-graphql');

const schema = require('./graphql/schema.js');
const graphqlRoot = require('./graphql/root.js');

var app = express();
app.use('/endpoint', graphqlHTTP({
  schema: schema,
  graphiql: true,
  rootValue: graphqlRoot,
}));

app.listen(3333, () => console.log('Now browse to localhost:3333/endpoint'));

My debug so far

  • I've used logs on the methodsserialize, etc, and none of them is called.
  • I've removed Date property from the root, and the behaviour is exactly the same
  • I've checked that the instance of dateScalar does what I expect. I've done so by calling console.log(dateScalar.serialize(new Date().getTime())) just after importing it in different files. When doing that I get the log telling me that serialize was called, and the result is what I was expecting.

It seems like at the root it never linked the scalar from my schema with my custom scalar instance. Maybe I'm doing something wrong here?

My guess is that GraphQL is serializing and parsing using .toString(), as it doesn't find my implementations for serializing/parsing.

Any ideas about how to make GraphQL use my custom scalar?


Solution

  • tl;dr

    use graphql-tools to make an executable schema and use apollo-server-express instead of express-graphql


    Unfortunately I'm not familiar with express-graphql. So my solution requires replaching it with apollo-server-express. Luckily it doesn't require many changes.

    This is what works for me with some modifications to match your code.

    Install packages first: npm install apollo-server-express graphql-tools

    And here's the code:

    const {makeExecutableSchema} = require('graphql-tools')
    const {graphqlExpress} = require('apollo-server-express');
    
    const graphqlRoot = require('./graphql/root.js');
    
    //...
    
    const schemaDef = `
      scalar Date
    
      # more schema
    `;
    
    //build the schema
    const schema = makeExecutableSchema({
      typeDefs: [schemaDef],
      resolvers: graphqlRoot
    });
    
    // your new endpoint:
    app.use('/endpoint', bodyParser.json(), graphqlExpress({ schema }));
    

    Read more about Adding a GraphQL endpoint

    Also, with Apollo it is easy to have multiple schema files and resolvers files without having to worry about combining them manually. Here's an explanation from the documentation: Modularizing the schema