-
-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
GraphQL: Allow true GraphQL Schema Customization #6360
GraphQL: Allow true GraphQL Schema Customization #6360
Conversation
We need to allow to use a code first (no SDL Auto generated by Parse type Item {
id
name
...
} An SDL Generated from a type Item {
computedField
} Should result into type Item {
id
name
computedField
...
} I'm currently investigating about merging type fields |
Codecov Report
@@ Coverage Diff @@
## master #6360 +/- ##
==========================================
+ Coverage 93.84% 93.97% +0.12%
==========================================
Files 169 169
Lines 11626 11811 +185
==========================================
+ Hits 10911 11099 +188
+ Misses 715 712 -3
Continue to review full report at Codecov.
|
parseGraphQLServer = new ParseGraphQLServer(parseServer, {
graphQLPath: '/graphql',
graphQLCustomTypeDefs: new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
fields: {
customQuery: {
type: new GraphQLNonNull(GraphQLString),
args: {
message: { type: new GraphQLNonNull(GraphQLString) },
},
resolve: (p, { message }) => message,
},
},
}),
types: [
new GraphQLObjectType({
name: 'SomeClass',
fields: {
nameUpperCase: {
type: new GraphQLNonNull(GraphQLString),
resolve: p => p.name.toUpperCase(),
},
language: {
type: new GraphQLEnumType({
name: 'LanguageEnum',
values: {
fr: { value: 'fr' },
en: { value: 'en' },
},
}),
resolve: () => 'fr',
},
},
}),
],
}),
}); |
I can add some documentation later with an |
I realized that i can add the same behavior to InputObject (create/edition) to map a GraphQL Enum on Parse String Field |
@Moumouls Can you please understand better the idea of this PR? I mean... What is the use-case that is handled but this PR and was not handled before? |
Old implementation is right for SDL based schema (with directives & cloud functions) :
But for a code first based schema like Nexus, GraphQLJS that generate an isolated/pure GraphQL Schema (no extend feature) it do not work properly or it overwrite the auto schema, so i implemented addtional features on the GraphQLSchema use case:
With this enhancement a developer can customize (type, input, etc..), overwrite auto types/inputs, add custom fields/types/resolvers, via standard way (pure GraphQL Schema). |
@Moumouls I think it's definitely important to consider custom types to further enrich/customise the autogenerated schema. A start to this flexibility was made with our A few points I'd like to address between you, @davimacedo and I (+ anyone else interested):
|
Hi @omairvaiyani ! I invite you to consult the tests, they explains the main behavior of my improvement. Where do these customisations sit ? Exist in the code itself how do we identify major inconsistencies or incongruities up front before merging this PR? not sure to understand, the behavior is the same as the SDL one (customTypeDefs) can we build a set of guidelines, best-practices before introducing such a major addition to this feature, especially in lieu of how many recent additions there have been? It's a standard implementation, guideline are the same as Nexus/GraphQLJS, no custom behavior here. We can redirect users to this documentations! |
Example of configuration: parseGraphQLServer = new ParseGraphQLServer(parseServer, {
graphQLPath: '/graphql',
graphQLCustomTypeDefs: new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
fields: {
// Here we define a custom Query field with the associated resolver
customQuery: {
type: new GraphQLNonNull(GraphQLString),
args: {
message: { type: new GraphQLNonNull(GraphQLString) },
},
resolve: (p, { message }) => message,
},
},
}),
types: [
// Here we add a new EnumType to the final Schema
new GraphQLInputObjectType({
name: 'CreateSomeClassFieldsInput',
fields: {
type: { type: TypeEnum },
},
}),
// Here we overload the auto UpdateSomeClassFieldsInput
new GraphQLInputObjectType({
name: 'UpdateSomeClassFieldsInput',
fields: {
type: { type: TypeEnum },
},
}),
// Here we overload the SomeClass, this type will be merged with the Orginial Auto SomeClass type
new GraphQLObjectType({
name: 'SomeClass',
fields: {
nameUpperCase: {
type: new GraphQLNonNull(GraphQLString),
resolve: p => p.name.toUpperCase(),
},
// Note, here we change the output type of type field (default string)
type: { type: TypeEnum },
// A full custom field (extend SDL behavior )
language: {
type: new GraphQLEnumType({
name: 'LanguageEnum',
values: {
fr: { value: 'fr' },
en: { value: 'en' },
},
}),
resolve: () => 'fr',
},
},
}),
],
}),
}); Query example: const result = await apolloClient.query({
variables: { id: obj.id },
query: gql`
query someClass($id: ID!) {
someClass(id: $id) {
# Will be computed using name
nameUpperCase
# Use our custom resolver
language
# Use auto resolver
type
}
}
`,
}); |
May be an additional note, my implementation is a code-first implementation. A native GraphQL Schema (code) is not supposed to be manipulated by API or Parse Dashboard. |
Thanks for the clarifications! If we're adopting this code-first approach, maybe the |
Yes @omairvaiyani, Here the native code first GraphQL Schema implementation allow developers and GraphQL developers to create robust apis, using true custom resolvers (avoid the use of directives to route calls to By enabling this, we are automatically compatible with other graphql frameworks, and fully compatible with special GraphQL systems such as "Schema Stitching", "Schema Transformation" and many others. |
I agree that code-first approach is a better way achieve a robust API, especially across development environments. Over time, I'd look to phase out the current configuration in favour of this, as having two ways to achieve customisation will confuse developers. I think having a like-for-like guide to achieving the same outcomes with this approach for developers looking to move away from the |
Humm ! for me the I think we need to keep the current implementation (SDL with directives) to allow (beginner) developers to route easly some calls to The purpose of configurations are differents:
May be we can merge this one @omairvaiyani and check how developers would like to interact to customize their schemas ? |
const hasCustomField = (fields, keys) => | ||
keys | ||
? !!keys.split(',').find(keyName => !fields[keyName.split('.')[0]]) | ||
: true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this expected, no keys
means there are custom fields?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bad naming yes, i fixed it !
Okay so having a step up system where beginners use |
I think merging both will be hard (we need to avoid a super custom implementation of GraphQL), currently the best option is to keep configurations separated. And yes you are right, simple config for beginners (that don't want to dive into GraphQL backend stuff), and |
Hi guys. Sorry for my delay here. Here it goes my understanding. We currently have:
I believe that with these 3 options we are covered for all use cases and I don't really understand which additional use-case we are providing with this PR since we already have the option to customize the schema using a code approach. What I understand that this PR is trying to do is actually merge the auto-generated schema with the customized schema in a non appropriated way. Using #6360 (comment) as an example: if you have generated by Parse: type Item {
id
name
...
} And want to achieve: type Item {
id
name
computedField
...
} You should use: extend type Item {
computedField
} And not: type Item {
computedField
} I mean, if you want to extend an existing type, you need to explicitly use the If we want to give even more flexibility to customize the auto generated types, maybe we should have some kind of events that the developers could implement to receive the type, modify it, and return it, before it being pushed to the schema. |
Hi davimacedo, |
In this case, instead of merging the schemas here in Parse Server, wouldn't it be better to send a PR to the GraphQL JS implementation? |
I was thinking little bit more here. Why don't we just add an option in which we call a function passing the schema (auto-generated + sdl merge) and the developer will have the chance to change it as much as they want and then return a new schema to be used by the server? |
@davimacedo i think the add of a But just for a simple behavior of overloading the Auto schema with another schema i think it's a too huge effort for non graphql expert developer to dive into a So i propose tu keep, the merge feature (like an extend in SDL) for DX, and add a function supoort on |
@omairvaiyani any additional thoughts? Otherwise I think we could go ahead with this idea. |
@davimacedo it should be ok now :) |
Sorry guys for the delay. I'm happy go with this idea |
It seems that here we have some so CI instability |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great job! I am re-running the CI and LGTM!
* Allow real GraphQL Schema via ParseServer.start * wip * working * tests ok * add tests about enum/input use case * Add async function based merge * Better naming * remove useless condition
@Moumouls Sorry for using a PR to ask for help, haven't found official docs about this feature. I'm trying to implement a resolver that should query a relation, with some filters. How would one go about it? |
Hi @0biWanKenobi Here i assume that you query a
Important note: On custom query YOU NEED TO TAKE CARE ABOUT FIELDS selected into your custom query on server side. In the This is an advanced usage and in many cases if you just need some filtering stuff; i'll suggest to use default generated queries and may be add some security options based on CLP,ACLs, beforeFind etc... depending on the current user OR adding a new field to filter the query correctly (if it's not a security reason). On a parse server and even more on the GraphQL API, the front app should be able to query anything (the graphql API is designed in this way).Parse Server is designed to give power and choice to API users (like a front app). I use parse from a while and in 90% of use cases a custom server side filter could be avoided using ACL,CLP,Role, beforeFind and afterFind 🙂 Don't feel bad to let the front end have the power it will save you a lot of time. Also a correct usage of ACL,CLP,Role will significantly improve the security of your Parse Server through the GraphQL API. |
No description provided.