Giter VIP home page Giter VIP logo

graphqlizejs's Introduction

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

graphqlizejs's People

Contributors

aliangliang avatar dependabot[bot] avatar nickolasmv avatar skoch-tf avatar stvkoch avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

graphqlizejs's Issues

[Feature] add support to subscriptions

Taking advantage of Sequelizejs hooks to generate 'subscriptions' data type and resolvers from models that have enabled the options:

gqSubscriptionCreate: true,
gqSubscriptionUpdate: true,
gqSubscriptionDelete: true

Datetime types is returning wrong value

Datetime values is returning a non sense integer that even it's not a timestamp.

v0.3.5 version fix it adding de scalar type date/datetime/time where will convert the datetime values to right scalar type.

[schema] Generate object schema structure, before generate the schema.

Before the start generate the schema, build an object structure, test it and generate the schema from this object instead of look direct over sequelize models.

{
   "scalars": [],
    "inputs": {},
    "types": {
        "Query": [],
        "Mutation": [],
        "Subscription": [],
        "category": [
            {
                "name": "id",
                "type": {
                    "name": "ID",
                    "allowNull": false,
                    "collection": false
                },
                "args": [
                    {
                        "name": "_where",
                        "type": {
                            "name": "_inputWhereId"
                        }
                    }
                ]
            }
        ]
    }

[Feature] Add JSON support to the list of sequelize's supported types.

Hi there,

First and foremost, thank you for your work. I really appreciate it.

TL;DR:

Add support to the JSON DataType either by a) adding it to the Object of types mapSequelizeToGraphql in types.js, or b) removing the absolute switch you pass to mapTypes() in schemas.js, in the body of generateInputOperators().

A deeper explanation

In some of my models I need to use JSON fields. According to the sequelize docs the JSON data type is supported.

I tried to check if my models would work with your library, so I took the code from your example folder and tried it along with my models; then got this error:

SyntaxError: Syntax Error: Expected Name, found ":". (99:3)
   97 | input _inputOperator {
   98 |             eq:
>  99 | ne:
      |   ^
  100 | gte:
  101 | gt:
  102 | lte:
    at e (/home/chirvo/sequelize2/node_modules/prettier/parser-graphql.js:1:319)

I know that's a sequelize error, so I started tracing the bug. Long story short, it took me to schemas.js, function generateInputOperators(). In it, you import mapTypes() from types.js and pass it a switch for it to return exactly a value of a existing key in an object constant mapSequelizeToGraphql. declared in the same types.js. Because of this, when you pass JSON as a datatype, the returned value is an empty string.

Now the possible solutions I propose are quite simplistic, and since I haven't dig deep enough in your code they may prove not viable, but here they are anyway: Either add the JSON datatype to the mapSequelizeToGraphql, or remove the absolute switch from the mapTypes call in generateInputOperators. I saw that the only place you call it using the switch is right there; other calls to the same function are not using the switch. Probably the later one may introduce bugs, or break any edge cases you foresaw that I didn't.

If you want me to do a PR with any of these proposed solution let me know.

Thanks!

EDIT: Formatting.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.