Merge Graphql Schemas
A utility library to facilitate merging of modularized GraphQL schemas and resolver objects.
This tool:
- Reduces the complexity of Graphql server implementation.
- Modularize type and resolver files.
Table of Contents
Install
npm install -S merge-graphql-schemas
Usage
Merging type definitions
Let's say this is your current schema:
type Client {
id: ID!
name: String
age: Int
products: [Product]
}
type Product {
id: ID!
description: String
price: Int
}
type Query {
clients: [Client]
client(id: ID!): Client
products: [Product]
product(id: ID!): Product
}
type Mutation {
addClient(name: String!, age: Int!): Client
}
Knowing that your app will grow, you want to move your definitions to separate files that should look like the following.
export default `
type Client {
id: ID!
name: String
age: Int
products: [Product]
}
type Query {
clients: [Client]
client(id: ID!): Client
}
type Mutation {
addClient(name: String!, age: Int!): Client
}
`;
export default `
type Product {
id: ID!
description: String
price: Int
client: Client
}
type Query {
products: [Product]
product(id: ID!): Product
}
`;
There are two ways you can use this package:
- manually import each type
- import everything from a specified folder
Manually import each type
If you decide to have manual control of each file that gets merged, all you need is the mergeTypes
function:
import { mergeTypes } from 'merge-graphql-schemas';
import clientType from './clientType';
import productType from './productType';
const types = [
clientType,
productType,
];
export default mergeTypes(types);
Import everything from a specified folder
In this way we use the fileLoader
function to import all files from the specified folder.
import path from 'path';
import { fileLoader, mergeTypes } from 'merge-graphql-schemas';
const typesArray = fileLoader(path.join(__dirname, './types'));
export default mergeTypes(typesArray);
When using the fileLoader
function you can also implement your type definitions using .graphql
or .graphqls
files.
The fileLoader
function will by default ignore files named index.js
. This allows you to create your index file inside the actual types folder if desired.
type Client {
id: ID!
name: String
age: Int
products: [Product]
}
type Query {
clients: [Client]
client(id: ID!): Client
}
type Mutation {
addClient(name: String!, age: Int!): Client
}
type Product {
id: ID!
description: String
price: Int
client: Client
}
type Query {
products: [Product]
product(id: ID!): Product
}
You can also load files in nested folders by setting the recursive
option.
Given the file structure below:
+-- graphql
| +-- types
| | +-- subGroupA
| | | +-- typeA1.graphql
| | | +-- typeA2.graphql
| | +-- subGroupB
| | | +-- typeB1.graphql
| | | +-- typeB2.graphql
| | +-- index.js
Here's how your index
file could look like:
const path = require('path')
const mergeGraphqlSchemas = require('merge-graphql-schemas')
const fileLoader = mergeGraphqlSchemas.fileLoader
const mergeTypes = mergeGraphqlSchemas.mergeTypes
const typesArray = fileLoader(path.join(__dirname, '.'), { recursive: true })
module.exports = mergeTypes(typesArray)
You can also load files in different folders by passing a glob pattern in fileLoader
.
Given the file structure below:
+-- graphql
| +-- subGroupA
| | +-- typeA1.graphql
| | +-- typeA2.graphql
| +-- subGroupB
| | +-- typeB1.graphql
| | +-- typeB2.graphql
| +-- index.js
Here's how your index
file could look like:
const path = require('path')
const mergeGraphqlSchemas = require('merge-graphql-schemas')
const fileLoader = mergeGraphqlSchemas.fileLoader
const mergeTypes = mergeGraphqlSchemas.mergeTypes
const typesArray = fileLoader(path.join(__dirname, 'graphql/**/*.graphql'))
module.exports = mergeTypes(typesArray)
Merging nested Types
The mergeTypes
function also allows merging multiple schemas. In the situations where you would like to have multiple
types subfolders, you can merge your types on each subfolder and then everything into one single schema. See the example below:
+-- graphql
| +-- types
| | +-- subGroupA
| | | +-- index.js <<< Merges all types in subGroupA
| | | +-- typeA1.graphql
| | | +-- typeA2.graphql
| | +-- subGroupB
| | | +-- index.js <<< Merges all types in subGroupB
| | | +-- typeB1.graphql
| | | +-- typeB2.graphql
| | +-- index.js <<< Merges exports from subGroupA and subGroupB
Merging resolvers
Resolvers should be implemented as simple JS objects. Following our example, for the types we implemented
our resolvers should look like the following:
export default {
Query: {
clients: () => {},
client: () => {},
},
Mutation: {
addClient: () => {},
},
Client: {
products: () => {},
},
}
export default {
Query: {
products: () => {},
product: () => {},
},
Product: {
client: () => {},
},
}
Warning
If you are using graphqlHTTP
you don't need to separate the resolver into Query/Mutation/Subscription
, otherwise it won't work. The resolvers should look like the following:
export default {
clients: () => {},
client: () => {},
addClient: () => {},
Product: {
products: () => {},
},
}
export default {
products: () => {},
product: () => {},
Product: {
client: () => {},
},
}
Just like your type definitions, you can choose to import files manually:
import { mergeResolvers } from 'merge-graphql-schemas';
import clientResolver from './clientResolver';
import productResolver from './productResolver';
const resolvers = [
clientResolver,
productResolver,
];
export default mergeResolvers(resolvers);
Or automatically:
import path from 'path';
import { fileLoader, mergeResolvers } from 'merge-graphql-schemas';
const resolversArray = fileLoader(path.join(__dirname, './resolvers'));
export default mergeResolvers(resolversArray);
Beware that mergeResolvers
is simply merging plain Javascript objects together.
This means that you should be careful with Queries, Mutations or Subscriptions with naming conflicts.
You can also load files with specified extensions by setting the extensions option.
Only these values are supported now. '.ts', '.js', '.gql', '.graphql', '.graphqls'
import path from 'path';
import { fileLoader, mergeResolvers } from 'merge-graphql-schemas';
const resolversArray = fileLoader(path.join(__dirname, './resolvers'), { extensions: ['.js'] });
export default mergeResolvers(resolversArray);
Server setup
Here's an example using express-graphql:
import express from 'express';
import graphqlHTTP from 'express-graphql';
import { buildSchema } from 'graphql';
import typeDefs from './graphql/types/index';
import rootValue from './graphql/resolvers/index';
const schema = buildSchema(typeDefs);
const app = express();
app.use('/graphql', graphqlHTTP({
schema,
rootValue,
graphiql: true,
}));
app.listen(3000);
Or using apollo-server:
import express from 'express';
import { apolloExpress } from 'apollo-server';
import { makeExecutableSchema } from 'graphql-tools';
import { graphiqlExpress } from 'apollo-server';
import bodyParser from 'body-parser';
import typeDefs from './graphql/typeDefs';
import resolvers from './graphql/resolvers';
const schema = makeExecutableSchema({ typeDefs, resolvers });
const app = express();
app.use(
'/graphql',
bodyParser.json(),
apolloExpress({ schema })
);
app.use('/graphiql', graphiqlExpress({ endpointURL: '/graphql' }));
app.listen(3000);
Maintainer
This is an open source package. We hope to deal with contributions in a timely manner, but that's not always the case. The main maintainer is:
@RodMachado
Feel free to ping if there are open issues or pull requests which are taking a while to be dealt with!
Contributing
Issues and Pull Requests are always welcome.
Please read OK Grow's global contribution guidelines.
If you are interested in becoming a maintainer, get in touch with us by sending an email or opening an issue. You should already have code merged into the project. Active contributors are encouraged to get in touch.
Please note that all interactions in @okgrow's repos should follow our Code of Conduct.
License
MIT © 2017 OK GROW!, https://www.okgrow.com.