Giter Club home page Giter Club logo

graphql-to-mongodb's Introduction

graphql-to-mongodb

Build Status

If you want to grant your NodeJS GraphQL service a whole lot of the power of the MongoDB database standing behind it with very little hassle, you've come to the right place!

Let's take a look at the most common use case, getMongoDbQueryResolver and getGraphQLQueryArgs:

Given a simple GraphQL type:

new GraphQLObjectType({
    name: 'PersonType',
    fields: () => ({
        age: { type: GraphQLInt },
        name: { type: new GraphQLObjectType({
            name: 'NameType',
            fields: () => ({
                first: { type: GraphQLString },
                last: { type: GraphQLString }
            })
        }),
        fullName: {
            type: GraphQLString,
            resolve: (obj, args, { db }) => `${obj.name.first} ${obj.name.last}`
        }
    })
})

An example GraphQL query supported by the package:

Queries the first 50 people, oldest first, over the age of 18, and whose first name is John.

{
    people (
        filter: {
            age: { GT: 18 },
            name: { 
                first: { EQ: "John" } 
            }
        },
        sort: { age: DESC },
        pagination: { limit: 50 }
    ) {
        fullName
        age
    }
}

To implement, we'll define the people query field in our GraphQL scheme like so:

people: {
    type: new GraphQLList(PersonType),
    args: getGraphQLQueryArgs(PersonType),
    resolve: getMongoDbQueryResolver(PersonType,
        async (filter, projection, options, obj, args, context) => {
            return await context.db.collection('people').find(filter, projection, options).toArray();
        })
}

You'll notice that integrating the package takes little more than adding some fancy middleware over the resolve function. The filter, projection, options added as the first parameters of the callback, can be sent directly to the MongoDB find function as shown. The rest of the parameter are the standard received from the GraphQL api.

  • Additionally, resolve fields' dependencies should be defined in the GraphQL type like so:
    fullName: {
        type: GraphQLString,
        resolve: (obj, args, { db }) => `${obj.name.first} ${obj.name.last}`,
        dependencies: ['name'] // or ['name.first', 'name.Last'], whatever tickles your fancy
    }
    This is needed to ensure that the projection does not omit any necessary fields. Alternatively, if throughput is of no concern, the projection can be replaced with an empty object.
  • As of mongodb package version 3.0, you should implement the resolve callback as:
    return await context.db.collection('people').find(filter, options).toArray();

That's it!

The following field is added to the schema (copied from graphiQl):

people(
    filter: PersonFilterType
    sort: PersonSortType
    pagination: GraphQLPaginationType
): [PersonType]

PersonFilterType:

age: IntFilter
name: NameObjectFilterType
OR: [PersonFilterType]
AND: [PersonFilterType]
NOR: [PersonFilterType]

* Filtering is possible over every none resolve field!

NameObjectFilterType:

first: StringFilter
last: StringFilter
opr: OprExists

OprExists enum type can be EXISTS or NOT_EXISTS, and can be found in nested objects and arrays

StringFilter:

EQ: String
GT: String
GTE: String
IN: [String]
LT: String
LTE: String
NEQ: String
NIN: [String]
NOT: [StringFNotilter]

PersonSortType:

age: SortType

SortType enum can be either ASC or DESC

GraphQLPaginationType:

limit: Int
skip: Int

Functionality galore! Also permits update, insert, and extensiable custom fields.

graphql-to-mongodb's People

Contributors

bartduisters avatar bkjoel avatar dependabot[bot] avatar ihadeed avatar jf26 avatar katrinleinweber avatar mordech avatar nadavshlezz avatar sabrogden avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

graphql-to-mongodb's Issues

Typo in README.md with request for clarification

README.md describes an aspect of the main use case as:

To implement, we'll define the peron query field in our GraphQL scheme like so:

people: {
...
}

I assume "peron" was intended to be written as "person", but would you please describe a little bit more about this "person query field" and why it is named "people" as opposed to "person"? Thanks!

Cannot read property 'resolve' of undefined.

person: {
    type: new GraphQLList(PersonTypeExpanded),
    args: getGraphQLQueryArgs(PersonType),
    resolve: getMongoDbQueryResolver(PersonType,
        async (filter, projection, options, source, args, context) =>
            await context.db.collection('person')
                .find(filter, projection, options).toArray()
    )
}

This returns cannot read proprety 'resolve' of undefined when using person( filter: personType, sort: personType): [personTypeExpanded], and trying to retrieve a field defined in personTypeExpanded.

How to incorporate into NestJS?

I was wondering if there was a way to integrate this with NestJS? I found myself implementing the features manually until I ran into your project. I tried to translate your code into the way NestJS is implementing their code, but ran into issues. Would very much appreciate any pointers you have.

Limit and Skip not working

export default {
  type: new GraphQLList(propertyType),
  args: getGraphQLQueryArgs(propertyType),
  resolve: getMongoDbQueryResolver(
    propertyType,
    async (filter, projection, options, obj, args, context) => {
      console.log(filter);
      console.log(projection);
      console.log(options);
      
      return await db.collection('properties').find(filter, projection, options).toArray();
    }
  )
};

What console.log gives

{ Class: { '$eq': 'RESIDENTIAL' } }
{ _id: 1, City: 1, Class: 1 }
{ sort: { _id: 1 }, limit: 5, skip: 5 }

What my query is:

query{
  properties(
    filter: {
      Class: {EQ: "RESIDENTIAL"}
    },
    sort: {_id: ASC},
    pagination: { limit: 5, skip: 5 }
  ) {
    _id
    City
    Class
  }
}

I get everything back. I have well over 1000+ records.

How to use mutations?

Any chance you can give an example on how to use mutations? such as insert, create, etc

Warning Field TradeId of type t_TradeType has a resolve function and no dependencies

I have provided a resolver to my subtype , but by doing so I get the above warning when I start.

the type schema is as :
RootType { // this as the mongoargs/resovler directives
...
Trade { // this is of type t_tradeType in the schema
...
TradeId : [{ id: "aaa", idtype: "added"}] // basically TradeId is an array of id+idtype object
...
anotherField
...
}
...
}

when I add a resolver on TradeId ( I need be able to pick the element of array and filter the rest) I get the warning on the subject. The issue I see is that the parent now is not of t_tradeType but some child field of Trade. If I remove the @mongQueryArgs/@mongoqueryResolver from the root type.. it works as expected.

t_tradeType: {
TradeId: (parent, args,,) => {
console.log("PARENT %j",parent); /// this somehow logs{ anotherField: "xxxxx" }
... FILTER LOGIC
... return new array with ONLY the element I need
}

}

But anotherField is not the parent of TradeId, its a sibling.. why is it being passed as the parent.

Appriciate the help.

How to ingrate this with a .graphql schema file?

I'm trying to integrate this package into an existing NodeJS service wich has the schema definition written in some .graphql files. However i'm having some troubles making the resolvers run :/ I've setup a small example project wich describe the actual situation I'm facing, you can find the repository here

https://github.com/GimignanoF/GraphQLFilters

Essentially i have setup two queries. One is using the approach I'm currently using, wich works just fine. The other one tries to use this package, however when i run that query i get this error as GraphQL result

Expected Iterable, but did not find one for field RootQuery.pages.

So, is there any way I can use this package in this scenario? Or should i rewrote the entire schema (wich is quite big) in the source code, rather then parsing it from the .graphql files?

Insert implementation?

Hey,

Is there an implementation for inserts?
Yes? Could you show an example?
No? Should I take a shot at it?

Otherwise it works quite well so far :)

Caching issue

I think there is some kind of caching issue.

The FilterTypes seem to get cached and when I change the fields in the schema (and create a new schema), I still get the old FilterTypes. Only when I restart the app and the schema is created from scratch (empty cache in graphql-to-mongodb) I get the correct FilterTypes.

Running clearTypesCache() before creating the schema helps. Not sure if this is the way it should work?

build error mongoDbFilter.ts

I'm trying to build it to test but get this message...

src/mongoDbFilter.ts:108:68 - error TS2345: Argument of type 'GraphQLObjectFilter | GraphQLLeafFilter' is not assignable to parameter of type 'GraphQLObjectFilter'.
Type 'GraphQLLeafFilter' is not assignable to type 'GraphQLObjectFilter'.
Types of property 'opr' are incompatible.
Type 'MongoDbLeafOperators' is not assignable to type '"exists" | "not_exists"'.
Type '"$eq"' is not assignable to type '"exists" | "not_exists"'.

108 const nestedFilter = parseMongoDbFilter(fieldType, fieldFilter, ...excludedFields);

How to use with graphql-tools

I am trying to make things more simple and was wanting to get way form using the the plain graphql language and use graphql-tools from Apollo.

FilterType no load fields on models

Good evening, the idea they propose seems fantastic to me. I am using the module, my only problem is that the type filters are not loading the fields of the referenced types. I would appreciate if you can help me with this problem. Regards

example of graphql-server

Hi

I just read through your docs and think a simple example repo would be great to have a quick start to see how your package is used on a server. Thanks

Projection not working

projection logs out {} here for any query, e.g.

query {
  viewer {
    id
    firstName
    fullName
  }
}
const viewer = {
  type: ViewerType,
  resolve: getMongoDbQueryResolver(
    ViewerType,
    async (_filter, projection, options, _a, _b, ctx: Context) => {
      console.log(projection);
      if (ctx.userId) {
        return await User.findOne({ _id: ctx.userId }, projection, options);
      }
    }
  )
};

How to use it with type-graphql

Hello. First of all, thank you very much for creating this useful library. I would like to know how can I use this library with type-graphql? More precisely, I don't know where I can find second argument for getMongoDbProjection. As I understand, it's generated by type-graphql under the hood.

Everything I need is just to create projection from GraphQL query info.

Thanks.

Is there a contains/like string filter?

I don't see a basic contains or like filter for strings. Is this present or on the roadmap?

e.g.
document with description = "A very fun toy"

filter: {
description: { CONTAINS: "toy" }
}

...should return the document.

It would be nice to have "StartsWith" or "EndsWith", or even RegEx functionality.

Does graphql-to-mongodb handle relationships?

Before I dig too deep, I was wondering if this library supports simple relationships, like a book with an authorId - can I pull an author object back on a book query?

Also, is there a better place to ask questions like this that aren't issues? I couldn't find anything on Stackoverflow. Do you guys have a gitter room, slack room, or forum?

custom projections

Is there way to support custom projections? Say I had the following GraphQL schema:

const viewer = new GraphQLObjectType({
  name: 'Viewer',
  description: 'The logged in user',
  fields: {
    id: {
      type: new GraphQLNonNull(GraphQLID)
    },
    firstName: {
      type: new GraphQLNonNull(GraphQLString),
    },
    lastName: {
      type: new GraphQLNonNull(GraphQLString),
    },
    fullName: {
      type: new GraphQLNonNull(GraphQLString)
    }
  }
});

But my MongoDB schema has User.name.first and not User.firstName. Is there a way to map these?

Handling ObjectId in filter

Say I want to get a specific person record and filter by _id

person (
   filter: {
       _id: { EQ: "5e1e2d653a32a05f51f621c1" }
   }
) {
   name { 
      lastName
   }
   age
}

Currently that will generate a filter like so

{ _id: { '$eq': '5e1e2d653a32a05f51f621c1' }

Can you detect when "_id" is used and make it essentially do this instead?

{ _id: { '$eq': new ObjectId('5e1e2d653a32a05f51f621c1') }

GraphQL schema generation failed when using Union type

If using a Union type field together with the getMongoDbQueryResolver GraphQL schema validation fails with the error:

TypeError: graphQLType.getFields is not a function
    at ...\node_modules\graphql-to-mongodb\lib\src\common.js:38:38

Add exist | not exist to leaf types

I needed it for my project and added it to my git clone of this project so I can do the following...

{
  "$or": [
    {
      "payload.fileInfo.size": {
        "$exists": true
      }
    },
    {
      "payload.fileInfo.size": {
        "$gt": "12345"
      }
    }
  ]
}

Every leaf type has an opr: exist | not exist and I removed the deprecated opr. I think its a good feature to have for people.

Example using Apollo?

I'm new to graphQL and got the simplest server up and running, but I've been trying to get the Apollo server working because I want to use GraphQLPlayground instead of GraphIQL - https://github.com/apollographql/apollo-server.

Do you guys have an example of graphql-to-mongodb working with the Apollo server? I'm having some trouble getting the two to play nicely together.

Nested Items Bug

{
  properties(filter: {
    SystemID: {EQ: 457397}
    Photos: {
      Sequence: {
        EQ: 0
      }
    }
  }
  ) {
    _id
    Photos {
      Sequence
    }
  }
}

Photos is part of another GraphQLObjectType as I don't know of a way to have nested fields.

export default new GraphQLObjectType({
  name: 'Photos',
  fields: {
    MediaID: { type: GraphQLInt },
    MediaTypeID: { type: GraphQLInt },
    Sequence: { type: GraphQLInt },
    MediaUrl: { type: GraphQLString },
    Caption: { type: GraphQLString },
    Description: { type: GraphQLString },
    FileSize: { type: GraphQLInt },
    Width: { type: GraphQLInt },
    Height: { type: GraphQLInt },
    Duration: { type: GraphQLInt },
    CreateDate: { type: GraphQLString },
    UpdateDate: { type: GraphQLString },
  },
});

My Properties:

export default new GraphQLObjectType({
  name: 'Property',
  fields: {
    _id: {
      type: new GraphQLNonNull(GraphQLID)
    },
    SystemID: { type: GraphQLInt },
    Class: { type: GraphQLString },
    PropertyType: { type: GraphQLString },
    Area: { type: GraphQLString },
    SearchPrice: { type: GraphQLInt },
    ListingPrice: { type: GraphQLInt },
    AddressNumber: { type: GraphQLString },
   ...... more fields between .....
    DocTimestamp: { type: GraphQLString },
    Photos: { 
      type: GraphQLList(photoType),
    },
  },
});

So filter will auto populate the Photos. Sort will not. While you can populate the field, the filter operations doesn't work. It will return everything in the array.

I don't know if this was intended or not. I was looking to sort out my Photos and/or filter them being a nested object.

The photos are embedded into the Mongodb document itself, so they aren't in a separate collection. I was trying to save from having to make multiple calls to each of the properties for photos. Meaning I have over 3,000 property listings. So that would have to return 3,000 database calls to grab the photo without caching / data loader.

On my listings page, I only wanted to return the photo with sequence 0, on the details page, I want to return only the single property, with all the photos to that property. (real estate project).

I supposed I could use a different method of handling this, but the issue to report here, is FILTER will auto populate the nested list, no errors. Sort will not auto populate the nested list.

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.