Giter Club home page Giter Club logo

graphql-design-tutorial's Introduction

Shopify GraphQL Design Tutorial

This tutorial was originally created by Shopify for internal purposes. We've created a public version of it since we think it's useful to anyone creating a GraphQL API.

It's based on lessons learned from creating and evolving production schemas at Shopify over almost 3 years. The tutorial has evolved and will continue to change in the future so nothing is set in stone.

We believe these design guidelines work in most cases. They may not all work for you. Even within the company we still question them and have exceptions since most rules can't apply 100% of the time. So don't just blindly copy and implement all of them. Pick and choose which ones make sense for you and your use cases.

Read the full tutorial here. It's also been translated by the community into a few other languages.

Contributing

Contributions are welcome; however, this tutorial reflects rules specific to Shopify and any substantial changes to those will primarily happen internally and not through external contributions.

Corrections and additional translations are welcome in the lang folder.

Code of Conduct

Anyone who wishes to contribute through code or issues, take a look at the CODE_OF_CONDUCT.md.

License

MIT, see LICENSE for details.

graphql-design-tutorial's People

Contributors

benjaminx avatar christianboehlke avatar daiwahome avatar darmody avatar dphm avatar eapache avatar emilebosch avatar hanpama avatar heka1024 avatar ise avatar jakearchibald avatar jamendozag avatar jasonflorentino avatar jasperste avatar jeff-tian avatar justintross avatar kazu728 avatar kissybnts avatar kkushimoto avatar kotaroyamazaki avatar nakaokat avatar nevir avatar rebeccajfriedman avatar ricguti avatar roseline124 avatar sat0yu avatar swalkinshaw avatar tgwizard avatar titanoboa avatar toadfansboy 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  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

graphql-design-tutorial's Issues

In GraphQL data modeling, you should think of your data as a graph, not as tables

It means that your data is a bunch of Nodes and Edges, not a bunch of table or column..

Since the product-to-collection relationship is many-to-many, you've got a join table in the middle called CollectionMembership

You should be able to design the a Many-to-Many relationship in your schema without the CollectionMembership things

Translation

Thank you so much for sharing such a great tutorial.
If you don't mind, can I post its Japanese translation to a tech blog?

Missing code segment

At some point the text reads:

This draft is a lot better than a

However, no draft precedes that section.

Is there a code segment missing?

Interfaces

Hi everyone!

First of all, thanks for this awesome tutorial, it is really helpful.

I have a question about interfaces. In this section states that AutomaticCollection and ManualCollection are actually implementation detail which could be transparent for the client.

I see that for a ManualCollection, make sense that it has empty CollectionRule, so not a big deal breaker. But imagine if one day it is necessary to add new fields which is ManualCollection specific.

My thoughts:

  • I could keep the type Collection, and add manualCollectionFieldSet to include all these ManualCollection specific fields. Like this:
type Collection {
    ...
    manualCollectionFiledSet: ManualCollectionFieldSet
}

type ManualCollectionFieldSet {
    ...
    // manual collection specific fields
}

But for this solution, the response may contain a lot of null values.

  • I could create an interface and have different implementations. I personally prefer this solution because the result is cleaner and the schema don't need a lot of null type fields.
interface Collection {
   ...
}

type ManualCollectionFieldSet implements Collection {
   ...
   // manual collection specific fields
}

I'm a bit confused because I'm facing a similar situation. Isn't it a better option to have an interface for Collection and two different implementations? It would be also easier to plug a new Collection type. What are your experiences working with interfaces? Is there any caveat I not aware of?

Thanks a lot!

ㄴㅇㄴ

Tasks

No tasks being tracked yet.

Putting data on edges (or not)

This is a design topic which has been discussed a bunch internally at my current company, and which is not addressed in the design tutorial. IIRC at Shopify we ended up at (what I believe to be) the correct answer without much discussion and never really thought about it, but in hindsight I think that was a fluke, and it's worth making an official design ruling on. This should probably be worked into the design tutorial, or tacked on as another appendix or something.

My current belief is:

While there are some cases where putting data directly on connection edge types is OK, there are many where it ends up causing issues, and should be avoided; it is best to adopt the simple rule never to put data on edge types. This may result in a few extra types in the schema, but simplifies reading/interpretation because it becomes safe to assume that edge types are always just boilerplate and can be safely ignored.

The cases where putting data on edges can cause issues include:

  • When you have a many-to-many relation, with connections running both ways from A to B and also from B to A. Putting data directly on the edge would require you to duplicate fields between the A-to-B-edge type and the B-to-A-edge type.
  • When you have several types (A, B, C), all with one-to-many relationships to the same type (X). This is solvable, but you would have to remember to create/use AXEdge and BXEdge etc. instead of the standard/default generated XEdge, which can't reasonably have edge-data for two or more different relationships.
  • When you have a mutation operating on the relationship between an A and a B, in which case that mutation would have to duplicate fields from the edge type, or return an edge type directly (in addition to whichever of A or B isn't covered by the edge), which is gross.
  • Probably others I'm not thinking of right now.

userErrors in Payload interface?

Obviously would like to start with a huge thank you to the authors of this amazing doc (seems like a recurring preface in all these "issues")

If all mutations should have this userErrors field, would it make sense to recommend a Payload interface with with that field?

When designing GraphQL, you should think of your data as a Graph instead of Tables

"Since the product-to-collection relationship is many-to-many, you've got a join table in the middle called CollectionMembership."

It feels like Authors still think about the traditional tables and columns when designing a GraphQL model. As a result, we got a "table" with 2 "columns" like this... in a GraphQL data model.

type CollectionMembership {
  collectionId: ID!
  productId: ID!
}

IMO, It is not the right mind to brain storming a GraphQL, you have to treat your data as a Graph, so think about them in term "nodes" and "edges" instead of "table" and "column".

By consequence, the relationship between Product & Collection should be designed as following:

type Product {
  id: ID!
  name: String!
  # Other product related fields...
  collections: ProductCollectionConnection!
}

type Collection {
  id: ID!
  name: String!
  # Other collection related fields...
  products: CollectionProductConnection!
}

type ProductCollectionConnection {
  edges: [ProductCollectionEdge!]!  # Non-null list of edges
  pageInfo: PageInfo!  # Information for pagination (optional)
}

type CollectionProductConnection {
  edges: [CollectionProductEdge!]!  # Non-null list of edges
  pageInfo: PageInfo!  # Information for pagination (optional)
}

type ProductCollectionEdge {
  node: Collection!  # Collection this edge points to
  # Optional fields specific to the product-collection relationship (e.g., position)
}

type CollectionProductEdge {
  node: Product!  # Product this edge points to
  # Optional fields specific to the collection-product relationship
}

With this schema, you can write GraphQL queries to fetch products with their collections and vice versa. Here's an example:

{
  product(id: 1) {
    name
    collections {
      edges {
        node {
          name
        }
      }
    }
  }
}

In my example is a 2-directions relationship, Product->Collection & Collection->Product, you can remove one direction if it is not interessting to keep..

Defaulting to semantic nullability

This guide has been a breath of fresh air with regard to nullability handing, in stark contrast from the official GraphQL standpoint on the matter:

https://graphql.org/learn/best-practices/#nullability
https://medium.com/@calebmer/when-to-use-graphql-non-null-fields-4059337f6fc8

And countless articles like this one, in agreement:
https://medium.com/expedia-group-tech/nullability-in-graphql-b8d06fbd8a3c

But I prefer the practice of catching errors at the resolver level, and either allowing the query to fail, or replacing the affected fields with "blank" or error-informing ("This comment failed to load") data. For me, this is far more manageable than dealing with non-semantic nulls everywhere.

Would it make sense for this tutorial talk about this, since it is a deviation from the GraphQL standard?

クライアントをGraphQLクライアントとするのはいかがでしょうか?

始めまして。とても参考になるドキュメントを公開して頂き、ありがとうございます。

日本語訳について提案があります。クライアントをGraphQLクライアントとするのはいかがでしょうか?

理由は、日本語だと「クライアント」には「ユーザ」と同様の意味を持つことがあり混乱するからです。

以上、ご検討のほどお願い致します。

Grouping GraphQL Mutations is Supported

First of all, this is the finest tutorial I've seen so far on designing a GraphQL schema. You have validated many of my observations that I have made over the last two years. Thank you for posting this.

The following statement from the tutorial document isn't quite accurate and perhaps you had missed that mutations are simply types just as queries are.

Unfortunately, GraphQL does not provide a method for grouping or otherwise organizing mutations, so we are forced into alphabetization as a workaround. Putting the core type first ensures that all of the related mutations group together in the final list.

You can create types to encapsulate your mutations and namespace them to create logical groups of these operations.

This is your example of the Mutation type which has prefixed names for the collection mutations:

type Mutation {
  collectionDelete(id: ID!)
  collectionPublish(collectionId: ID!)
  collectionUnpublish(collectionId: ID!)
  collectionAddProducts(collectionId: ID!, productIds: [ID!]!)
  collectionRemoveProducts(collectionId: ID!, productIds: [ID!])
  collectionCreate(title: String!, ruleSet: CollectionRuleSetInput, image: ImageInput, description: HTML!)
}

Instead of prefixing every mutation operation with the target object's name you can simply create a CollectionMutation type:

type CollectionMutation {
  # We've removed the prefixing of "collection" to reduce noise.
  delete(id: ID!)
  publish(collectionId: ID!)
  collectionPublish(collectionId: ID!)
  # ... Other operations removed for brevity
}

Then simply use that type within your root Mutation type:

"""
Root mutation type.
"""
type Mutation {

  """ 
  Mutations for Collection.
  """
  collection: CollectionMutation

  """
  Mutations for Order.
  """
  order: OrderMutation

  # ... Other mutation types
}

Then your mutation is simply invoked using the new type and scoped to Collection.

mutation DeleteCollection($id: ID!){
  collection {
    delete(id: $id)
  }
}

This pattern will reduce the confusion of seeing a giant list of mutations at the root level.

example implementation

thanks for this great tutorial! I find this very useful. I have been trying to implement this in the past week,
but am facing friction, abstracting areas like userErrors. It would be very useful if an example was also provided, which implements this spec, so that everyone can follow along easily.

this is what I've been working on lately (with python)
https://github.com/codebyaryan/flask-graphql-boilerplate

Naming of mutations and rule 17

Thank you very much for the guide. I do love it a lot, although I have some questions with Rule number 17. Is rule number 17 purely for the autocomplete and ease for developers? If we communicate in the business language for the rest of the API shouldn't this be also the case (And we should solve the editor/grouping problem instead)

It does seem that the api leaks developer preference in the business domain, It does result in a structured api though where things are grouped functionality but it is conflicting with some other API's and result in a little weird naming at times. Any thoughts?

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.