Coder Social home page Coder Social logo

Comments (29)

nodkz avatar nodkz commented on July 19, 2024 4

@Stoffern take a look on https://github.com/nodkz/graphql-compose
And it's examples (server) http://graphql-compose.herokuapp.com/
Relay live example (client) https://nodkz.github.io/relay-northwind/

Please come with ideas 😉

from graffiti-mongoose.

jashmenn avatar jashmenn commented on July 19, 2024 3

I've been looking more into this and I just wanted to leave a tip for anyone else trying to extend the graffiti-mongoose schema.

Just a word of warning, this uses private variables so it's a bit of a hack until an official way to extend the schema is available.

Basically the idea is that instead of using getSchema we'll use getFields and 1. extract the RootQuery 2. modify it 3. use your modified RootQuery to build the schema. The reason we're able to rebuild the RootQuery is because GraphQLObjectType stores the GraphQLObjectTypeConfig in the instance variable _typeConfig.

Here's the (hacky) solution:

const graffitiModels = getModels([Post, User]);
var graffitiFields = getFields(graffitiModels);

var testRootType = new GraphQLObjectType({
  name: 'testRoot',
  fields: {
    hello: {
      type: GraphQLString,
      resolve() {
        return 'world';
      }
    }
  }
});

var originalRootQuery = graffitiFields.query._typeConfig;
originalRootQuery.fields.test = {
  type: testRootType,
  resolve(obj) { return obj; }
}

var fields = {
  query: new GraphQLObjectType(originalRootQuery),
}

const schema = new GraphQLSchema(fields);

from graffiti-mongoose.

zuhair-naqvi avatar zuhair-naqvi commented on July 19, 2024 2

I think producing the schema directly from models is too restrictive, instead the top-level item in the getSchema array could be a higher-order object that specifies the model and any arbitrary queries to be added to the model's field definition. Graffiti could then pass the instance of the model as the first parameter to the resolve method of each query, allowing for something like the following pseudo-code:

let Post = mongoose.model('Post');
let User = mongoose.model('User');

PostType = {
    model: Post,
    queries: {
        getLikedFriends: {
          type: new GraphQLList(UserType),
          args: { id: { type: GraphQLID } },      
          resolve: (_instance, args) => _instance.getLikedFriends(agrs.id, ctx.loggedInUser)
        },
        customSearch: {
          type: new GraphQLList(Post),
          args: { 
            keywords: { type: String },
            isByFriend: { type: Boolean }  
          },
          resolve: (_instance, args) => _instance.performCustomSearch(args.keywords, args.isByFriend)
        }       
    }
}

UserType = {
    model: User,
    queries: {
        getPosts: {
            type: new GraphQLList(PostType),
            resolve: (_instance) => _instance.getRecentPosts()
        }
    }
}

const schema = getSchema([PostType, UserType]);

I'm quite new to GraphQL so let me know if this is making sense!

from graffiti-mongoose.

nodkz avatar nodkz commented on July 19, 2024 2

Adding customQueries and customMutations to getSchema:
wellth-app@404395f

@tothandras may review it and import

from graffiti-mongoose.

Secretmapper avatar Secretmapper commented on July 19, 2024 2

@nodkz Is there any documentation for customMutations?

For example, how to use existing types generated/defined by graffiti-mongoose for say, the output fields of mutationWithClientMutationId

Using the types generated by getTypes result in a Schema must contain unique named types... error

from graffiti-mongoose.

sibelius avatar sibelius commented on July 19, 2024 2

We built CreateGraphQL that generate code from mongoose models.

Code generation is the best way to be extensible and very customizable.

Check it out our post: https://medium.com/entria/announcing-create-graphql-17bdd81b9f96#.6ez6y751o

We would like to be very extensible and support multiples databases and templates aerogear/create-graphql#59

from graffiti-mongoose.

 avatar commented on July 19, 2024 1

Feels like we're re-inventing the wheel here. You're wrapping what looks suspiciously like the graffiti model around your mongoose model. Maybe we could just give the user the opportunity to pass in the graffiti model themselves and/or merge it with our generated model?

from graffiti-mongoose.

sibelius avatar sibelius commented on July 19, 2024 1

@zopf could you provide a simple example of customQuery and customMutations?

from graffiti-mongoose.

zopf avatar zopf commented on July 19, 2024 1

Here's a proposal for these "virtual" fields:

Just add your own fields to the object returned by type.getTypes(models).YourModel.getFields().

Here's an example I just tested and seems to work on my fork ( https://github.com/wellth-app/graffiti-mongoose ). Please note that I'm using my fork's rebuildCache=false param as I make the call to getSchema, which allows me to use the cached types generated by my first call to getTypes when I make the later call to getSchema (which calls getTypes internally).

const UserSchema = new mongoose.Schema({
    nameFirst: String
});
const User = mongoose.model('User', UserSchema);
const models = [
  User
];
const graphQLTypes = getTypes(models);
const userFields = graphQLTypes.User.getFields();
userFields.myCustomField = {
  name: 'myCustomField',
  description: undefined,
  type: graphQLTypes.User,
  resolve: async function resolveCustomField(value, context, info) {
    // here's where you'd do some kind of fancy filtering or what have you
    return await User.findOne({ _id: value._id }).exec();
  },
  args: []
};
// proves that this custom field stays on the type after it has been set
console.log("graphQLTypes.User.getFields().myCustomField: ", graphQLTypes.User.getFields().myCustomField);
// my code builds a set of custom queries and mutations here...
const customQueries = {};
const customMutations = {};
// Overall Schema
return getSchema(
  models,
  {
    hooks,
    allowMongoIDMutation: true,
    rebuildCache: false, // use cached types from previous getTypes call
    customQueries,
    customMutations
  }
);

Booting up that code (in the context of some other stuff, admittedly) allows me to make the following query:

query justTesting {
  users {
    _id
    nameFirst
    myCustomField {
      _id
      nameFirst
    }
  }
}

... and receive the following result:

{
  "data": {
    "users": [
      {
        "_id": "56cf2e8f2c69da9a7293662f",
        "nameFirst": "Alec",
        "myCustomField": {
          "_id": "56cf2e8f2c69da9a7293662f",
          "nameFirst": "Alec"
        }
      }
    ]
  }
}

What do you think? I can't tell if it's hacky or reasonable to be modifying the object returned from getFields()... but it certainly is giving us the internal object, not a clone. And replacing it does seem to work. Feedback from those more experienced than myself in GraphQL would be great 👍

from graffiti-mongoose.

flipace avatar flipace commented on July 19, 2024 1

any updates on this issue?

from graffiti-mongoose.

ajagesser avatar ajagesser commented on July 19, 2024 1

@tothandras what solution should we use for now??
At the moment I'm implementing the hacky solution of @jashmenn

from graffiti-mongoose.

zuhair-naqvi avatar zuhair-naqvi commented on July 19, 2024

In short I'm trying to figure out how to add types that resolve over arbitrary code over mongoose models, given the biggest selling point of GraphQL is the ability to query over arbitrary code, there should be a canonical way to do this with graffiti-mongoose

from graffiti-mongoose.

tothandras avatar tothandras commented on July 19, 2024

@zuhair-naqvi Do you have a preferred API for getSchema to add this feature?

from graffiti-mongoose.

macrostart avatar macrostart commented on July 19, 2024

+1 for providing a way to extend the default set of queries (and hopefully mutations too).

I also like the idea to add a new config step so that not everything is exposed by default. This is the most common use case anyway: I believe most people would want to expose only a subset of their queries and mutations. I know that it's possible to hide fields or restrict access with hooks, but it's more complicated. Having a dedicated step for that seems to be the cleanest approach.

from graffiti-mongoose.

zuhair-naqvi avatar zuhair-naqvi commented on July 19, 2024

Perhaps add a fields property to config which allows you to choose which default bindings you wish to expose so the resulting object might look like:

let User = mongoose.model('User')
UserConfig = {
  model: User,
  fields: [ ...User.schema.paths ],
  queries: {
    q1: { type: new GraphQLObjectType, resolve: (_instance) => /** your code **/ },
    q2: { type: new GraphQLListType, agrs: { x: {type: Integer} }, resolve: (_instance, args) => /** your code **/ },
  }
}

This would be necessary for anyone with existing application code to be able to use graffiti-mongoose for real use cases.

from graffiti-mongoose.

zuhair-naqvi avatar zuhair-naqvi commented on July 19, 2024

@burkhardr you're right. There needs to be a way to augment graffiti models, to pass in the array of exposed fields as well as attach callbacks to the graffiti model that receive the model instance, args and AST for querying - haven't given mutation much thought yet but this should generally work for both.

The other question this brings up is how you attach additional types say I want to fetch some data from mongo but other from a RESTful backend, which is again a key selling point of GraphQL.

The more I think about this the more it's becoming apparent that graffiti should delegate control of the underlying GraphQL schema / RootQuery to users rather than building the entire schema for them. This will allow users to gradually phase out the ORM as ORMs only really make sense in the legacy REST world.

I think the most sensible use-case for graffiti mongoose will be to help transition existing application code away from mongoose over time.

from graffiti-mongoose.

zuhair-naqvi avatar zuhair-naqvi commented on July 19, 2024

Any further thoughts?

from graffiti-mongoose.

tothandras avatar tothandras commented on July 19, 2024

@zuhair-naqvi I am open to changes! First, we definitely need to split up this project into 2. Migrate out the non Mongoose specific parts into an independent library, that would make creating new adapters easier and we could take advantage of the graffiti model too. I will be really busy in the next few weeks though (finishing school), I am happy to see PRs and following suggestions!

from graffiti-mongoose.

jashmenn avatar jashmenn commented on July 19, 2024

I agree that we definitely need a way to extend the current schema, preferably with easy access to the existing schema.

I think there's two things we need to be able to do:

  1. Add queries and mutators to existing mongoose.Schemas/mongoose.models
  2. Add arbitrary extensions and resolves to the schema

For #1, we could add queries and mutators to the mongoose.Schema directly:

const PostSchema = new mongoose.Schema({
  name: String
  // ... etc
})
PostSchema.queries.getLikedFriends = ...
PostSchema.queries.customSearch = ...

For #2, we could insert another hook in RootQuery like this:

  const RootQuery = new GraphQLObjectType({
    name: 'RootQuery',
    fields: {
      viewer: viewerField,
      node: {
        name: 'node',
        description: 'Fetches an object given its ID',
        type: nodeInterface,
        args: {
          id: {
            type: new GraphQLNonNull(GraphQLID),
            description: 'The ID of an object'
          }
        },
        resolve: addHooks(getIdFetcher(graffitiModels), singular)
      },
      ...(addQueryHooks(queries)) // <-- here
    }
  });

... and similarly for RootMutation.

from graffiti-mongoose.

zopf avatar zopf commented on July 19, 2024

@sibeliusseraphini please see sample unit tests below taken from wellth-app@b2b841e#diff-8b7cf0fa5fd81301d632e0a6f8fb2af6

and to @Secretmapper's point, I ended up working around that by creating a type cache (disabled by default) in the type module: wellth-app@4f59703 and wellth-app@4f59703

it('should return a GraphQL schema with custom queries', () => {
      const graphQLType = types.TestQuery;
      const customQueries = {
        testQuery: {
          type: graphQLType,
          args: {
            id: {
              type: new GraphQLNonNull(GraphQLID)
            }
          }
        }
      };
      const schema = getSchema({}, {customQueries});
      expect(schema).instanceOf(GraphQLSchema);
      expect(schema._queryType.name).to.be.equal('RootQuery');
      expect(schema._mutationType.name).to.be.equal('RootMutation');
      expect(schema._queryType._fields.testQuery.name).to.be.equal('testQuery');
      expect(schema._queryType._fields.testQuery.type._fields.fetchCount.resolve()).to.be.equal('42');
    });

    it('should return a GraphQL schema with custom mutations', () => {
      const graphQLType = types.TestQuery;
      const customMutations = {
        testQuery: {
          type: graphQLType,
          args: {
            id: {
              type: new GraphQLNonNull(GraphQLID)
            }
          }
        }
      };
      const schema = getSchema({}, {customMutations});
      expect(schema).instanceOf(GraphQLSchema);
      expect(schema._queryType.name).to.be.equal('RootQuery');
      expect(schema._mutationType.name).to.be.equal('RootMutation');
      expect(schema._mutationType._fields.testQuery.name).to.be.equal('testQuery');
      expect(schema._mutationType._fields.testQuery.type._fields.fetchCount.resolve()).to.be.equal('42');
    });

from graffiti-mongoose.

sibelius avatar sibelius commented on July 19, 2024

this custom query is only available on the root node? can i add a custom query inside a Model?

from graffiti-mongoose.

zopf avatar zopf commented on July 19, 2024

I only added it on the root node. Haven't really thought about it inside of models...

from graffiti-mongoose.

sibelius avatar sibelius commented on July 19, 2024

Inside of models is like a virtual field in fact.

And it would fix some issues: #102, #64, #63, #25

from graffiti-mongoose.

sibelius avatar sibelius commented on July 19, 2024

thanks for the great example @zopf I will take a look tm

from graffiti-mongoose.

st0ffern avatar st0ffern commented on July 19, 2024

@sibelius I dont think that graffiti will work well together with relay if you want to customize every type of data.
I am setting up a specification for a ORM where you define the table, and graphql/relay data in once place. There may be other solutions but i think this is the best so far..
https://github.com/stoffern/graphorm-spec
Please come with ideas 😉

from graffiti-mongoose.

lyxsus avatar lyxsus commented on July 19, 2024

Sorry, is there any solution for that which will work on current version?

from graffiti-mongoose.

st0ffern avatar st0ffern commented on July 19, 2024

@lyxsus graphql-compose have everything 😉

from graffiti-mongoose.

toverux avatar toverux commented on July 19, 2024

Just adding my $0.02: Yes, I think that the library should be more extendable and flexible. I've just tried it and thought about integrating it in my new application, because the generated schema is simply awesome.
However... I was very disappointed to see that:

  • Mongoose hooks aren't called, and the alternative (root-level hooks) is not very sexy
  • I can't add validation easily
  • Can't manage permissions with enough granularity (except adding hooks everywhere on fields)
  • Can't add my own mutations/queries or remove auto-generated ones
  • Etc

Also, it could be interesting to access the Mongoose model instance in hooks, so we can benefit from the Mongoose model's methods and statics.

Y'all did an awesome work on this lib and I've never created an API so easily. But right now, it's unusable for general-purpose use in most real-life applications.

from graffiti-mongoose.

JohnProg avatar JohnProg commented on July 19, 2024

This was my solution:

const { GraphQLObjectType, GraphQLSchema } = require('graphql');
const { getModels } = require('@risingstack/graffiti-mongoose/lib/model');
const { getFields } = require('@risingstack/graffiti-mongoose/lib/schema');
const { contactMutation } = require('./mutations');
const { contactQuery } = require('./queries');
const models = require('../models');

const graffitiModels = getModels(models);
const graffitiFields = getFields(graffitiModels);

const rootQuery = graffitiFields.query._typeConfig;
const rootMutation = graffitiFields.mutation._typeConfig;

Object.assign(rootQuery.fields, {
  contact: contactQuery.user,
  contacts: contactQuery.users,
});

Object.assign(rootMutation.fields, {
  createContact: contactMutation.createContact,
});

module.exports = new GraphQLSchema({
  query: new GraphQLObjectType(rootQuery),
  mutation: new GraphQLObjectType(rootMutation),
});

I hope this can help you :).

from graffiti-mongoose.

Related Issues (20)

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.