Graphqlizejs automatically generate data types and resolvers for graphql servers from your sequelizejs models!
it's awesome... really awesome!
You define your models and everything it's available!
-
Inputs
- inputs wheres
- inputs operators
- inputs mutations
-
Types
- types models
- types associations
-
Queries
- queries models
- queries counters
-
Mutations
- create mutation
- update mutation
- delete mutation
-
Subscriptions
- create
- update
- delete
git clone https://github.com/stvkoch/graphqlize.git
cd graphqlize
npm install# or npm
npm start # or npm
# open url http://localhost:4000/graphql
# can access complete generate schema in http://localhost:4000
Do you know about sequelizejs?
- No? Then check out the site http://docs.sequelizejs.com/
You can do a lot of things with sequelizejs and Graphqlizejs automagic generate graphql datatype and resolvers from your models
OK, let's check the demo?
graphql> https://graphqlize.herokuapp.com/graphql
schema> https://graphqlize.herokuapp.com/
{
# IN operator
queryAsInOp: services(where: {id: { in: ["3", "7", "12"] }}) {
id
name
price
}
# operator combination AND
countQueryAsAndOp: _servicesCount(where: {price: { gt: 150, lt: 200 }})
queryAsAndOp: services(where: {price: { gt: 150, lt: 200 }}) {
id
name
price
}
# you can also use conditions inside of yours associations
country(where: {id: { eq: "PT" }}) {
id
name
_servicesCount(where: {price: { gt: 150, lt: 200 }})
services(where: {price: { gt: 150, lt: 200 }}) {
id
name
price
}
}
# we don't support directly OR, but in graphql you request more that one list
expensiveServices: services(where: {price: { gt: 980 }}) {
id
name
price
}
#OR
cheapServices: services(where: {price: { lt: 20 }}) {
id
name
price
}
# query over JSONB structures
overJsonb: services(where: { meta: { path: "meta.baz.foo", where: { eq: "baz" } } }) {
id
meta
countryId
country {
id
name
}
}
}
Let's imagine that we have follow models (example folder):
// models/category.js file with all graphqlizejs options available
export default (sequelize, DataTypes) => {
const Category = sequelize.define(
"category",
{
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
// option enable/disable graphql prop [default: false]
gqIgnore: false
},
name: {
type: DataTypes.STRING,
// option enable/disable graphql prop [default: false]
gqIgnore: false
}
},
{
freezeTableName: true,
// optional different name for graphql
gqName: 'category',
// option enable/disable generate all grapqhl queries/mutations for this model [default: false]
gqIgnore: false,
// option enable/disable generate grapqhl query for this model [default: true]
gqQuery: true,
// option enable/disable generate grapqhl query count for this model [default: true]
gqQueryCount: true,
// option enable/disable generate grapqhl mutation create for this model [default: true]
gqCreate: true,
// option enable/disable generate grapqhl mutation update for this model [default: true]
gqUpdate: true,
// option enable/disable generate grapqhl mutation delete for this model [default: true]
gqDelete: true,
// enable/disable generate grapqhl subscriptions of follow operations [default: false]
gqSubscriptionCreate: false,
gqSubscriptionUpdate: false,
gqSubscriptionDelete: false,
// set middlewares for each operations to restrict or changes requests or results [default: defaultMiddleware]
gqMiddleware: {
query: defaultMiddleware,
queryCount: defaultMiddleware,
create: defaultMiddleware,
update: defaultMiddleware,
delete: defaultMiddleware,
subscribe: defaultMiddleware
}
}
);
Category.associate = models => {
Category.hasOne(models.category, { as: "parent", foreignKey: "parentId" });
Category.hasMany(models.service);
};
return Category;
};
// models/service.js file
export default (sequelize, DataTypes) => {
const Service = sequelize.define(
"service",
{
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
name: DataTypes.STRING,
price: DataTypes.DECIMAL(10, 2),
meta: {
type: DataTypes.JSONB,
allowNull: false
}
},
{
freezeTableName: true
}
);
Service.associate = models => {
Service.belongsTo(models.category);
};
return Service;
};
//...
import { schema, resolvers } from "graphqlizejs";
import db from './models';
const schemaGenerated = schema(db.sequelize);
const resolversGenerated = resolvers(db.sequelize);
const server = new ApolloServer({
typeDefs: gql(schemaGenerated),
resolvers: resolversGenerated,
context: { db },
introspection: true,
playground: true
});
....
query GetCategory {
categories {
id
name
}
}
To add conditions in your queries you should use _inputWhere input generated for each model. This kind inputs will hold field model and input operators for the type defined.
query GetCategory($where: _inputWhereCategory) {
categories(where: $where) {
id
name
}
}
variable:
{
"where": {"name": {"like": "%u%"}}
}
To avoid collisions names, graphqlizejs generate input names and counter association fields starting with an underscore character. Example: _associationsCount, _inputs.
Each associate field defined in your model has your counter field called by underscore + association name + Count word.
In the example, below look for _servicesCount.
query GetCategory {
categories {
id
name
totalServices: _servicesCount
serviceCountStartU: _servicesCount(where: {name: {like:"u%"}})
services(where: {name:{like:"u%"}}) {
id
name
}
}
}
Retrieve all services by category:
query GetCategory {
categories {
id
name
services {
id
name
}
}
}
You also can filter your association's data as you did "Simple Queries With Conditions" example.
Each query also has your counter association field follow the same name definition: underscore + model name* + _Count* word.
query GetCategoryCount {
renameCategoryCount: _categoriesCount
_categoriesCount(where: {name:{like:"%u%"}})
}
For each model, graphqlize will create a graphql input with all available model fields to be used in your conditions. You will see that, the fields defined in your input not use the model type, instead, it's used the type _input Type Operator_ that will hold all operators supported by the model type specified.
For instance country model, graphqlize will generate the _inputWhereCountry.
Sequelize ORM supports several query operators to allow filtered your data using findAll method. For this reason was create _inputTypeOperator.
Most of the types support the following operators:
eq
ne
gte
gt
lte
lt
not
is
in
notIn
between
notBetween
String type support the follow additional operators:
like
notLike
iLike
notILike
startsWith
endsWith
substring
regexp
notRegexp
iRegexp
notIRegexp
overlap
contains
contained
adjacent
strictLeft
strictRight
JSONB type support same operators found into String
in JSON type you can't query looking into the JSON struture, JSON as storage as String/Text, so you can use String operators
To able to mutate your data you will need to hold your data inside of the input mutation type. Graphqlizejs will generate the _inputCreate and _inputUpdate for each model and through models.
Example of country model:
type _inputCreateCountry {
name: String!
callCode: String
currencyCode: String
createdAt: String
updatedAt: String
}
Note that graphqlizejs didn't create the input with the primary keys. If you want to create or update your primary keys, enable graphqlizejs to create the input with the primary keys setting the model options with:
gqInputCreateWithPrimaryKeys: true
type _inputUpdateCountry {
name: String!
callCode: String
currencyCode: String
createdAt: String
updatedAt: String
}
Same way, if you want to enable update primary keys set the model option:
gqInputUpdateWithPrimaryKeys: true
- _limit: Int
- _offset: Int
- _orderBy: Array[Array]
- _group: Array
_orderBy argument accept a array with fieldName and direction. Ex: ['username', 'DESC']
You can subscribe changes using graphqlizejs subscriptions generated for each mutation by setting:
gqSubscriptionCreate: true,
gqSubscriptionUpdate: true,
gqSubscriptionDelete: true
subscription {
updateCountry(where: { id: { eq: "PT" } }) {
id
name
serviceCount: _servicesCount
}
}
mutation {
updateCountry(
where: { id: { eq: "PT" } },
input: { name: "Purtugaal" }
) {
id
}
}
Middleware is the way to add some control over the model resolvers. You can add middlewares receiving the next resolver and returning the function with the same signature found in apollo server resolvers (root/parent, args, context, info) arguments.
Defining middleware:
function requireUser(nextResolver) {
return (parent, args, context, info) => {
if (!context.user)
throw new AuthenticationError('Required valid authentication.');
// you always should call the next resolver if you want to keep flow the resolvers.
return nextResolver(parent, args, context, info);
}
}
Using middleware in your models:
import flow from 'lodash.flow';
import {requireUser, onlyOwnData} from '../models/middlewares';
// models/service.js file
export default (sequelize, DataTypes) => {
const Service = sequelize.define(
"service",
{
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
name: DataTypes.STRING,
price: DataTypes.DECIMAL(10, 2)
},
{
freezeTableName: true,
gqMiddleware: {
query: requireUser,
queryCount: requireUser,
create: flow([requireUser, onlyOwnData]),
update: flow([requireUser, onlyOwnData]),
destroy: flow([requireUser, onlyOwnData])
}
}
);
Service.associate = models => {
Service.belongsTo(models.category);
};
return Service;
};