1
votes

I have two node servers both serving graphql via express-graphql. I wish to have my first server stitch its schema with that of the second server.

I have followed these instructions at: https://www.apollographql.com/docs/graphql-tools/remote-schemas

as I am using graphql-tools.

I am able to retrieve the schema from my second node server but as soon as I try to print it (with a mere console.log) i get this error:

Uncaught exception TypeError: schema.getDirectives is not a function
    at printFilteredSchema (/Users/cchabert/git-repo/client-configuration-api/node_modules/graphql/utilities/schemaPrinter.js:61:27)
    at Object.printSchema (/Users/cchabert/git-repo/client-configuration-api/node_modules/graphql/utilities/schemaPrinter.js:47:10)
    at makeRemoteExecutableSchema (/Users/cchabert/git-repo/client-configuration-api/node_modules/graphql-tools/dist/stitching/makeRemoteExecutableSchema.js:60:30)
    at schema (/Users/cchabert/git-repo/client-configuration-api/app/api/schema.js:68:24)
    at module.exports (/Users/cchabert/git-repo/client-configuration-api/app/routes.js:102:13)
    at Object.<anonymous> (/Users/cchabert/git-repo/client-configuration-api/app/index.js:15:20)
    at Module._compile (internal/modules/cjs/loader.js:799:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:810:10)
    at Module.load (internal/modules/cjs/loader.js:666:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:606:12)
    at Function.Module._load (internal/modules/cjs/loader.js:598:3)
    at Function.Module.runMain (internal/modules/cjs/loader.js:862:12)
    at internal/main/run_main_module.js:21:11

which causes the server to die but I am also able to see the schema somehow:

received schema:  {"_queryType":"Query","_mutationType":"Mutation",
"_subscriptionType":null,"_directives":["@skip","@include","@deprecated"],
"_typeMap":{"Query":"Query","String":"String","Client":"Client",
"ID":"ID","DateTime":"DateTime",[...]}

I do see the _directives field in the remote schema definition but the docs isn't super clear on how to deal with this.

I have looked through the github issues of the graphql-tools repo as well but couldn't find anything.

Here is a code snippet:

const {
  addMockFunctionsToSchema,
  makeExecutableSchema,
  makeRemoteExecutableSchema,
  introspectSchema,
  mergeSchemas
} = require('graphql-tools')
const _ = require('lodash)
const { createHttpLink } = require('apollo-link-http')
const fetch = require('node-fetch')

[..]
const customFetch = (uri, options = {}) => {
  const httpOptions = _.merge(options, {
    headers: {
      'Content-type': 'application/json'
    }
  })
  return fetch(uri, httpOptions)
}

function schema() {
  const Query = `
    type Query {
      _empty: String
    }
    type Mutation {
      _empty: String
    }
  `
  const resolvers = {}

  const mocks = {}

  const localSchema = makeExecutableSchema({
    typeDefs: [Query, [...]],
    resolvers: [resolvers, [...]]
  }) // by itself this schema works without any issues

  const mergedMocks = _.merge(mocks, [...])

  addMockFunctionsToSchema({
    schema: localSchema,
    mocks: mergedMocks,
    preserveResolvers: true
  })

  const infoApiLink = createHttpLink({ uri, fetch: customFetch })

  const remoteSchema = makeRemoteExecutableSchema({
    schema: introspectSchema(infoApiLink).then(remoteSchema => {
      console.log('received schema: ', JSON.stringify(remoteSchema))
      return remoteSchema
    }),
    link: infoApiLink
  })

  return mergeSchemas({ schemas: [localSchema, remoteSchema] })
}

module.exports = {
  schema
}

I would also like to make this work using only Promises (no async/await) as mentioned in https://github.com/apollographql/graphql-tools/blob/master/docs/source/remote-schemas.md#--introspectschemafetcher-context

Any suggestions welcome.

1

1 Answers

1
votes

makeRemoteExecutableSchema should be passed an instance of a GraphQLSchema, but you're not doing that -- what you are doing is passing it a Promise that will resolve to a GraphQLSchema, which won't work. When you call introspectSchema, it has to make an introspection call, which is done asynchronously. It returns a Promise that resolves to the resulting GraphQLSchema object. We need to use await or then in order to get that value and then we can use it as needed.

The unnecessarily messy way without async/await:

function schema () {
  // ... rest of your code
  return introspectSchema(infoApiLink).then(schema => {
    const remoteSchema = makeRemoteExecutableSchema({ schema, link: infoApiLink })
    return mergeSchemas({ schemas: [localSchema, remoteSchema] })
  })
}

Or using async/await:

async function schema () {
  // ... rest of your code
  const schema = await introspectSchema(infoApiLink)
  const remoteSchema = makeRemoteExecutableSchema({ schema, link: infoApiLink })
  return mergeSchemas({ schemas: [localSchema, remoteSchema] })

}

Either way, keep in mind by calling your schema function you will be returning a Promise that will resolve to the value returned by mergeSchemas. So where before you could have imported the function, called it and used the result directly, you will once again have to use either then or await to grab the value the Promise resolves to first:

import { schema } from './some-module'

schema()
  .then(schema => {
    const server = new ApolloServer({ schema })
    server.listen()
  })
  .catch(error => {
    // handle the Promise rejecting
  })