-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Update best practices docs #1838
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
Merged
Merged
Changes from 4 commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
2bc3f0f
Move content from best practices page, replace with links.
mandiwise 14c7710
Conditionally apply word breaks to card text.
mandiwise fbf796f
Update description text.
mandiwise d625775
Add caching card to page.
mandiwise beca8cf
Apply suggestions from code review
benjie File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,61 +1,57 @@ | ||
--- | ||
sidebarTitle: Introduction | ||
--- | ||
import { Cards } from '../../components/cards' | ||
|
||
# GraphQL Best Practices | ||
|
||
The GraphQL specification is intentionally silent on a handful of important issues facing APIs such as dealing with the network, authorization, and pagination. This doesn't mean that there aren't solutions for these issues when using GraphQL, just that they're outside the description about what GraphQL _is_ and instead just common practice. | ||
|
||
The articles in this section should not be taken as gospel, and in some cases may rightfully be ignored in favor of some other approach. Some articles introduce some of the philosophy developed within Facebook around designing and deploying GraphQL services, while others are more tactical suggestions for solving common problems like serving over HTTP and performing authorization. | ||
|
||
Following are brief descriptions of some of the more common best practices and opinionated stances held by GraphQL services, however each article in this section will go into more depth on these and other topics. | ||
|
||
## HTTP | ||
|
||
GraphQL is typically served over HTTP via a single endpoint which expresses the full set of capabilities of the service. This is in contrast to REST APIs which expose a suite of URLs each of which expose a single resource. While GraphQL could be used alongside a suite of resource URLs, this can make it harder to use with tools like [GraphiQL](https://github.com/graphql/graphiql). | ||
|
||
Read more about this in [Serving over HTTP](/learn/serving-over-http/). | ||
|
||
## JSON (with GZIP) | ||
|
||
GraphQL services typically respond using JSON, however the GraphQL spec [does not require it](http://spec.graphql.org/draft/#sec-Serialization-Format). JSON may seem like an odd choice for an API layer promising better network performance, however because it is mostly text, it compresses exceptionally well with GZIP. | ||
|
||
It's encouraged that any production GraphQL services enable GZIP and encourage their clients to send the header: | ||
|
||
``` | ||
Accept-Encoding: gzip | ||
``` | ||
|
||
JSON is also very familiar to client and API developers, and is easy to read and debug. In fact, the GraphQL syntax is partly inspired by the JSON syntax. | ||
|
||
## Versioning | ||
|
||
While there's nothing that prevents a GraphQL service from being versioned just like any other REST API, GraphQL takes a strong opinion on avoiding versioning by providing the tools for the continuous evolution of a GraphQL schema. | ||
|
||
Why do most APIs version? When there's limited control over the data that's returned from an API endpoint, _any change_ can be considered a breaking change, and breaking changes require a new version. If adding new features to an API requires a new version, then a tradeoff emerges between releasing often and having many incremental versions versus the understandability and maintainability of the API. | ||
|
||
In contrast, GraphQL only returns the data that's explicitly requested, so new capabilities can be added via new types and new fields on those types without creating a breaking change. This has led to a common practice of always avoiding breaking changes and serving a versionless API. | ||
|
||
## Nullability | ||
|
||
Most type systems which recognise "null" provide both the common type and the _nullable_ version of that type, whereby default types do not include "null" unless explicitly declared. However, in a GraphQL type system, every field is _nullable_ by default. This is because there are many things that can go awry in a networked service backed by databases and other services. A database could go down, an asynchronous action could fail, an exception could be thrown. Beyond simply system failures, authorization can often be granular, where individual fields within a request can have different authorization rules. | ||
|
||
By defaulting every field to _nullable_, any of these reasons may result in just that field returned "null" rather than having a complete failure for the request. Instead, GraphQL provides [non-null](/learn/schema/#lists-and-non-null) variants of types which make a guarantee to clients that if requested, the field will never return "null". Instead, if an error occurs, the previous parent field will be "null" instead. | ||
|
||
When designing a GraphQL schema, it's important to keep in mind all the problems that could go wrong and if "null" is an appropriate value for a failed field. Typically it is, but occasionally, it's not. In those cases, use non-null types to make that guarantee. | ||
|
||
## Pagination | ||
|
||
The GraphQL type system allows for some fields to return [lists of values](/learn/schema/#lists-and-non-null), but leaves the pagination of longer lists of values up to the API designer. There are a wide range of possible API designs for pagination, each of which has pros and cons. | ||
|
||
Typically, fields that could return long lists accept arguments "first" and "after" to allow for specifying a specific region of a list, where "after" is a unique identifier of each of the values in the list. | ||
|
||
Ultimately designing APIs with feature-rich pagination led to a best practice pattern called "Connections". Some client tools for GraphQL, such as [Relay](https://facebook.github.io/relay/), know about the Connections pattern and can automatically provide support for client-side pagination when a GraphQL API employs this pattern. | ||
|
||
Read more about this in the article on [Pagination](/learn/pagination/). | ||
|
||
## Server-side Batching & Caching | ||
|
||
GraphQL is designed in a way that allows you to write clean code on the server, where every field on every type has a focused single-purpose function for resolving that value. However without additional consideration, a naive GraphQL service could be very "chatty" or repeatedly load data from your databases. | ||
|
||
This is commonly solved by a batching technique, where multiple requests for data from a backend are collected over a short period of time and then dispatched in a single request to an underlying database or microservice by using a tool like Facebook's [DataLoader](https://github.com/facebook/dataloader). | ||
<Cards | ||
items={[ | ||
{ | ||
title: "Thinking in Graphs", | ||
link: "/learn/thinking-in-graphs/", | ||
description: "Model your business domain as a graph", | ||
}, | ||
{ | ||
title: "Serving over HTTP", | ||
link: "/learn/serving-over-http/", | ||
description: "Handle GraphQL requests on HTTP servers", | ||
}, | ||
{ | ||
title: "Authorization", | ||
link: "/learn/authorization/", | ||
description: "Delegate authorization logic to the business logic layer", | ||
}, | ||
{ | ||
title: "Pagination", | ||
link: "/learn/pagination/", | ||
description: "Allow clients to traverse lists of objects with a consistent field pagination model", | ||
}, | ||
{ | ||
title: "Schema Design", | ||
link: "/learn/schema-design/", | ||
description: "Design and evolve a type system over time without versions", | ||
}, | ||
{ | ||
title: "Global Object Identification", | ||
link: "/learn/global-object-identification/", | ||
description: "Consistent object access enables simple caching and object lookups", | ||
}, | ||
{ | ||
title: "Caching", | ||
link: "/learn/caching/", | ||
description: "Provide Object Identifiers so clients can build rich caches", | ||
}, | ||
{ | ||
title: "Performance", | ||
link: "/learn/performance/", | ||
description: "Optimize the execution and delivery of GraphQL responses", | ||
}, | ||
{ | ||
title: "Security", | ||
link: "/learn/security/", | ||
description: "Protect GraphQL APIs from malicious operations", | ||
}, | ||
]} | ||
/> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
# Schema Design | ||
|
||
<p className="learn-subtitle">Design and evolve a type system over time without versions</p> | ||
|
||
## Versioning | ||
|
||
While there's nothing that prevents a GraphQL service from being versioned just like any other REST API, GraphQL takes a strong opinion on avoiding versioning by providing the tools for the continuous evolution of a GraphQL schema. | ||
|
||
Why do most APIs version? When there's limited control over the data that's returned from an API endpoint, _any change_ can be considered a breaking change, and breaking changes require a new version. If adding new features to an API requires a new version, then a tradeoff emerges between releasing often and having many incremental versions versus the understandability and maintainability of the API. | ||
|
||
In contrast, GraphQL only returns the data that's explicitly requested, so new capabilities can be added via new types and new fields on those types without creating a breaking change. This has led to a common practice of always avoiding breaking changes and serving a versionless API. | ||
benjie marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
## Nullability | ||
|
||
Most type systems which recognise "null" provide both the common type and the _nullable_ version of that type, whereby default types do not include "null" unless explicitly declared. However, in a GraphQL type system, every field is _nullable_ by default. This is because there are many things that can go awry in a networked service backed by databases and other services. A database could go down, an asynchronous action could fail, an exception could be thrown. Beyond simply system failures, authorization can often be granular, where individual fields within a request can have different authorization rules. | ||
|
||
By defaulting every field to _nullable_, any of these reasons may result in just that field returned "null" rather than having a complete failure for the request. Instead, GraphQL provides [non-null](/learn/schema/#lists-and-non-null) variants of types which make a guarantee to clients that if requested, the field will never return "null". Instead, if an error occurs, the previous parent field will be "null" instead. | ||
|
||
When designing a GraphQL schema, it's important to keep in mind all the problems that could go wrong and if "null" is an appropriate value for a failed field. Typically it is, but occasionally, it's not. In those cases, use non-null types to make that guarantee. |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.