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 helpersimport { 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.
FeaturesThe resolver
function takes a model as its first (required) argument, but also has a second options object argument. The available options are:
resolver(SequelizeModel, { // Whether or not this should return a list. Defaults to whether or not the // field type is an instance of `GraphQLList`. list: false, // Whether or not relay connections should be handled. Defaults to `true`. handleConnection: true, /** * Manipulate the query before it's sent to Sequelize. * @param findOptions {object} - Options sent to Seqeulize model's find function * @param args {object} - The arguments from the incoming GraphQL query * @param context {object} - Resolver context, see more at GraphQL docs below. * @returns findOptions or promise that resolves with findOptions */ before: (findOptions, args, context) => { findOptions.where = { /* Custom where arguments */ }; return findOptions; }, /** * Manipulate the Sequelize find results before it's sent back to the requester. * @param result {object|array} - Result of the query, object or array depending on list or not. * @param args {object} - The arguments from the incoming GraphQL query * @param context {object} - Resolver context, see more at GraphQL docs below. * @returns result(s) or promise that resolves with result(s) */ after: (result, args, context) => { result.sort(/* Custom sort function */); return result; }, /* * Transfer fields from the graphql context to the options passed to model calls * Inherits from global resolver.contextToOptions */ contextToOptions: { a: 'a', b: 'c' } }); resolver.contextToOptions = {}; /* Set contextToOptions globally */
The args
and context
parameters are provided by GraphQL. More information about those is available in their resolver docs.
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: { // Field for retrieving a user by ID user: { type: userType, // args will automatically be mapped to `where` args: { id: { description: 'id of the user', type: new GraphQLNonNull(GraphQLInt) } }, resolve: resolver(User) }, // Field for searching for a user by name userSearch: { type: new GraphQLList(userType), args: { query: { description: "Fuzzy-matched name of user", type: new GraphQLNonNull(GraphQLString), } }, resolve: resolver(User, { // Custom `where` clause that fuzzy-matches user's name and // alphabetical sort by username before: (findOptions, args) => { findOptions.where = { name: { "$like": `%${args.query}%` }, }; findOptions.order = [['name', 'ASC']]; return findOptions; }, // Custom sort override for exact matches first 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: { // The resolver will use `findOne` or `findAll` depending on whether the field it's used in is a `GraphQLList` or not. type: new GraphQLList(userType), args: { // An arg with the key limit will automatically be converted to a limit on the target limit: { type: GraphQLInt }, // An arg with the key order will automatically be converted to a order on the target 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, { // ... options exclude: Array, // array of model attributes to ignore - default: [] only: Array, // only generate definitions for these model attributes - default: null globalId: Boolean, // return an relay global id field - default: false map: Object, // rename fields - default: {} allowNull: Boolean, // disable wrapping mandatory fields in `GraphQLNonNull` - default: false commentToDescription: Boolean, // convert model comment to GraphQL description - default: false cache: Object, // Cache enum types to prevent duplicate type name error - default: {} }); /* { id: { type: new GraphQLNonNull(GraphQLInt) }, email: { type: new GraphQLNonNull(GraphQLString) }, firstName: { type: GraphQLString }, lastName: { type: GraphQLString } } */ userType = new GraphQLObjectType({ name: 'User', description: 'A user', fields: Object.assign(attributeFields(Model), { // ... extra fields }) });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) => { //map bools as strings if (type instanceof Sequelize.BOOLEAN) { return GraphQLString } //use default for everything else return false }); //map fields attributeFields(Model); /* { id: { type: new GraphQLNonNull(GraphQLInt) }, email: { type: new GraphQLNonNull(GraphQLString) }, isValid: { type: new GraphQLNonNull(GraphQLString) }, } */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" } }); /* { id: { type: new GraphQLNonNull(GraphQLInt) }, Email: { type: new GraphQLNonNull(GraphQLString) }, FirstName: { type: GraphQLString }, LastName: { type: GraphQLString } } */ attributeFields(Model, { map:(k) => k.toLowerCase() }); /* { id: { type: new GraphQLNonNull(GraphQLInt) }, email: { type: new GraphQLNonNull(GraphQLString) }, firstname: { type: GraphQLString }, lastname: { type: GraphQLString } } */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
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); /* { id: { type: new GraphQLNonNull(GraphQLInt) } } */ var Model = sequelize.define('Project', { project_id: { type: Sequelize.UUID } }); defaultArgs(Model); /* { project_id: { type: GraphQLString }, where: { type: JSONType } } */
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(), { // ... additional args })
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
}
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4