Skip to content

Generate data types and resolvers from models of sequelizejs

License

Notifications You must be signed in to change notification settings

stvkoch/graphqlizejs

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Graphqlizejs

Build Status NPM JavaScript Style Guide

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

Install

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

It's awesome because SequelizeJs it's very powerful!

Do you know about sequelizejs?

You can do a lot of things with sequelizejs and Graphqlizejs automagic generate graphql datatype and resolvers from your models

No patience?

OK, let's check the demo?

Graphql

graphql> https://graphqlize.herokuapp.com/graphql

schema> https://graphqlize.herokuapp.com/

Examples of queries that you can play

{
  # 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
    }
  }
}

Go to by examples

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

Generating schema and resolvers

//...
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
});
....

Simple Queries

query GetCategory {
  categories {
    id
    name
  }
}

Simple Queries With Conditions

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.

Simple Count Queries

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
    }
  }
}

Association Queries

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.

Association Count Queries

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%"}})
}

Inputs Where

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.

Inputs Operators

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

Inputs Create and Update

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.

Input Create

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

Input Update

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

Pagination handlers inside of input where type:

  • _limit: Int
  • _offset: Int
  • _orderBy: Array[Array]
  • _group: Array

orderBy

_orderBy argument accept a array with fieldName and direction. Ex: ['username', 'DESC']

Subscriptions

You can subscribe changes using graphqlizejs subscriptions generated for each mutation by setting:

gqSubscriptionCreate: true,
gqSubscriptionUpdate: true,
gqSubscriptionDelete: true

Example Subscription

subscription {
  updateCountry(where: { id: { eq: "PT" } }) {
    id
    name
    serviceCount: _servicesCount
  }
}
mutation {
  updateCountry(
    where: { id: { eq: "PT" } },
    input: { name: "Purtugaal" }
  ) {
    id
  }
}

Middlewares Resolvers

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

Complete example

https://github.com/stvkoch/example-graphqlizejs