graphql-sequelize
Should be used with dataloader-sequelize to avoid N+1 queries
Installation
$ npm install --save graphql-sequelize
graphql-sequelize assumes you have graphql and sequelize installed.
Resolve helpers
import { resolver } from "graphql-sequelize";
resolver(SequelizeModel[, options]);
A helper for resolving graphql queries targeted at Sequelize models or associations.
Please take a look at the tests to best get an idea of implementation.
Features
- Automatically converts args to where if arg keys matches model attributes
- Automatically converts an arg named 'limit' to a sequelize limit
- Automatically converts an arg named 'order' to a sequelize order
Relay & Connections
Relay documentation
Options
The resolver
function takes a model as its first (required) argument, but also
has a second options object argument. The available options are:
resolver(SequelizeModel, {
list: false,
handleConnection: true,
before: (findOptions, args, context) => {
findOptions.where = { };
return findOptions;
},
after: (result, args, context) => {
result.sort();
return result;
},
contextToOptions: {
a: 'a',
b: 'c'
}
});
resolver.contextToOptions = {};
The args
and context
parameters are provided by GraphQL. More information
about those is available in their resolver docs.
Examples
import {resolver} from 'graphql-sequelize';
let User = sequelize.define('user', {
name: Sequelize.STRING
});
let Task = sequelize.define('task', {
title: Sequelize.STRING
});
User.Tasks = User.hasMany(Task, {as: 'tasks'});
let taskType = new GraphQLObjectType({
name: 'Task',
description: 'A task',
fields: {
id: {
type: new GraphQLNonNull(GraphQLInt),
description: 'The id of the task.',
},
title: {
type: GraphQLString,
description: 'The title of the task.',
}
}
});
let userType = new GraphQLObjectType({
name: 'User',
description: 'A user',
fields: {
id: {
type: new GraphQLNonNull(GraphQLInt),
description: 'The id of the user.',
},
name: {
type: GraphQLString,
description: 'The name of the user.',
},
tasks: {
type: new GraphQLList(taskType),
resolve: resolver(User.Tasks)
}
}
});
let schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'RootQueryType',
fields: {
user: {
type: userType,
args: {
id: {
description: 'id of the user',
type: new GraphQLNonNull(GraphQLInt)
}
},
resolve: resolver(User)
},
userSearch: {
type: new GraphQLList(userType),
args: {
query: {
description: "Fuzzy-matched name of user",
type: new GraphQLNonNull(GraphQLString),
}
},
resolve: resolver(User, {
before: (findOptions, args) => {
findOptions.where = {
name: { "$like": `%${args.query}%` },
};
findOptions.order = [['name', 'ASC']];
return findOptions;
},
after: (results, args) => {
return results.sort((a, b) => {
if (a.name === args.query) {
return 1;
}
else if (b.name === args.query) {
return -1;
}
return 0;
});
}
})
}
}
})
});
let schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'RootQueryType',
fields: {
users: {
type: new GraphQLList(userType),
args: {
limit: {
type: GraphQLInt
},
order: {
type: GraphQLString
}
},
resolve: resolver(User)
}
}
})
});
field helpers
field helpers help you automatically define a models attributes as fields for a GraphQL object type.
var Model = sequelize.define('User', {
email: {
type: Sequelize.STRING,
allowNull: false
},
firstName: {
type: Sequelize.STRING
},
lastName: {
type: Sequelize.STRING
}
});
import {attributeFields} from 'graphql-sequelize';
attributeFields(Model, {
exclude: Array,
only: Array,
globalId: Boolean,
map: Object,
allowNull: Boolean,
commentToDescription: Boolean,
cache: Object,
});
userType = new GraphQLObjectType({
name: 'User',
description: 'A user',
fields: Object.assign(attributeFields(Model), {
})
});
Providing custom types
attributeFields
uses the graphql-sequelize typeMapper
to map Sequelize types to GraphQL types. You can supply your own
mapping function to override this behavior using the mapType
export.
var Model = sequelize.define('User', {
email: {
type: Sequelize.STRING,
allowNull: false
},
isValid: {
type: Sequelize.BOOLEAN,
allowNull: false
}
});
import {attributeFields,typeMapper} from 'graphql-sequelize';
typeMapper.mapType((type) => {
if (type instanceof Sequelize.BOOLEAN) {
return GraphQLString
}
return false
});
attributeFields(Model);
Renaming generated fields
attributeFields accepts a map
option to customize the way the attribute fields are named. The map
option accepts
an object or a function that returns a string.
var Model = sequelize.define('User', {
email: {
type: Sequelize.STRING,
allowNull: false
},
firstName: {
type: Sequelize.STRING
},
lastName: {
type: Sequelize.STRING
}
});
attributeFields(Model, {
map:{
email:"Email",
firstName:"FirstName",
lastName:"LastName"
}
});
attributeFields(Model, {
map:(k) => k.toLowerCase()
});
ENUM attributes with non-alphanumeric characters
GraphQL enum types only support ASCII alphanumeric characters, digits and underscores with leading non-digit.
If you have other characters, like a dash (-
) in your Sequelize enum types,
they will be converted to camelCase. If your enum value starts from a digit, it
will be prepended with an underscore.
For example:
-
foo-bar
becomes fooBar
-
25.8
becomes _258
VIRTUAL attributes and GraphQL fields
If you have Sequelize.VIRTUAL
attributes on your sequelize model, you need to explicitly set the return type and any field dependencies via new Sequelize.VIRTUAL(returnType, [dependencies ... ])
.
For example, fullName
here will not always return valid data when queried via GraphQL:
firstName: { type: Sequelize.STRING },
lastName: { type: Sequelize.STRING },
fullName: {
type: Sequelize.VIRTUAL,
get: function() { return `${this.firstName} ${this.lastName}`; },
},
To work properly fullName
needs to be more fully specified:
firstName: { type: Sequelize.STRING },
lastName: { type: Sequelize.STRING },
fullName: {
type: new Sequelize.VIRTUAL(Sequelize.STRING, ['firstName', 'lastName']),
get: function() { return `${this.firstName} ${this.lastName}`; },
},
args helpers
defaultArgs
defaultArgs(Model)
will return an object containing an arg with a key and type matching your models primary key and
the "where" argument for passing complex query operations described here
var Model = sequelize.define('User', {
});
defaultArgs(Model);
var Model = sequelize.define('Project', {
project_id: {
type: Sequelize.UUID
}
});
defaultArgs(Model);
If you would like to pass "where" as a query variable - you should pass it as a JSON string and declare its type as SequelizeJSON:
/* with GraphiQL */
// request
query($where: SequelizeJSON) {
user(where: $where) {
name
}
}
// query variables
# JSON doesn't allow single quotes, so you need to use escaped double quotes in your JSON string
{
"where": "{\"name\": {\"like\": \"Henry%\"}}"
}
defaultListArgs
defaultListArgs
will return an object like:
{
limit: {
type: GraphQLInt
},
order: {
type: GraphQLString
},
where: {
type: JSONType
}
}
Which when added to args will let the resolver automatically support limit and ordering in args for graphql queries.
Should be used with fields of type GraphQLList
.
import {defaultListArgs} from 'graphql-sequelize'
args: Object.assign(defaultListArgs(), {
})
order
expects a valid field name and will sort ASC
by default. For DESC
you would prepend reverse:
to the field name.
/* with GraphiQL */
// users represents a GraphQLList of type user
query($limit: Int, $order: String, $where: SequelizeJSON) {
users(limit: $limit, order: $order, where: $where) {
name
}
}
// query variables
{
"order": "name" // OR "reverse:name" for DESC
}