Skip to content

nextensions/graphql-sequelize

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

graphql-sequelize

NPM Build Status Slack Coverage

Installation

$ npm install --save graphql-sequelize

graphql-sequelize assumes you have graphql and sequelize installed.

Resolve helpers

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. *** Resolver helpers is limit query 2000 row. If you want more. Recommend use query 2 time, Example (limit: 2000,offset: 0) and (limit: 2000,offset: 1)

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
  • Only loads the attributes defined in the query (automatically adds primary key and foreign keys)
  • Batching of nested associations (see dataloader-sequelize)

Relay & Connections

Relay documentation

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 will automatically be mapped to `where`
        args: {
          id: {
            description: 'id of the user',
            type: new GraphQLNonNull(GraphQLInt)
          }
        },
        resolve: resolver(User)
      }
    }
  })
});

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: new GraphQLList(new GraphQLList(GraphQLString))
          }
        },
        resolve: resolver(User)
      }
    }
  })
});

Example for Raw Query

Function resloverQuery(sequelize, sql, options()) or resloverQuery(sequelize, sql, replacements)

import {resolverQuery} 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: resolverQuery(sequelize, 'SELECT * FROM task WHERE id = :id', { id: 20 })
    }
  }
});

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: resolverQuery(sequelize, 'SELECT id, name, tasks FROM user WHERE id = :id', { id: 20 })
      }
    }
  })
});

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: new GraphQLList(new GraphQLList(GraphQLString))
          }
        },
        resolve: resolverQuery(sequelize, 'SELECT id, name, tasks FROM user', {})
      }
    }
  })
});

Example Input

query {
  user(limit: 20, offset: 5, where: { name: 'John' }, order: [['name','DESC']]) {
    id
    name
  }
}

Code

resolverQuery(sequelize, 'SELECT id, name, tasks FROM user', {}, sequelize.models.User);

Output

SELECT * FROM (SELECT id, name, tasks FROM user) subquery WHERE `name` = 'John' LIMIT 5, 20 ORDER BY `name` DESC

Example resloverQuery() use nested query Input

query {
  todo {
    id
    name
    userId
    user(limit: 20, offset: 5, where: { name: 'John' }, order: [['name','DESC']]) {
      id
      name
    }
  }
}
resolverQuery(sequelize, 'SELECT id, name, tasks FROM user WHERE id = :id', (source, args, context, info) => {
  source.replacements = { id: info.source.userId }
  return source;
}, sequelize.models.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: _.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 and underscores. If you have other characters, like a dash (-) in your Sequelize enum types, they will be converted to camelCase. For example: foo-bar becomes fooBar.

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);

/*
{
  id: {
    type: new GraphQLNonNull(GraphQLInt)
  }
}
*/

var Model = sequelize.define('Project', {
  project_id: {
    type: Sequelize.UUID
  }
});

defaultArgs(Model);

/*
{
  project_id: {
    type: GraphQLString
  },
  where: {
    type: JSONType
  }
}
*/

defaultListArgs

defaultListArgs will return an object like:

{
  limit: {
    type: GraphQLInt
  },
  offset: {
    type: GraphQLInt
  },
  order: {
    type: new GraphQLList(new GraphQLList(GraphQLString))
  },
  where: {
    type: JSONType
  }
}

Example Query

query {
  users (limit:20, offset: 5, order:[["firstname","DESC"],"createdAt"], where: { firstname: "John" }) {
    id
    firstname
    createAt
  }
}

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: _.assign(defaultListArgs(), {
  // ... additional args
})

About

GraphQL & Relay for MySQL & Postgres via Sequelize

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • JavaScript 100.0%