From 2bc3f0fca4208d53253e6edea8dad255b98969c2 Mon Sep 17 00:00:00 2001 From: Mandi Wise Date: Wed, 6 Nov 2024 13:52:20 -0700 Subject: [PATCH 1/5] Move content from best practices page, replace with links. --- src/pages/learn/_meta.ts | 1 + src/pages/learn/best-practices.mdx | 99 ++++++++++++++---------------- src/pages/learn/schema-design.mdx | 19 ++++++ 3 files changed, 65 insertions(+), 54 deletions(-) create mode 100644 src/pages/learn/schema-design.mdx diff --git a/src/pages/learn/_meta.ts b/src/pages/learn/_meta.ts index 4affaab60a..3433da3431 100644 --- a/src/pages/learn/_meta.ts +++ b/src/pages/learn/_meta.ts @@ -18,6 +18,7 @@ export default { "serving-over-http": "", authorization: "", pagination: "", + "schema-design": "Schema Design", "global-object-identification": "", caching: "", } diff --git a/src/pages/learn/best-practices.mdx b/src/pages/learn/best-practices.mdx index e6cc939cbd..94d3eee152 100644 --- a/src/pages/learn/best-practices.mdx +++ b/src/pages/learn/best-practices.mdx @@ -1,6 +1,4 @@ ---- -sidebarTitle: Introduction ---- +import { Cards } from '../../components/cards' # GraphQL Best Practices @@ -8,54 +6,47 @@ The GraphQL specification is intentionally silent on a handful of important issu 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). + diff --git a/src/pages/learn/schema-design.mdx b/src/pages/learn/schema-design.mdx new file mode 100644 index 0000000000..e70325162f --- /dev/null +++ b/src/pages/learn/schema-design.mdx @@ -0,0 +1,19 @@ +# Schema Design + +

Design and evolve a type system over time without versions

+ +## 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. \ No newline at end of file From 14c771092a13486bd32c9d490fdeb2734efc3375 Mon Sep 17 00:00:00 2001 From: Mandi Wise Date: Wed, 6 Nov 2024 13:52:39 -0700 Subject: [PATCH 2/5] Conditionally apply word breaks to card text. --- src/components/cards.tsx | 53 ++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 29 deletions(-) diff --git a/src/components/cards.tsx b/src/components/cards.tsx index 10d78e3731..8c3868044e 100644 --- a/src/components/cards.tsx +++ b/src/components/cards.tsx @@ -15,36 +15,31 @@ export function Cards({ }) { return (
- {items.map( - ({ - icon: Icon, - title, - link, - description = link.replace(/^https?:\/\//, ""), - }) => { - const isExternal = link.startsWith("https://") - return ( - { + const isExternal = link.startsWith("https://") + return ( + + {/* @ts-expect-error */} + {typeof Icon === "function" ? : Icon} + {title} + - {/* @ts-expect-error */} - {typeof Icon === "function" ? : Icon} - {title} - - {description} - - - ) - }, - )} + {description ? description : link.replace(/^https?:\/\//, "")} + + + ) + })}
) } From fbf796f393181ccc75dd9eb6036ee469d8508b76 Mon Sep 17 00:00:00 2001 From: Mandi Wise Date: Fri, 8 Nov 2024 10:18:14 -0700 Subject: [PATCH 3/5] Update description text. --- src/pages/learn/best-practices.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/learn/best-practices.mdx b/src/pages/learn/best-practices.mdx index 94d3eee152..abffd31db9 100644 --- a/src/pages/learn/best-practices.mdx +++ b/src/pages/learn/best-practices.mdx @@ -46,7 +46,7 @@ The articles in this section should not be taken as gospel, and in some cases ma { title: "Security", link: "/learn/security/", - description: "Secure a GraphQL API from malicious queries", + description: "Protect GraphQL APIs from malicious operations", }, ]} /> From d6257751bdc1185da2efa1c03639da33d519ee4b Mon Sep 17 00:00:00 2001 From: Mandi Wise Date: Thu, 28 Nov 2024 10:18:40 -0700 Subject: [PATCH 4/5] Add caching card to page. --- src/pages/learn/best-practices.mdx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/pages/learn/best-practices.mdx b/src/pages/learn/best-practices.mdx index abffd31db9..5c9b5527d4 100644 --- a/src/pages/learn/best-practices.mdx +++ b/src/pages/learn/best-practices.mdx @@ -37,6 +37,11 @@ The articles in this section should not be taken as gospel, and in some cases ma 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", From beca8cfdb01df1a6e986960ddcf509e84a77ed06 Mon Sep 17 00:00:00 2001 From: Benjie Date: Thu, 28 Nov 2024 17:39:14 +0000 Subject: [PATCH 5/5] Apply suggestions from code review --- src/pages/learn/schema-design.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/learn/schema-design.mdx b/src/pages/learn/schema-design.mdx index e70325162f..43d2de1048 100644 --- a/src/pages/learn/schema-design.mdx +++ b/src/pages/learn/schema-design.mdx @@ -4,11 +4,11 @@ ## 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. +While there's nothing that prevents a GraphQL service from being versioned just like any other 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. +In contrast, GraphQL only returns the data that's explicitly requested, so new capabilities can be added via new types or new fields on existing types without creating a breaking change. This has led to a common practice of always avoiding breaking changes and serving a versionless API. ## Nullability