express-graphql icon indicating copy to clipboard operation
express-graphql copied to clipboard

use with ES6 modules triggers assertion in graphql

Open stevvvn opened this issue 6 years ago • 4 comments

The following does not work, though it seems to me it should ([email protected]):

import graphqlHTTP from 'express-graphql'; // @0.6.12
import { buildSchema } from 'graphql'; // @1.13.2
import express from 'express';

const app = express();
app.use('/graphql', graphqlHTTP({
  'schema': buildSchema('type Query { hello: String }'),
  'rootValue': { 'hello': () => 'Hello, world!' }
}));
app.listen(4000);
$ curl -s -X POST -H "Content-type: application/json" http://localhost:4000/graphql --data '{ "query": "{ hello }" }'|jq
{
  "errors": [
    {
      "message": "Cannot use GraphQLSchema \"[object Object]\" from another module or realm.\n\nEnsure that there is only one instance of \"graphql\" in the node_modules\ndirectory. If different versions of \"graphql\" are the dependencies of other\nrelied on modules, use \"resolutions\" to ensure only one version is installed.\n\nhttps://yarnpkg.com/en/docs/selective-version-resolutions\n\nDuplicate \"graphql\" modules cannot be used at the same time since different\nversions may have different capabilities and behavior. The data from one\nversion used in the function from another could produce confusing and\nspurious results."
    }
  ]
}

Creating a wrapper to require buildSchema instead:

req.js

module.exports = require('graphql').buildSchema;

And changing the initial example:

//import { buildSchema } from 'graphql';
import buildSchema from './req';

works as expected:

$ curl -s -X POST -H "Content-type: application/json" http://localhost:4000/graphql --data '{ "query": "{ hello }" }'|jq
{
  "data": {
    "hello": "Hello, world!"
  }
}

stevvvn avatar Apr 07 '18 23:04 stevvvn

I've been investigating this and I don't have a solution, but I'll share what I found.

The core of the problem is that there are two different ways to get a GraphQLSchema:

  • import { buildSchema } from 'graphql' ends up using node_modules/graphql/type/schema.mjs (an ES6 module).
  • import graphqlHTTP from 'express-graphql' does require('graphql'), which ends up loading node_modules/graphql/type/schema.js (a CJS module).

When passing a schema created with buildSchema (so an instance of GraphQLSchema defined as a ES6 module) to express-graphql, it validates that the schema is instanceOf GraphQLSchema. But because express-graphql uses the CJS version, GraphQLSchema is a different class and therefore the passed schema is not an instance of such class.

I think that the solution would be to have a index.mjs that uses import to load everything related to Graphql, so both modules end up using the same GraphQLSchema, but I'm not 100% sure

scinos avatar Apr 22 '18 12:04 scinos

Ideally two things should happen:

  1. Add native ESM support to this package via .mjs, similar to graphql: https://github.com/graphql/graphql-js/pull/1244. This would fix this bug when the consumer uses express-graphql directly with --experimental-modules. It will not fix the issue when other packages such as koa-graphql that do not yet support native ESM use express-graphql internally via CJS.
  2. Somehow fix the graphql assertion to detect and allow simultaneous use of the CJS and ESM versions of itself if they are from the same package, or just remove it. This will be pretty desirable for true CJS/ESM cross compatibility as other packages should be able to use either or both versions simultaneously.

jaydenseric avatar May 02 '18 00:05 jaydenseric

Until the smart people figure this out, I managed to work around this issue by using custom module loader:

export function resolve(specifier, parentModuleURL, defaultResolver) {
	const resolvedModule = defaultResolver(specifier, parentModuleURL);
	if (specifier === 'graphql') {
		resolvedModule.url = resolvedModule.url.replace('index.mjs', 'index.js');
		resolvedModule.format = 'cjs';
	}
	return resolvedModule;
}

This will force the cjs version of grahql. (see https://nodejs.org/api/esm.html#esm_loader_hooks on how to use it).

You can actually extend the module loader further to force esm in your project. This will allow you to use js extension instead of mjs until the ESM api is less experimental and the mjs extension is hopefully no longer needed.

martinkadlec0 avatar Dec 11 '18 17:12 martinkadlec0

This library has been deprecated and this repo will be archived soon. It has been superseded by graphql-http.

Furthermore, if you seek a fully-featured, well-maintained and performant server - I heavily recommend GraphQL Yoga!

enisdenjo avatar Mar 20 '23 12:03 enisdenjo