Grapes application setup

First things first, we need to setup our grapes aka graphql-relay-authentication application. And the problems start.

Setup of a GraphQL relay application

A GraphQL relay application comes with 2 components:

Fortunately, there is a github project which can help us here: fortruce/relay-skeleton.

This project gives us the boilerplate setup for a React, Relay, GraphQL project skeleton. Thank you Joseph.

React, Relay, GraphQL technology stack

This project comes with the usual stack:

And the first issues

As I did it a couple of times in the past on this blog, I like to enhance a little bit the backend GraphQL server with 2 things:

The new express middleware is easy to add:

import express from 'express';
import { Schema } from './data/schema';
import graphQLHTTP from 'express-graphql';
// ADD: import fot the print utilities
import { printSchema } from 'graphql/utilities/schemaPrinter';
import path from 'path';

const app = express();
app.use(express.static(path.resolve(__dirname, 'public')));
// ADD: a middleware to call the print on the Schema
app.use('/schema',function(req,res,next) {
    res.set('Content-Type', 'text/plain');
    res.send(printSchema(Schema));
  });
app.use('/', graphQLHTTP({ schema: Schema, pretty: true }));
app.listen(8080, (err) => {
  if (err) {
    return console.error(err);
  }
  console.log('GraphQL Server is now running on localhost:8080');
});

And, as I may have hinted you with the title of the section, it didn’t work:

Error
    at invariant (/Users/pierrecarion/work/graphql-relay-authentication/build/webpack:/~/graphql/jsutils/invariant.js:20:1)
    at printType (/Users/pierrecarion/work/graphql-relay-authentication/build/webpack:/~/graphql/utilities/schemaPrinter.js:81:1)
    at Array.map (native)
    at printFilteredSchema (/Users/pierrecarion/work/graphql-relay-authentication/build/webpack:/~/graphql/utilities/schemaPrinter.js:64:1)

I did that about 10 times in the past and it always worked!

After some debugging, I found the code throwing this error in graphql/utilities/schemaPrinter.js:

function printType(type) {
  if (type instanceof _typeDefinition.GraphQLScalarType) {
    return printScalar(type);
  } else if (type instanceof _typeDefinition.GraphQLObjectType) {
    return printObject(type);
  } else if (type instanceof _typeDefinition.GraphQLInterfaceType) {
    return printInterface(type);
  } else if (type instanceof _typeDefinition.GraphQLUnionType) {
    return printUnion(type);
  } else if (type instanceof _typeDefinition.GraphQLEnumType) {
    return printEnum(type);
  }
  // Exception thrown by this invariant:
  (0, _jsutilsInvariant2['default'])(type instanceof _typeDefinition.GraphQLInputObjectType);
  return printInputObject(type);
}

If I removed the invariant, the schema was being displayed correctly…

I added a log to display the type of the type:

console.log('@@@ ctor name:' + type.constructor.name);

and the log printed: GraphQLObjectType but for some reason this type was not caught in the previous if statements.

This kind of bug reminded me of the good old times of java programming where such a test could fail could happen if the same class was loaded by 2 different class loaders.

No class loader here in javascript but … the skeleton code was using webpack to bundle the server code.

No webpack for server

webpack is usually used to bundle javascript code for the web components.

Using webpack, you can write your front end code using the regular import/require code: webpack processes all your client code and generate a single javascript code with the dependencies resolved (webpack can do much more than that but we’ll cover that in another post)

Even though there is no reason not to use webpack for server code, I do think that the error was caused by webpack.

My thinking is that there was 2 GraphQLObjectType classes in the runtime:

No proof for that intuition, but as soon as I removed webpack to bundle the server code, the error disappeared.

babel to bundle server code

A more usual way to bundle ES6 server code is to use babel (along with grunt here):

// generate the server using babel
gulp.task('babel-server', function () {
  return gulp.src("src/server/**/*.js")
    .pipe(sourcemaps.init())
    .pipe(babel())
    .pipe(sourcemaps.write("."))
    .pipe(gulp.dest("build"));
});

// recompile the schema whenever .js files in data are updated
gulp.task('watch-schema', () => {
  gulp.watch(path.join(__dirname, './src/server/data', '**/*.js'), ['generate-schema','babel-server']);
});

gulp.task('server', ['babel-server', 'watch-schema'], () => {
  nodemon({
    execMap: {
      js: 'node'
    },
    script: path.join(__dirname, 'build', 'server.js'),
    watch: ['build/'],
    ext: 'js'
  }).on('restart', () => {
    console.log('[nodemon]: restart');
  });
});

Using a nodemon and gulp watch tasks, I have automatic relaoding of the server.

And the /schema URL is working now.

Was not that bad after all.