Apollo is a beautifully cohesive set of tools for quickly and efficiently building out a GraphQL-powered project. Unfortunately, that cohesiveness can cause problems when you try to do something a little out of the norm.
Recently, I ran into a problem with trying to use traditional GraphQL schema types (like GraphQLObjectType
) together with an Apollo-generated schema.
Straight out of the box, these two approaches don’t play nicely together. However, I managed to put together a working, but less than ideal, solution.
Read on!
The Problem
Recently, I’ve been working on an Apollo-powered client project. Apollo schema strings are used to define types and schemas throughout the project.
While working on a new feature for the project, I began using Mongoose to model data living in a MongoDB database. Using Mongoose and GraphQL together meant I had to define two nearly identical schemas.
One schema for Mongoose:
export default new Schema({
_id: Number,
name: String,
roomId: Number
});
And another for GraphQL:
export default `
type Bed {
_id: Int
name: String
roomId: Int
room: Room
}
`;
From past experience, I knew that having to maintain both schemas would be a nightmare. They would inevitably diverge, either through laziness, forgetfulness, or ignorance, and that divergence would lead to problems down the road.
The solution to this problem, in my mind, was to generate the GraphQL schema from the Mongoose model.
Mongoose Schema to GraphQL
Thankfully, after a quick search I found a Node.js package to do exactly that: Sarkis Arutiunian’s mongoose-schema-to-graphql
.
As you would expect, the createType
function in the mongoose-schema-to-graphql
package accepts a Mongoose schema as an argument and returns a corresponding GraphQLObjectType
object as a result.
Armed with this tool, I could replace the Bed
GraphQL type definition with a call to createType
:
export default () => [
createType(Bed.schema),
`
extend type Bed {
room: Room
}
`
];
At least that was the dream…
Unfortunately, trying to pass the generated GraphQLObjectType
into Apollo’s makeExecutableSchema
fails. The typeDefs
option in makeExecutableSchema
only accepts an array of GraphQL schema strings, or a function that returns an array of GraphQL schema strings.
Trying to pass in a GraphQLObjectType
causes makeExecutableSchema
to blow up.
A (Less Than Ideal) Solution
Our mongoose-schema-to-graphql
package only returns raw GraphQL types, and makeExecutableSchema
only accepts schema strings.
Short of forking and extending Apollo’s graphql-tools
, it seems like our only option is to convert the GraphQLObjectType
returned by createType
into a string before handing it off to makeExecutableSchema
.
But how do we convert a GraphQLObjectType
into a corresponding GraphQL schema string?
export default () => [
printType(createType(Bed.schema)),
`
extend type Bed {
room: Room
}
`
];
The printType
utility function in Facebook’s graphql
package saves the day!
We can use the printType
function to convert the GraphQLObjectType
generated from our Mongoose model into a GraphQL schema string which we can then drop into makeExecutableSchema
.
Unfortunately, makeExecutableSchema
immediately undoes all of that work we just did and converts the GraphQL schema strings it was passed into raw GraphQL types.
Oh well, at least it works.
Coming to Terms
While the createType
/printType
conversion process works, I wasn’t happy with the solution.
In my throes of desperation, I opened an issue on the graphql-tools
project asking for either a better approach to this problem, or support for raw GraphQL types within makeExecutableSchema
.
Sashko confirmed my suspicions that there currently isn’t a better way of dealing with this situation. He mentioned that the ability to intermingle these two styles of schema creation within makeExecutableSchema
has been requested in the past, and pull requests are welcome.
Maybe I’ll build up the courage to dive deeper into graphql-tools
and look for a better solution.