okgrow/merge-graphql-schemas

Name: merge-graphql-schemas

Owner: OK GROW!

Description: A utility library to facilitate merging of modularized GraphQL schemas and resolver objects.

Created: 2016-12-23 18:08:52.0

Updated: 2018-05-24 04:21:05.0

Pushed: 2018-05-02 13:09:26.0

Homepage: https://www.npmjs.com/package/merge-graphql-schemas

Size: 417

Language: JavaScript

GitHub Committers

UserMost Recent Commit# Commits

Other Committers

UserEmailMost Recent Commit# Commits

README

Merge Graphql Schemas

Build Status npm version npm downloads

A utility library to facilitate merging of modularized GraphQL schemas and resolver objects.

This tool:

Table of Contents
Install
install -S merge-graphql-schemas
Usage
Merging type definitions

Let's say this is your current schema:

 Client {
: ID!
me: String
e: Int
oducts: [Product]


 Product {
: ID!
scription: String
ice: Int


 Query {
ients: [Client]
ient(id: ID!): Client
oducts: [Product]
oduct(id: ID!): Product


 Mutation {
dClient(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.

/graphql/types/clientType.js
rt default `
pe Client {
id: ID!
name: String
age: Int
products: [Product]


pe Query {
clients: [Client]
client(id: ID!): Client


pe Mutation {
addClient(name: String!, age: Int!): Client



/graphql/types/productType.js
rt default `
pe Product {
id: ID!
description: String
price: Int
client: Client


pe Query {
products: [Product]
product(id: ID!): Product


There are two ways you can use this package:

Manually import each type

If you decide to have manual control of each file that gets merged, all you need is the mergeTypes(types) function.

Ability to merge a GQL Type defined multiple times in separate files. Will throw an error when fieldDefintitons have conflicting values defined. See PR #118 for more details.

/graphql/types/index.js
rt { mergeTypes } from 'merge-graphql-schemas';
rt clientType from './clientType';
rt productType from './productType';

t types = [
ientType,
oductType,


OTE: 2nd param is optional, and defaults to false
nly use if you have defined the same type multiple times in
ifferent files and wish to attempt merging them together.
rt default mergeTypes(types, { all: true });
Import everything from a specified folder

In this way we use the fileLoader function to import all files from the specified folder.

/graphql/typeDefs.js
rt path from 'path';
rt { fileLoader, mergeTypes } from 'merge-graphql-schemas';

t typesArray = fileLoader(path.join(__dirname, './types'));

rt default mergeTypes(typesArray, { all: true });

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 or index.ts. This allows you to create your index file inside the actual types folder if desired.

graphql/types/clientType.graphql
 Client {
: ID!
me: String
e: Int
oducts: [Product]


 Query {
ients: [Client]
ient(id: ID!): Client


 Mutation {
dClient(name: String!, age: Int!): Client


graphql/types/productType.graphql
 Product {
: ID!
scription: String
ice: Int
ient: Client


 Query {
oducts: [Product]
oduct(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:

t path = require('path')
t mergeGraphqlSchemas = require('merge-graphql-schemas')
t fileLoader = mergeGraphqlSchemas.fileLoader
t mergeTypes = mergeGraphqlSchemas.mergeTypes

t typesArray = fileLoader(path.join(__dirname, '.'), { recursive: true })

le.exports = mergeTypes(typesArray, { all: true })

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:

t path = require('path')
t mergeGraphqlSchemas = require('merge-graphql-schemas')
t fileLoader = mergeGraphqlSchemas.fileLoader
t mergeTypes = mergeGraphqlSchemas.mergeTypes

t typesArray = fileLoader(path.join(__dirname, 'graphql/**/*.graphql'))

le.exports = mergeTypes(typesArray, { all: true })
Output the string of typeDefs

Since the output of mergeTypes is just a string, after you merge your types, you can save it to a file to be passed around to other systems. Here is an example using ES6 modules:

rt { fileLoader, mergeTypes } from 'merge-graphql-schemas'
rt { writeFileSync } from 'fs'

t typeDefs = mergeTypes(fileLoader(`${__dirname}/schema/**/*.graphql`), { all: true })
eFileSync('joined.graphql', typeDefs)
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:

/graphql/resolvers/clientResolver.js
rt default {
ery: {
clients: () => {},
client: () => {},

tation: {
addClient: () => {},

ient: {
products: () => {},



/graphql/resolvers/productResolver.js
rt default {
ery: {
products: () => {},
product: () => {},

oduct: {
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:

/graphql/resolvers/clientResolver.js
rt default {
 Query
ients: () => {},
ient: () => {},

 Mutation
dClient: () => {},

oduct: {
products: () => {},



/graphql/resolvers/productResolver.js
rt default {
 Query
oducts: () => {},
oduct: () => {},

oduct: {
client: () => {},


Just like your type definitions, you can choose to import files manually:

/graphql/resolvers/index.js
rt { mergeResolvers } from 'merge-graphql-schemas';
rt clientResolver from './clientResolver';
rt productResolver from './productResolver';

t resolvers = [
ientResolver,
oductResolver,


rt default mergeResolvers(resolvers);

Or automatically:

/graphql/resolvers.js
rt path from 'path';
rt { fileLoader, mergeResolvers } from 'merge-graphql-schemas';

t resolversArray = fileLoader(path.join(__dirname, './resolvers'));

rt 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'

/graphql/resolvers.js
rt path from 'path';
rt { fileLoader, mergeResolvers } from 'merge-graphql-schemas';

t resolversArray = fileLoader(path.join(__dirname, './resolvers'), { extensions: ['.js'] });

rt default mergeResolvers(resolversArray);

Optional: Automatic with Resolver Naming Convention

If you would like to use the automated fileLoader approach but would like complete freedom over the structure of your resolver files, then simply use a resolver file naming convention like, [file].resolvers.js/ts.

Then setup your fileLoader like so, and you're in business:

/graphql/resolvers/index.js/ts
rt path from 'path';
rt { fileLoader, mergeResolvers } from 'merge-graphql-schemas';

t resolversArray = fileLoader(path.join(__dirname, "./**/*.resolvers.*"));
rt default mergeResolvers(resolversArray);

With this approach, you're free to structure resolver files as you see fit. Of course, unique naming of Queries, Mutations and Subscriptions still applies!

Now you can structure by function

graphql
+-- resolvers
|   +-- author.resolvers.js/ts
|   +-- book.resolvers.js/ts
|   +-- index.ts  <<< Merges all `*.resolvers.*` files

Or by type

graphql
+-- entity
|   +-- author
|   |   +-- author.resolvers.js/ts
|   |   +-- ...
|   +-- book
|   |   +-- book.resolvers.js/ts
|   |   +-- ...
|   +-- index.ts <<< Merges all `*.resolvers.*` files
Server setup

Here's an example using express-graphql:

rt express from 'express';
rt graphqlHTTP from 'express-graphql';
rt { buildSchema } from 'graphql';

rt typeDefs from './graphql/types/index';
rt rootValue from './graphql/resolvers/index';

t schema = buildSchema(typeDefs);

t app = express();
use('/graphql', graphqlHTTP({
hema,
otValue,
aphiql: true,


listen(3000);

Or using apollo-server:

rt express from 'express';
rt { apolloExpress } from 'apollo-server';
rt { makeExecutableSchema } from 'graphql-tools';
rt { graphiqlExpress } from 'apollo-server';
rt bodyParser from 'body-parser';

rt typeDefs from './graphql/typeDefs';
rt resolvers from './graphql/resolvers';

t schema = makeExecutableSchema({ typeDefs, resolvers });

t app = express();

use(
graphql',
dyParser.json(),
olloExpress({ schema })

use('/graphiql', graphiqlExpress({ endpointURL: '/graphql' }));

listen(3000);
Maintainers

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 maintainers are:

@RodMachado

@cfnelson

@RichardLitt (triage and basic support)

Along with the team at @okgrow.

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.


This work is supported by the National Institutes of Health's National Center for Advancing Translational Sciences, Grant Number U24TR002306. This work is solely the responsibility of the creators and does not necessarily represent the official views of the National Institutes of Health.