Coder Social home page Coder Social logo

graphql-rules / graphql-rules Goto Github PK

View Code? Open in Web Editor NEW
169.0 7.0 26.0 3.88 MB

GraphQL Rules

Home Page: https://graphql-rules.onrender.com

License: Other

JavaScript 10.60% TypeScript 48.79% CSS 40.44% Shell 0.17%
graphql graphql-rules graphql-design graphql-patterns

graphql-rules's Introduction

graphql-rules

Currently, site available here https://graphql-rules.com/

Likely temporarily, while we figure out domain ownership etc. site available here https://graphql-rules.onrender.com

Content of rules can be found here docs/rules

Feel free to send pull requests and open issues.

Quick local start

pnpm install
pnpm develop

Under the hood site uses Gatsby. Before start you may read great tutorial.

Contributions

We welcome any contributions to the rules or code. Before making any code contributions make sure to open issue or discussion so you don't waste your time on something that won't be merged. Thanks!

CC-BY-4.0 By @nodkz and awesome folks. Maintained by @rip212

graphql-rules's People

Contributors

andrew-demb avatar asherkin avatar dankimhaejun avatar dependabot[bot] avatar dimamachina avatar dkassen avatar dnalborczyk avatar duckbrain avatar jekrock avatar jillesme avatar leoloso avatar makazone avatar nodkz avatar rip21 avatar stivenviedman avatar trescube avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

graphql-rules's Issues

[types section]: Add note about `Int` boundaries in real life

Add here: https://github.com/graphql-rules/graphql-rules/tree/master/docs/rules/02-type
Author: Andrii Chyzh @andriichyzh : https://user-images.githubusercontent.com/1946920/59221573-6c2c6380-8be9-11e9-877e-833d40202924.png

Translated version:

Be careful with Int type

  • This is signed 32-bit integer
  • If the integer interval value represents a value less than -2^31 or greater than or equal 2^31, a field error should be raised.
  • In numbers: -2'147'483'648 / 2'147'483'647

This numbers in real life

  • Time (in milliseconds): ~24.86 days
  • Data Volume (in bytes): ~2.15 Gb

If you return from backend or client pass via argument a value which greater - you'll get an ERROR.

Background and more information about the mutation payload query rule

I just read https://graphql-rules.com/rules/mutation-payload-query. This is an interesting approach which kind of boggled my mind when I read it -- it upset, in a good way, my whole thinking about what a GraphQL mutation could do vs an equivalent regular RPC.

That being said, it still bothers me a bit that a mutation would allow a user to query for data completely unrelated to the mutation they requested.

Can someone explain the real-world experience / background that led to the creation of this rule? I'd be interested in understanding the use cases not already presented in the rule text, if any, and alternative approaches considered, such as simply ensuring that the schema mutation exposes the changed fields in the payload, instead of the entire query.

Suggestion: go beyond CRUD

While explaining CRUD operations is a great start, it could be interesting to also provide examples for things like voting, like/dislike, add to cart, checkout, sign up, etc.

Suggestion: alternative patterns

Another idea I had was to provide multiple patterns when relevant. I realize this might feel a little counter-productive if the goal is to provide clear guidelines, but on the other hand it might save us from wasting time debating which is the "one true pattern" every time.

To pick a simple example, there is not a clear consensus between updateMovie and movieUpdate, so maybe we just feature both? Same with pagination, there is limit-based pagination and cursor-based pagination.

Here's an outline that shows what I mean:

  • CRUD
    • Introduction
    • Queries
      • Single
      • List
        • Guidelines
        • Pattern A (Prisma) [basic]
          • movieList
        • Pattern B (Vulcan) [experimental]
          • Movies
      • Filtering
      • Pagination
    • Mutations
      • Create
      • Update
        • Guidelines
        • Pattern A (PostGraphile, Vulcan) [basic]
          • createMovie(...)
        • Pattern B (Prisma, FooQL) [basic]
          • movieCreate(...)
        • Pattern C (AppSync) [advanced]
          • newMovie(...)
  • Auth
    • Introduction
    • Mutations
      • Log in
      • Log out
      • Reset password
  • E-commerce
    • Mutations
      • Add to cart
      • Check out
  • Social Media
    • Mutations
      • Voting
      • Like

Mutation errors mechanism seems sub-optimal

I reviewed https://graphql-rules.com/rules/mutation-payload-errors closely. There seems to be a better approach.

The rule suggests:

type LikePostPayload {
  recordId: Int
  # `record` is nullable! If there is an error we may return null for Post
  record: Post
  errors: [LikePostProblems!]
}

Note the statement:

record is nullable! If there is an error we may return null for Post

Of course, the downside of this is that making record: Post nullable solely for this reason conflicts with rule https://graphql-rules.com/rules/output-non-null. Typed languages on the client side that handle nullability (Kotlin, Elm, Typescript, Rescript, etc.) now lose the field nullability information for the success case.

Instead, why not define the payload as a union type of both success and error conditions:

union LikePostPayload = LikePostSuccess | SpikeProtectionProblem | PostDoesNotExistsProblem

type LikePostSuccess {
  recordId: Int!
  record: Post!
}

The LikePostPayload union very nicely documents in one place all the possible return values for the mutation, and typed client languages with pattern matching or equivalent functionality deal very nicely with this. Furthermore, the LikePostSuccess type correctly models the nullability of the recordId and record fields for the success case.

The mutation is cleaner because it is now symmetric and __typename can now be a top-level field rather than being buried underneath errors. In other words, there is a single top-level discriminator for clients, rather than the client having to inspect the errors field first:

mutation {
  likePost(id: 666) {
    __typename
    ... on LikePostSuccess {
      recordId
      record {
        title
        likes
      }
    }
    ... on ProblemInterface {
      message
    }
    ... on SpikeProtectionProblem {
      wait
    }
    ... on PostDoesNotExistsProblem {
      postId
    }
  }
}

NOTE: As an aside, I also removed message from ... on SpikeProtectionProblem and ... on PostDoesNotExistsProblem. It is not necessary because message will already be returned in both those cases via ... on ProblemInterface.

Rule 2.2 may be anti-pattern in some cases

Nowadays GraphQL is a pretty common option for Backend For Frontend (BFF) services.

Rule 2.2. may do bad bad favor if you do not fully have control over the data you expose to the client.

Problem example
Schema

type User {
    status: StatusEnum
}

 enum StatusEnum {
    ACTIVE
    PENDING
    REJECTED
}

Resolver

module.exports = {
  Query: {
      users: () => {
           return axios.get('https://user.microservice.com/api/users')
      }
}

And in this case, the enum definition of this schema is an antipattern, as at one day user microservice may be changed and new status is added, but BFF is gonna be stale.This will cause errors and the client may not get a response, as new status is absent in StatusEnum For such cases is better to pick string type, as enum is not failure-tolerant.

So I suggest clearly express to user, that this rule should be applied only if GraphQL service has control over the data

Rule 6.1 on Namespace-types breaks the graphql spec

I've been looking at rule 6.1, which presents a compelling argument for using namespaces to group mutations. I really like this approach. However, after doing further research, it appears that this suggestion breaks the GraphQL spec in a subtle, but potentially important way.

Per the official spec, top-level mutations are to be performed serially.

There is a concise write-up detailing how namespaces violate this here: https://graphqlme.com/2020/07/09/namespaced-graphql-mutations-its-a-trap/

I still like the pattern and can see it making sense for internal APIs, where developers may be made aware of the change to the spec, or are able to control server-side execution within the namespace.

But unless I'm misunderstanding something, it may be nice to add a disclaimer so people are aware of this trade-off.

Discussion: Project Name

I've heard from some people that "GraphQL Rules" as a name can feel a bit too strict and rigid, and I fear some people might be unwilling to contribute because they feel GraphQL shouldn't have "rules" beyond what's in the spec.

Maybe renaming the project to "GraphQL Patterns" would make it clearer that those patterns are suggestions and best practices more than absolute rules?

There is already a podcast called GraphQL Patterns by @Nader3 but maybe he wouldn't mind letting us share the same name?

Mutation Patterns: Single vs Double Argument

I know most guides recommend a "single input argument" pattern for mutations, but I wanted to get feedback on this alternate pattern. Here's what I mean.

Single Argument

The single argument makes sense for update mutations where input actually contains multiple nested arguments:

mutation updateMovie {  
  updateMovie(input: { filter: { title: "Die Hard" } }, data: { year: 1989 } }) {
    result {
       ...SomeFragment
    }
  }
}

But for create mutations for example that extra level of nesting doesn't seem useful?

mutation createMovie {  
  createMovie(input: { data: { title: "Die Hard", year: 1989 } }) {
    result {
       ...SomeFragment
    }
  }
}

Double Argument

Maybe for that reason, Hasura for example uses two arguments, where and _set, and doesn't nest them inside an input:

mutation update_article {
  update_article(
    where: {id: {_eq: 3}},
    _set: {
      title: "lorem ipsum",
      content: "dolor sit amet",
      rating: 2
    }
  ) {
    affected_rows
    returning {
      ...
    }
  }
}

Elaborating on this, I would like to suggest that a double argument pattern can pretty much work for any of the basic CRUD queries and mutations. The two arguments being:

  • selector: { filter: { ... }, sort: { ... } }: an argument used to select data.
  • data (a.k.a. movie, patch, etc.): contains any data that will be mutated.

Unlike the Hasura example where where is a top-level argument, selector would itself contain filter (a.k.a. where), sort (a.k.a. orderBy), and so on. The idea is to use the same selector input type anywhere you need to select data, whether it's for a query or mutation.

Which gives us:

  • movie(selector)
  • movies(selector)
  • createMovie(data)
  • updateMovie(selector, data)
  • deleteMovie(selector)

(Note: to that I would even add a third options argument that control how the server should behave (e.g. caching behavior). But that's another matter.)

Basically, I get why a single argument is better than n arguments (as explained here) but I think two arguments is actually better than both of these approaches, since selecting vs mutating data seems to be a good natural distinction that lends itself to this 2-argument approach.

EDIT: oh and the selector and data names are just placeholders, maybe there are better ones.

Can I translate Korean for personal use?

hi everyone!

First of all, Thanks for making this graphql-rules. It will be nice for beginners like myself.

If you don't mind, can I translate this document Korean for personal use?

I want to study this document and record my personal blog. (maybe this blog https://velog.io/@dankim ).

After you give me a permission, I will do next step.

Thanks for creating this nice document.

Incorrect citation of scientific paper

Thank you for this great document!

I would suggest to remove the argument saying there is no difference between camel case and underscore case.

The GraphQL rules argues that Sharif and Maletic "did not find any favourites" between both writing style. This is incorrect. Sharif and Maletic did find a "significant improvement in time and lower visual effort with the underscore style", and "up to twice as much with respect to time" for novices.

Mutation argument naming

In any mutation you will need to A) identify the document/row/item to mutate and B) pass the mutated data. Sometimes these can both be done with a single argument (pass an object containing an id field and use that to figure out which row to update with that object's data), but either way I'd be curious to know what names people use for these arguments.

For selecting data, both id and/or filter (if you want to be more flexible) seem like good choices.

For the mutation payload though there are more options. PostGraphile uses patch for example, while Hasura uses _set (or _inc). Prisma and Vulcan both use data. You could also call the argument based on the type of the data being mutated (i.e. post, movie, comment, etc.) although I don't recommend it as it makes your API less consistent.

Rule 3.3. Colocate related fields in custom types โ€“ is there a solution 3?

Rule 3.3. provides two solutions:

  1. Create a new type with related fields
  2. Use union types with fragments

I believe there is a 3rd solution that has aspects of 1 & 2, but does not use implements:

type Claim {
  text: String!
  source: ClaimSource
}

type ClaimSourcePhone {
  phone: String!
  operatorCode: String!
}

type ClaimSourceMail {
  email: String!
}

union ClaimSource = ClaimSourcePhone | ClaimSourceMail

Is this a good solution or there are any gotcha's in doing it this way?

Suggestions: basic/advanced/experimental patterns

To make it easier to reach consensus on specific patterns, I think it could be useful to tag them according to their "status". What I have in mind would be:

  • Basic: patterns or best practices that nearly everybody agrees on and that should probably be implemented by most GraphQL projects.
  • Advanced: patterns or practices that people agree on, but might not be needed by every project. For example, something like cursor-based pagination.
  • Experimental: patterns that some people find useful, but haven't reached a consensus yet; or maybe go against the GraphQL spec somehow.

Having this "experimental" designation would make it easier to include new patterns even if people don't unanimously approve of them. They could always "graduate" to basic or advanced later on once they become more widespread.

Thoughts?

Company specific rules

Do you guys accept rules from different companies?
I would agree with most of the suggestions but for some of the cases.
I would really love to use this website as an entry point for anyone who wants to build a graphql schema. Do you think it is possible to extend it with company-specific rules.

Suggestion: list who uses each pattern

I don't know how easy it would be to do this in practice, but I think it could be very cool to list who uses the different patterns.

For example:

  • createMovie: syntax used by Hasura, PostGraphile
  • MovieCreate: syntax used by Prisma, Vulcan
  • cursor-based pagination: used by Product Hunt, GitHub, PostGraphile
  • single input mutations: pattern followed by Relay, Apollo, PostGraphile

etc.

To give a practical example, it could be useful to see at a glance which APIs implement the Relay spec. Or maybe you already use Hasura and see that PostGraphile does things in a similar way, so you decide to check it out as well.

Also, linking back to major actors in the GraphQL ecosystem like Prisma, PostGraphile, etc. would give them an incentive to contribute to the project.

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.