-
Notifications
You must be signed in to change notification settings - Fork 50
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Extending grafitti schema #64
Comments
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 |
@zuhair-naqvi Do you have a preferred API for |
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! |
+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. |
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. |
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? |
@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. |
Any further thoughts? |
@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! |
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:
For #1, we could add const PostSchema = new mongoose.Schema({
name: String
// ... etc
})
PostSchema.queries.getLikedFriends = ...
PostSchema.queries.customSearch = ... For #2, we could insert another hook in 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 |
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 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); |
Adding customQueries and customMutations to getSchema: @tothandras may review it and import |
@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 Using the types generated by |
@zopf could you provide a simple example of customQuery and customMutations? |
@sibeliusseraphini please see sample unit tests below taken from https://github.com/wellth-app/graffiti-mongoose/commit/b2b841e7a6d2ff44eb977b2fa1466799c587f037#diff-8b7cf0fa5fd81301d632e0a6f8fb2af6 and to @Secretmapper's point, I ended up working around that by creating a type cache (disabled by default) in the
|
this custom query is only available on the root node? can i add a custom query inside a Model? |
I only added it on the root node. Haven't really thought about it inside of models... |
Here's a proposal for these "virtual" fields: Just add your own fields to the object returned by 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 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 |
thanks for the great example @zopf I will take a look tm |
any updates on this issue? |
@tothandras what solution should we use for now?? |
@sibelius I dont think that graffiti will work well together with relay if you want to customize every type of data. |
@Stoffern take a look on https://github.com/nodkz/graphql-compose Please come with ideas 😉 |
Sorry, is there any solution for that which will work on current version? |
@lyxsus graphql-compose have everything 😉 |
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.
Also, it could be interesting to access the Mongoose model instance in hooks, so we can benefit from the Mongoose model's 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. |
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 |
This was my solution:
I hope this can help you :). |
getSchema returns a GraphQLSchema object based on MongooseModels, how do I go about adding my own types to this schema?
Consider the use case:
I have a
Post
object withgetLikedFriends
method, which looks up allPost.likes
references and queriesUsers
collection to check if any of theUser.friends
references for the loggedIn user match withPost.likes
So instead of creating a new Mongoose model and re-implementing the logic inside my pre-existing
getLikedFriends
method, I'd like to just add this as a new type to the GraphQL schema and let the resolve function callPost.getLikedFriends(ctx.loggedInUser)
This is something I can easily do using vanilla GraphQL but not sure how to augment the schema returned by graffiti :/
The text was updated successfully, but these errors were encountered: