Skip to content
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

Thoughts on resolver forwarding #40

Closed
marktani opened this issue Jan 30, 2018 · 24 comments
Closed

Thoughts on resolver forwarding #40

marktani opened this issue Jan 30, 2018 · 24 comments

Comments

@marktani
Copy link

Based on recent feedback, I'd like to kickstart an open discussion about the state of resolver forwarding and potential improvements.

forwardTo is a great convenience function:

const { forwardTo } = require('prisma-binding')

const Query = {
  posts: forwardTo('db'),
}

Here, we forward the posts query from our GraphQL server to a Prisma API.
However, as soon as we want to modify the behaviour by, say, checking authentication before resolving, we have to fall back to the full ctx.db.query call:

const Query = {
   posts(parent, { where, skip, last, first }, ctx, info) {
    await checkAuth()
    return ctx.db.query.posts({ where, skip, last, first }, info)
  },
}

The best way to tackle this at the moment is to write a little convenience wrapper to check authentication like so:

const Query = {
   posts: checkAuth(forwardTo('db'))
}

Can you come up with other scenarios where resolver forwarding falls short, and approaches to improve the entire experience surrounding resolver forwarding?

#37 is a great idea in that direction, but I'm sure there are more great approaches to be found 🙂

@SpaceK33z
Copy link
Contributor

SpaceK33z commented Feb 3, 2018

Can you come up with other scenarios where resolver forwarding falls short, and approaches to improve the entire experience surrounding resolver forwarding?

I think the biggest issue with forwardTo() by far is that you have to manually copy all the generated GraphQL code by Prisma to your own schema.graphql. So if you want to forward posts you'd have to copy and paste this:

posts(
    where: PostWhereInput
    orderBy: PostOrderByInput
    skip: Int
    after: String
    before: String
    first: Int
    last: Int
): [User]!

@kbrandwijk
Copy link
Contributor

@SpaceK33z No, that's not the case. You can just import it, by putting # import Query.posts from 'prisma.graphql' in your schema.graphql.

@SpaceK33z
Copy link
Contributor

Thanks, I did not know that 😅. Still learning GraphQL...

@ramsaylanier
Copy link

Would it be possible to compare GraphQL schemas between the database and application using introspection and then determine which application queries don't have resolvers?

@arrowfunxtion
Copy link

arrowfunxtion commented Feb 23, 2018

@kbrandwijk Import doesn't automatically expose the imported type to client. It's only referencing, that being said, I still need to redefine the type to expose to the client.

My concerns is, say, I want to be able to expose automatically all the generated type from generated/prisma.graphql to the client without manually redefine on schema.graphql

@arrowfunxtion
Copy link

@marktani I have no problem on performing auth checking using forwardTo. Instead of wrapping per node basis, I would wrap all the resolvers before passing it GraphQLServer configuration. My concern now is what I state on my previous comments

@ramsaylanier
Copy link

@arrowfunxtion wrapping all of the resolvers with an auth check works if every query requires authentication. It's doesn't work if you want per query authentication.

@arrowfunxtion
Copy link

arrowfunxtion commented Feb 24, 2018

@ramsaylanier Nope, it works per query too. Basically the wrapper will check if the particular query needs an auth or authorization (defined in separate files, I called policies.js). If there is policy defined, then use the policy function, if not, let it pass

@lastmjs
Copy link

lastmjs commented Mar 13, 2018

This blog post offers potential simple solutions to these issues: https://medium.com/@lastmjs/advanced-graphql-directive-permissions-with-prisma-fdee6f846044

It shows a simple way to automatically expose all generated queries and mutations, and to easily combine those with your own custom schema and resolvers. Copying schema information is kept to a minimum. Permissions are handled elegantly through custom directives. Let me know if you have any questions.

@rcy
Copy link

rcy commented Mar 14, 2018

Not sure if this is the place to share, but I posted a PR #65 which adds an optional targetFieldName argument to forwardTo. I am in the process of migrating a large project from the graphcool framework to prisma, and want to leave as much dependent client/frontend code alone as possible. So now I can do:

allPosts: forwardTo('db', 'posts')

Basically, allow forwarding to db endpoints whose name doesn't match the app endpoint.

@schickling
Copy link
Contributor

That's a great suggestion and already a good step in the direction which I've laid out in this comment.

@schickling
Copy link
Contributor

@timsuchanek has written up some thoughts on how to push this forward. Curious to hear more thoughts on this: #83

@usulpro
Copy link

usulpro commented Oct 2, 2018

Hi! So what is the current status of this issue? is there a recommended approach for that?

@breytex
Copy link

breytex commented Nov 25, 2018

Bump, also interested in a minimal stack approach reducing duplicated schema code and trivial resolvers :)

@schickling
Copy link
Contributor

schickling commented Dec 3, 2018

Update: We're currently working on a new kind of GraphQL server framework which doesn't require you duplicate your schema/resolver code while still making it easy to adjust/evolve your schema in an easy and (type-)safe way. (The best part: you won't have to think about schema delegation or mess with the info object which is quite difficult to wrap your head around for newcomers.)

@breytex
Copy link

breytex commented Dec 3, 2018

Hi @schickling,
that's brilliant news.
I tried to come up with a system as you described based on directives, but that gets messy very very quickly.

Can't wait to see it as a core feature.
Is there any way I can help? Joining development? Testing beta?

Thanks!

@webberwang
Copy link

webberwang commented May 7, 2019

For simple CRUD operations which happen often, having to specify resolvers is still cumbersome even with forwardTo().

Would be cool to have some sort of config that lets you set forwardAll: true.

prisma-labs/prisma-binding#383

@nikolasburk
Copy link
Contributor

nikolasburk commented May 7, 2019

Hey @webberwang, this is a great point! Have you seen nexus-prisma already? It provides a similar functionality as forwardTo and easily let's you expose and customize Prisma's CRUD operations.

Here is a simple example that shows how to setup a CRUD API for your Prisma models:

datamodel.prisma

type User {
  id: ID! @id
  email: String! @unique
  name: String
  posts: [Post!]!
}

type Post {
  id: ID! @id
  createdAt: DateTime! @createdAt
  updatedAt: DateTime! @updatedAt
  published: Boolean! @default(value: false)
  title: String!
  content: String
  author: User!
}

index.ts (GraphQL server code)

const Query = prismaObjectType({
  name: 'Query',
  definition(t) {
    t.prismaFields(['*'])
  },
})

const Mutation = prismaObjectType({
  name: 'Mutation',
  definition(t) {
    t.prismaFields(['*'])
  },
})

const schema = makePrismaSchema({
  types: [Query, Mutation],

  prisma: {
    datamodelInfo,
    client: prisma,
  },

}

schema.graphql (generated)

type Mutation {
  createPost(data: PostCreateInput!): Post!
  createUser(data: UserCreateInput!): User!
  deleteManyPosts(where: PostWhereInput): BatchPayload!
  deleteManyUsers(where: UserWhereInput): BatchPayload!
  deletePost(where: PostWhereUniqueInput!): Post
  deleteUser(where: UserWhereUniqueInput!): User
  updateManyPosts(data: PostUpdateManyMutationInput!, where: PostWhereInput): BatchPayload!
  updateManyUsers(data: UserUpdateManyMutationInput!, where: UserWhereInput): BatchPayload!
  updatePost(data: PostUpdateInput!, where: PostWhereUniqueInput!): Post
  updateUser(data: UserUpdateInput!, where: UserWhereUniqueInput!): User
  upsertPost(create: PostCreateInput!, update: PostUpdateInput!, where: PostWhereUniqueInput!): Post!
  upsertUser(create: UserCreateInput!, update: UserUpdateInput!, where: UserWhereUniqueInput!): User!
}

type Query {
  node(id: ID!): Node
  post(where: PostWhereUniqueInput!): Post
  posts(after: String, before: String, first: Int, last: Int, orderBy: PostOrderByInput, skip: Int, where: PostWhereInput): [Post!]!
  postsConnection(after: String, before: String, first: Int, last: Int, orderBy: PostOrderByInput, skip: Int, where: PostWhereInput): PostConnection!
  user(where: UserWhereUniqueInput!): User
  users(after: String, before: String, first: Int, last: Int, orderBy: UserOrderByInput, skip: Int, where: UserWhereInput): [User!]!
  usersConnection(after: String, before: String, first: Int, last: Int, orderBy: UserOrderByInput, skip: Int, where: UserWhereInput): UserConnection!
}

# ... and all other generated Prisma types

Also feel free to check out this 15min demo video that shows how to get started with nexus-prisma.

@webberwang
Copy link

webberwang commented May 7, 2019

@nikolasburk Thank you for the informative demo code. I have actually looked into the code-first approach. While it sounds great to have the single source of truth to be in the actual programming language, the syntax just isn't as appealing as SDL.

Would it be possible to use nexus-prisma along side the normal SDL, where I use nexus-prisma mainly, but use SDL to create custom Query/Mutations?


Edit:

I was able to use mergeSchemas to combine the nexus-prisma schema & the SDL schema. Then share schema across modules using import.

@lastmjs
Copy link

lastmjs commented May 7, 2019

Exactly, I'm pretty disappointed in Prisma's new direction

@nikolasburk
Copy link
Contributor

the syntax just isn't as appealing as SDL

That's a great point, I definitely agree that SDL is extremely nice to read! I personally prefer having a type-safe API over an untyped string-based API to construct my schema. For me, it's enough that the SDL is generated and I can use it as foundation to discuss the API with my colleagues, I don't need it when initially implementing the schema. But that's just my subjective opinion and I think the other one is equally valid – so SDL-first by all means remains a valid approach for building GraphQL servers!

I also want to point out that while we believe that code-first is an amazing approach that solves a lot of problems, it is totally possible to keep using Prisma when building your schema SDL-first! This might require some more boilerplate (which can be alleviated by codegen tools like graphqlgen or graphql-code-generator) and not give you the same level of type-safety, but overall absolutely possible and if that's your preferred way, you should stick to it 🙂

@lastmjs
Copy link

lastmjs commented May 8, 2019

I'm glad SDL first is still possible. As for the argument about type safety, why can't someone just create plugins based off of the TypeScript language server or whatever that is called, but for the GraphQL language. TypeScript only gives you type safety because of the language tools around it. For example, lit-plugin uses those TypeScript tools to provide static type checking for HTML. I'm pretty sure the same kind of thing could be done for GraphQL

@webberwang
Copy link

webberwang commented May 8, 2019

@lastmjs You can generate types from schema.graphql using https://graphql-code-generator.com/

@nikolasburk Are there examples of how the front end gql queries can benefit from nexus? I couldn't find any so far. Would be awesome to be able to type gql Query/Mutation. Right now I can only type check the arguments to the gql Query/Mutation, but not the actual Query/Mutation itself.

Or does nexus allow createUser to be added to the react-apollo <Mutation mutation={createUser}/> as such?

Instead of

export const createUser = gql`mutation...`

2019-05-08_14-10-01

@Urigo
Copy link
Collaborator

Urigo commented May 26, 2020

Thank you for reporting.

In the last few months, since the transition of many libraries under The Guild's leadership, We've reviewed and released many improvements and versions to graphql-cli, graphql-config and graphql-import.

We've reviewed graphql-binding, had many meetings with current users and engaged the community also through the roadmap issue.

What we've found is that the new GraphQL Mesh library is covering not only all the current capabilities of GraphQL Binding, but also the future ideas that were introduced in the original GraphQL Binding blog post and haven't come to life yet.

And the best thing - GraphQL Mesh gives you all those capabilities, even if your source is not a GraphQL service at all!
it can be GraphQL, OpenAPI/Swagger, gRPC, SQL or any other source!
And of course you can even merge all those sources into a single SDK.

Just like GraphQL Binding, you get a fully typed SDK (thanks to the protocols SDKs and the GraphQL Code Generator), but from any source, and that SDK can run anywhere, as a connector or as a full blown gateway.
And you can share your own "Mesh Modules" (which you would probably call "your own binding") and our community already created many of those!
Also, we decided to simply expose regular GraphQL, so you can choose how to consume it using all the awesome fluent client SDKs out there.

If you think that we've missed anything from GraphQL Binding that is not supported in a better way in GraphQL Mesh, please let us know!


In the context of that particular issue - GraphQL Mesh uses a few interesting mechanisms like transforms and handlers, including using the new schema stitching introduced in GraphQL Tools v6 and Apollo Federation.
You can reuse underlining APIs, transform them in however way you like.
I believe it should give you a nice experience and power but if you think forwardTo in some way is missing in GraphQL Mesh, please feel free to open a new issue on the GraphQL Mesh repo.

We're looking forward for your feedback of how we can make your experience even better!

@Urigo Urigo closed this as completed May 26, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests