Comments (29)
@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.
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.
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.
Adding customQueries and customMutations to getSchema:
wellth-app@404395f
@tothandras may review it and import
from graffiti-mongoose.
@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.
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.
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.
@zopf could you provide a simple example of customQuery and customMutations?
from graffiti-mongoose.
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.
any updates on this issue?
from graffiti-mongoose.
@tothandras what solution should we use for now??
At the moment I'm implementing the hacky solution of @jashmenn
from graffiti-mongoose.
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.
@zuhair-naqvi Do you have a preferred API for getSchema
to add this feature?
from graffiti-mongoose.
+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.
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.
@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.
Any further thoughts?
from graffiti-mongoose.
@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.
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:
- Add queries and mutators to existing
mongoose.Schema
s/mongoose.model
s - 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.
@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.
this custom query is only available on the root node? can i add a custom query inside a Model?
from graffiti-mongoose.
I only added it on the root node. Haven't really thought about it inside of models...
from graffiti-mongoose.
Inside of models is like a virtual field in fact.
And it would fix some issues: #102, #64, #63, #25
from graffiti-mongoose.
thanks for the great example @zopf I will take a look tm
from graffiti-mongoose.
@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.
Sorry, is there any solution for that which will work on current version?
from graffiti-mongoose.
@lyxsus graphql-compose have everything 😉
from graffiti-mongoose.
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.
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)
- Implicit dependency on babel-polyfill HOT 9
- Cannot set currently-valued fields to null in update mutation HOT 2
- How to make a custom field: id? HOT 10
- "TypeError: Cannot read property 'forEach' of undefined" when embedded object field is a reference HOT 6
- Custom query thunk - accepts only InputObject types HOT 3
- Question on customQueries HOT 7
- Example project doesnt work with [email protected] and [email protected] HOT 1
- Async hook support HOT 2
- How to use Express Middleware for Authentication/Authorization. HOT 4
- Get specific field validation error?
- How to mutate nested Schema? HOT 3
- What is different between id and _id HOT 1
- UNMET PEER DEPENDENCY HOT 1
- Maintainer Volunteer Thread HOT 1
- Mongoose ObjectId and GraphQL id is not the same. How to link them? HOT 5
- It is possible to add some description to schema and to be reflected in the docs?
- Does graffiti-mongoose works with react-apollo ? HOT 2
- Custom mutations?
- Custom queries/mutations HOT 1
- setTypeFields bug
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from graffiti-mongoose.