https://github.com/jcoreio/graphql-sequelize
https://github.com/jcoreio/graphql-sequelize
graphql sequelize
Last synced: about 2 months ago
JSON representation
- Host: GitHub
- URL: https://github.com/jcoreio/graphql-sequelize
- Owner: jcoreio
- License: mit
- Created: 2018-11-27T19:55:59.000Z (over 7 years ago)
- Default Branch: master
- Last Pushed: 2023-03-02T13:54:02.000Z (over 3 years ago)
- Last Synced: 2025-04-28T03:19:01.600Z (about 1 year ago)
- Topics: graphql, sequelize
- Language: JavaScript
- Homepage:
- Size: 2.84 MB
- Stars: 0
- Watchers: 3
- Forks: 0
- Open Issues: 26
-
Metadata Files:
- Readme: README.md
- License: LICENSE.md
Awesome Lists containing this project
README
# @jcoreio/graphql-sequelize
[](https://circleci.com/gh/jcoreio/graphql-sequelize)
[](https://codecov.io/gh/jcoreio/graphql-sequelize)
[](https://github.com/semantic-release/semantic-release)
[](http://commitizen.github.io/cz-cli/)
[](https://badge.fury.io/js/%40jcoreio%2Fgraphql-sequelize)
This is my fork of `graphql-sequelize` that implements the connections specification correctly and more efficiently.
The maintainers of `graphql-sequelize` dropped the ball on my issues about their incorrect connections implementation and my PRs to fix it.
- [Installation](#installation)
- [Resolve helpers](#resolve-helpers)
- [field helpers](#field-helpers)
- [args helpers](#args-helpers)
## Installation
`$ npm install --save @jcoreio/graphql-sequelize`
@jcoreio/graphql-sequelize assumes you have graphql and sequelize installed.
## Resolve helpers
```js
import { resolver } from "@jcoreio/graphql-sequelize";
resolver(SequelizeModel[, options]);
```
A helper for resolving graphql queries targeted at Sequelize models or associations.
Please take a look at [the tests](https://github.com/jcoreio/graphql-sequelize/blob/master/test/integration/resolver.test.js) 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](docs/relay.md)
### Options
The `resolver` function takes a model as its first (required) argument, but also
has a second options object argument. The available options are:
```js
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](http://graphql.org/learn/execution/#root-fields-resolvers)._
### Examples
```js
import { resolver } from '@jcoreio/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.
```js
var Model = sequelize.define('User', {
email: {
type: Sequelize.STRING,
allowNull: false,
},
firstName: {
type: Sequelize.STRING,
},
lastName: {
type: Sequelize.STRING,
},
})
import { attributeFields } from '@jcoreio/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.
```js
var Model = sequelize.define('User', {
email: {
type: Sequelize.STRING,
allowNull: false,
},
isValid: {
type: Sequelize.BOOLEAN,
allowNull: false,
},
})
import { attributeFields, typeMapper } from '@jcoreio/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.
```js
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](https://facebook.github.io/graphql/#Name).
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:
```js
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:
```js
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](http://docs.sequelizejs.com/en/latest/docs/querying/)
```js
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:
```js
{
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`.
```js
import { defaultListArgs } from '@jcoreio/graphql-sequelize'
args: _.assign(defaultListArgs(), {
// ... additional args
})
```