GraysQL is a manager and loader for GraphQL. It provides a uniform way of organize your GraphQL schema. GraphQL tries to create an easy to read codebase. It features a plugins API to allow the extension of the core functionalities.
It's directly compatible with the GraphQL reference implementation.
Install from NPM. You need to install it's peer dependencies if you haven't done yet too.
$ npm install --save graysql graphql graphql-relay
Here is a simple example to get started:
const GraphQLUtils = require('graphql/utilities');
const GraysQL = require('graysql');
const Graylay = require('graysql/extensions/graylay'); // Add support for Relay entities
const DB = require('./db'); // Mockup data source
const GQL = new GraysQL({
DB: DB
});
// Add some extensions
GQL.use(Graylay);
// You can import types
GQL.registerType(require('./types/group'));
// Or define them inline
GQL.registerType(function (GQL, types) {
return {
name: 'User',
nodeId: id => GQL.options.DB.getUser(id),
isTypeOf: obj => obj instanceof GQL.options.DB.User,
interfaces: ['Node'],
fields: {
id: {
type: 'Int'
},
nick: {
type: 'String'
},
group: {
type: 'Group' // Define type dependencies
}
},
queries: {
user: { // You can define inline queries
type: 'User',
args: {
id: {
type: 'Int'
}
},
resolve: (_, args) => GQL.options.DB.getUser(args.id);
},
users: require('./queries/users') // Or import them
}
}
});
const Schema = GQL.generateSchema();
console.log(GraphQLUtils.printSchema(Schema));
A Type is a representation of a GraphQLObjectType. Types are the main object in GraysQL and every other object relates to them in some way. They define the layout of your schema. To create a new Type simply create a JavaScript function that takes an argument that is a reference to the current GraysQL instance and that function should return an object with the keys needed to create an object.
The mandatory keys are:
name
: A string representing the name of the type that will be used to create and store it. Other types will use this name to reference the type as well.fields
: An object representing the fields of the type. This is directly related to thefields
property in GraphQLObjectType.
The type of the field is specified as a string instead of a GraphQLScalarType. The basic list of supported types is:
Int
: Translates to GraphQLIntString
: Translates to GraphQLStringTypeName
: Reference another type in the system.[TypeName]
: Reference an array of another type in the system. Equivalent to GraphQLList.
Besides that, extensions can define custom keys or even you can define your own keys if you need it.
Types can be defined in its own file. For example, types/user.js
and types/group.js
.
// types/user.js
module.exports = function (GQL) {
return {
name: 'User',
fields: {
id: { type: 'Int' },
nick: { type: 'String' },
group: { type: 'String' }
}
};
}
// types/group.js
module.exports = function (GQL) {
return {
name: 'Group',
fields: {
id: { type: 'Int' },
nick: { type: 'String' },
members: { type: '[User]' }
}
};
}
When the two types are defined, we can register them within the system:
const GraysQL = require('graysql');
const GQL = new GraysQL();
GQL.registerType(require('./types/user.js'));
GQL.registerType(require('./types/group.js'));
TODO
In order to make request to the Schema you must define Queries in your Types. As queries are only plain Javascript object, they can be defined inline in your types or in separate files and later exported. However, if you define your queries in standalone files, you should wrap them in a function. This function, like types, will receive a single parameter that is the current GQL intance, and should return the query object.
Once a query is defined, you can add it to the sytem using two ways. Embedding them in a type, like
the first example, or using the auxiliar function GQL.addQuery(query)
.
The query object should have three keys that are mandatory:
type
: The type to which the query is applied. It has to be registered in the system before the query will be generated.args
: It's equivalent toargs
in GraphQL queries. It uses the same syntax for types than the keyfields
in Types.resolve
: It's the same asresolve
in GraphQL queries.
// queries/user.js
module.exports = function (GQL) {
return {
type: 'User',
args: {
id: { type: 'Int' }
},
resolve: (_, args) => GQL.options.DB.getUser(args.id)
}
}
// index.js
GQL.addQuery('user', require('./queries/user'));
TODO
GraysQL is initialized by passing an object with any number of custom keys. This keys will be available later in the GraysQL instance.
const DB = require('./db');
const GraysQL = require('graysql');
const GQL = new GraysQL({
DB: DB
});
console.log(GQL.options.DB);
Receives a GraysQL extension and add it to GraysQL.
- Parameters
extension
Function: A valid extension to be added.
const GraysQL = require('graysql');
const LoadFromDir = require('graysql/extensions/load-from-dir');
const Graylay = require('graysql/extensions/graylay');
const GQL = new GraysQL();
GQL.use(LoadFromDir);
GQL.use(Graylay);
Registers a new type in the system.
- Parameters
type
Function: A valid type to be registered.overwrite
Boolean: A flag wether the registered type should overwrite an existent type with the same name or not.
- Returns
- Object: The registered type.
const GraysQL = require('graysql');
const GQL = new GraysQL();
const UserType = function (GQL) {
return {
name: 'User',
fields: {
id: { type: 'Int' },
nick: { type: 'String' }
}
};
}
GQL.registerType(UserType);
Registers a new interface in the system that can be later implemented by types. Interfaces should be registered before the implementing types.
- Parameters
interface
Function: A valid interface to be registered.overwrite
Boolean: A flag wether the registered interface should overwrite an existent interface with the same name or not.
- Returns
- Object: The registered interface.
const GraysQL = require('graysql');
const GQL = new GraysQL();
const EmployeeInterface = function (GQL) {
return {
name: 'Employee',
fields: {
employeeId: { type: 'Int' }
}
};
}
GQL.registerInterface(EmployeeInterface);
Adds a new query to the system. Note that if the type of the query is not already registered in the system, this will throw an error.
- Parameters
name
String: The name of the query to be added.query
Function: A valid query to be added.overwrite
Boolean: A flag wether the added query should overwrite an existent query with the same name or not.
- Returns
- Object: The added query.
Adds a new mutation to the system. Note that if the type of the mutation is not already registered in the system, this will throw an error.
- Parameters
name
String: The name of the mutation to be added.mutation
Function: A valid mutation to be added.overwrite
Boolean: A flag wether the added mutation should overwrite an existent mutation with the same name or not.
- Returns
- Object: The added mutation.
Generates a GraphQLSchema from the registered objects.
- Returns
- GraphQLSchema: The generated schema.
Loads the models defined with an ORM into GraysQL to generate a Schema from them. More in the project repository.
Allows GraysQL to scan a folder to build a schema from the files found. You only need to define your schema and GraysQL will take care of the registration process for you. This way, you can model your schema from the dir structure. This allows you to add or delete objects on the fly, without having to register the new ones, or de-register the old ones
The folder structure that LoadFromDir will search for it's:
schema/
├── types/
│ ├── type-name/
│ │ └── index.js
├── interfaces/
│ └── interface-name.js
Where:
- schema/: Is the root folder of the Schema. It contains all the structure.
- types/: Contains all the defined types. Each type goes in its own folder.
- type-name/: Contains a type. The type is defined in the
index.js
file. - interfaces/: Contains the interfaces. Each interface goes in it's own file
interface-name.js
.
In order for the queries to be automatically added to GraysQL, each type should define its queries
in its own query
key.
LoadFromDir defines a new method in GraysQL:
Load the objects from the given folder and add them to GraysQL.
- Parameters
directory
String: The root folder that contains the objects.Overwrite
Boolean: A flag wether the loaded objects should overwrite existent objects with the same name or not.
// schema/types/user.js
module.exports = function (GQL) {
return {
name: 'User',
fields: {
id: { type: 'Int' },
nick: { type: 'String' }
},
queries: {
user: {
type: 'User',
args: { id: { type: 'Id' } },
resolve: (_, args) => GQL.options.DB.getUser(args.id)
}
}
};
}
// index.js
const DB = require('./db');
const GraysQL = require('graysql');
const LoadFromDir = require('graysql/extensions/load-from-dir');
const GQL = new GraysQL({ DB: DB });
GQL.use(LoadFromDir);
GQL.load('schema');
Creates Relay compatible objects from your objects. Add needed entities for
Relay to work with your Schema. For instance, it creates the
Node interface or establishes connections between your types. Besides to the common keys your type
need to define, Graylay adds one more, nodeId
. Graylay will use this key to generate the
Node interface needed by Relay.
Additionaly, Graylay adds two new symbols to define relations between types. If you precede a type
name with @
Graylay will translate that into a
connection with that type and will
resolve to a connectionFromArray. If you
precede a type name with @>
instead, Graylay will translate that into a connection that will
resolve to a connectionFromPromisedArray.
In any case, you can specify your own resolve, and Graylay will honour it. Besides that, your types
will need to define the usual keys
needed by Relay like isTypeOf
and implement the Node
interface.
const GraysQL = require('graysql');
const DB = require('./db');
const GraphQLRelay = require('graphql-relay');
const Graylay = require('graysql/extensions/graylay');
const UserType = function (GQL) {
return {
name: 'User',
nodeId: id => GQL.options.DB.getUser(id),
isTypeOf: obj => obj instanceof GQL.options.DB.User,
interfaces: ['Node'],
fields: {
id: { type: 'Int' },
nick: { type: 'String' },
groups: {
type: '@Group' // Resolves to a connectionFromArray
}
}
};
}
const GQL = new GraysQL({ DB: DB });
GQL.use(Graylay);
GQL.registerType(UserType);
TODO
Usage examples can be found in examples directory.
The tests are written with mocha and can be run with the following command:
$ npm test
To get a code coverage report, run the following command:
$ npm run cover
- Add support for non nullable args in mutations and queries.
- Implement Graylay and LoadFromDir.
- Document Plugin API.
- Provide an async version of LoadFromDir.
- Add support for lists as arguments in queries and mutations.