diff --git a/website/pages/docs/advanced-custom-scalars.mdx b/website/pages/docs/advanced-custom-scalars.mdx
index a7e7119a56..91a068409a 100644
--- a/website/pages/docs/advanced-custom-scalars.mdx
+++ b/website/pages/docs/advanced-custom-scalars.mdx
@@ -100,25 +100,36 @@ describe('DateTime scalar', () => {
Integrate the scalar into a schema and run real GraphQL queries to validate end-to-end behavior.
```js
-const { graphql, buildSchema } = require('graphql');
+const { graphql, GraphQLSchema, GraphQLObjectType } = require('graphql');
+const { DateTimeResolver as DateTime } = require('graphql-scalars');
+
+const Query = new GraphQLObjectType({
+ name: 'Query',
+ fields: {
+ now: {
+ type: DateTime,
+ resolve() {
+ return new Date();
+ },
+ },
+ },
+});
-const schema = buildSchema(`
+/*
scalar DateTime
type Query {
now: DateTime
}
-`);
-
-const rootValue = {
- now: () => new Date('2024-01-01T00:00:00Z'),
-};
+*/
+const schema = new GraphQLSchema({
+ query: Query,
+});
async function testQuery() {
const response = await graphql({
schema,
source: '{ now }',
- rootValue,
});
console.log(response);
}
@@ -181,13 +192,22 @@ If you need domain-specific behavior, you can wrap an existing scalar with custo
```js
const { EmailAddressResolver } = require('graphql-scalars');
-const StrictEmail = new GraphQLScalarType({
+const StrictEmailAddress = new GraphQLScalarType({
...EmailAddressResolver,
+ name: 'StrictEmailAddress',
parseValue(value) {
- if (!value.endsWith('@example.com')) {
+ const email = EmailAddressResolver.parseValue(value);
+ if (!email.endsWith('@example.com')) {
+ throw new TypeError('Only example.com emails are allowed.');
+ }
+ return email;
+ },
+ parseLiteral(literal, variables) {
+ const email = EmailAddressResolver.parseLiteral(literal, variables);
+ if (!email.endsWith('@example.com')) {
throw new TypeError('Only example.com emails are allowed.');
}
- return EmailAddressResolver.parseValue(value);
+ return email;
},
});
```
diff --git a/website/pages/docs/cursor-based-pagination.mdx b/website/pages/docs/cursor-based-pagination.mdx
index 5b548be264..a5f628315d 100644
--- a/website/pages/docs/cursor-based-pagination.mdx
+++ b/website/pages/docs/cursor-based-pagination.mdx
@@ -2,6 +2,8 @@
title: Implementing Cursor-based Pagination
---
+import { Callout } from "nextra/components";
+
When a GraphQL API returns a list of data, pagination helps avoid
fetching too much data at once. Cursor-based pagination fetches items
relative to a specific point in the list, rather than using numeric offsets.
@@ -18,15 +20,15 @@ that works well with clients.
Cursor-based pagination typically uses a structured format that separates
pagination metadata from the actual data. The most widely adopted pattern follows the
-[Relay Cursor Connections Specification](https://relay.dev/graphql/connections.htm). While
+[GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm). While
this format originated in Relay, many GraphQL APIs use it independently because of its
clarity and flexibility.
This pattern wraps your list of items in a connection type, which includes the following fields:
-- `edges`: A list of edge objects, each representing an item in the list.
-- `node`: The actual object you want to retrieve, such as user, post, or comment.
-- `cursor`: An opaque string that identifies the position of the item in the list.
+- `edges`: A list of edge objects, representing for each item in the list:
+ - `node`: The actual object you want to retrieve, such as user, post, or comment.
+ - `cursor`: An opaque string that identifies the position of the item in the list.
- `pageInfo`: Metadata about the list, such as whether more items are available.
The following query and response show how this structure works:
@@ -192,7 +194,7 @@ const usersField = {
let start = 0;
if (args.after) {
const index = decodeCursor(args.after);
- if (index != null) {
+ if (Number.isFinite(index)) {
start = index + 1;
}
}
@@ -243,7 +245,7 @@ async function resolveUsers(_, args) {
if (args.after) {
const index = decodeCursor(args.after);
- if (index != null) {
+ if (Number.isFinite(index)) {
offset = index + 1;
}
}
@@ -279,6 +281,25 @@ an `OFFSET`. To paginate backward, you can reverse the sort order and slice the
results accordingly, or use keyset pagination for improved performance on large
datasets.
+
+
+The above is just an example to aid understanding; in a production application,
+for most databases it is better to use `WHERE` clauses to implement cursor
+pagination rather than using `OFFSET`. Using `WHERE` can leverage indices
+(indexes) to jump directly to the relevant records, whereas `OFFSET` typically
+must scan over and discard that number of records. When paginating very large
+datasets, `OFFSET` can become more expensive as the value grows, whereas using
+`WHERE` tends to have fixed cost. Using `WHERE` can also typically handle the
+addition or removal of data more gracefully.
+
+For example, if you were ordering a collection of users by username, you could
+use the username itself as the `cursor`, thus GraphQL's `allUsers(first: 10,
+after: $cursor)` could become SQL's `WHERE username > $1 LIMIT 10`. Even if
+that user was deleted, you could still continue to paginate from that position
+onwards.
+
+
+
## Handling edge cases
When implementing pagination, consider how your resolver should handle the following scenarios:
@@ -297,7 +318,7 @@ errors.
To learn more about cursor-based pagination patterns and best practices, see:
-- [Relay Cursor Connections Specification](https://relay.dev/graphql/connections.htm)
+- [GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm)
- [Pagination](https://graphql.org/learn/pagination/) guide on graphql.org
- [`graphql-relay-js`](https://github.com/graphql/graphql-relay-js): Utility library for
building Relay-compatible GraphQL servers using GraphQL.js
diff --git a/website/pages/docs/custom-scalars.mdx b/website/pages/docs/custom-scalars.mdx
index b5d1959867..d724360e9b 100644
--- a/website/pages/docs/custom-scalars.mdx
+++ b/website/pages/docs/custom-scalars.mdx
@@ -80,6 +80,10 @@ providing a name, description, and three functions:
- `serialize`: How the server sends internal values to clients.
- `parseValue`: How the server parses incoming variable values.
- `parseLiteral`: How the server parses inline values in queries.
+- `specifiedByURL` (optional): A URL specifying the behavior of your scalar;
+ this can be used by clients and tooling to recognize and handle common scalars
+ such as [date-time](https://scalars.graphql.org/andimarek/date-time.html)
+ independent of their name.
The following example is a custom `DateTime` scalar that handles ISO-8601 encoded
date strings:
@@ -90,6 +94,7 @@ const { GraphQLScalarType, Kind } = require('graphql');
const DateTime = new GraphQLScalarType({
name: 'DateTime',
description: 'An ISO-8601 encoded UTC date string.',
+ specifiedByURL: 'https://scalars.graphql.org/andimarek/date-time.html',
serialize(value) {
if (!(value instanceof Date)) {
diff --git a/website/pages/docs/graphql-errors.mdx b/website/pages/docs/graphql-errors.mdx
index 533f63bbe9..13e286f025 100644
--- a/website/pages/docs/graphql-errors.mdx
+++ b/website/pages/docs/graphql-errors.mdx
@@ -1,6 +1,7 @@
---
title: Understanding GraphQL.js Errors
---
+import { Callout, GitHubNoteIcon } from "nextra/components";
# Understanding GraphQL.js Errors
@@ -37,13 +38,22 @@ For example:
Each error object can include the following fields:
- `message`: A human-readable description of the error.
-- `locations` (optional): Where the error occurred in the operation.
+- `locations` (optional): Where the error occurred in the operation document.
- `path` (optional): The path to the field that caused the error.
- `extensions` (optional): Additional error metadata, often used for error codes, HTTP status
codes or debugging information.
-The GraphQL specification only requires the `message` field. All others are optional, but
-recommended to help clients understand and react to errors.
+
+
+The GraphQL specification separates errors into two types: _request_ errors, and
+_execution_ errors. Request errors indicate something went wrong that prevented
+the GraphQL operation from executing, for example the document is invalid, and
+only requires the `message` field. Execution errors indicate something went
+wrong during execution, typically due to the result of calling a resolver, and
+requires both the `message` and `path` fields to be present. All others fields
+are optional, but recommended to help clients understand and react to errors.
+
+
## Creating and handling errors with `GraphQLError`
@@ -81,12 +91,16 @@ Each option helps tie the error to specific parts of the GraphQL execution:
When a resolver throws an error:
-- If the thrown value is already a `GraphQLError`, GraphQL.js uses it as-is.
-- If it is another type of error (such as a built-in `Error`), GraphQL.js wraps it into a
-`GraphQLError`.
+- If the thrown value is a `GraphQLError` and contains the required information
+(`path`), GraphQL.js uses it as-is.
+- Otherwise, GraphQL.js wraps it into a `GraphQLError`.
This ensures that all errors returned to the client follow a consistent structure.
+You may throw any type of error that makes sense in your application; throwing
+`Error` is fine, you do not need to throw `GraphQLError`. However, ensure that
+your errors do not reveal security sensitive information.
+
## How errors propagate during execution
Errors in GraphQL don't necessarily abort the entire operation. How an error affects the response
@@ -96,6 +110,7 @@ depends on the nullability of the field where the error occurs.
the error and sets the field's value to `null` in the `data` payload.
- **Non-nullable fields**: If a resolver for a non-nullable field throws an error, GraphQL.js
records the error and then sets the nearest parent nullable field to `null`.
+If no such nullable field exists, then the operation root will be set `null` (`"data": null`).
For example, consider the following schema:
diff --git a/website/pages/docs/n1-dataloader.mdx b/website/pages/docs/n1-dataloader.mdx
index 7a5680a0f5..57e8f351aa 100644
--- a/website/pages/docs/n1-dataloader.mdx
+++ b/website/pages/docs/n1-dataloader.mdx
@@ -2,7 +2,7 @@
title: Solving the N+1 Problem with `DataLoader`
---
-When building a server with GraphQL.js, it's common to encounter
+When building your first server with GraphQL.js, it's common to encounter
performance issues related to the N+1 problem: a pattern that
results in many unnecessary database or service calls,
especially in nested query structures.
@@ -69,7 +69,8 @@ when setting up a GraphQL HTTP server such as [express-graphql](https://github.c
Suppose each `Post` has an `authorId`, and you have a `getUsersByIds(ids)`
function that can fetch multiple users in a single call:
-```js
+{/* prettier-ignore */}
+```js {14-17,37}
import {
graphql,
GraphQLObjectType,
@@ -81,6 +82,15 @@ import {
import DataLoader from 'dataloader';
import { getPosts, getUsersByIds } from './db.js';
+function createContext() {
+ return {
+ userLoader: new DataLoader(async (userIds) => {
+ const users = await getUsersByIds(userIds);
+ return userIds.map(id => users.find(user => user.id === id));
+ }),
+ };
+}
+
const UserType = new GraphQLObjectType({
name: 'User',
fields: () => ({
@@ -114,15 +124,6 @@ const QueryType = new GraphQLObjectType({
});
const schema = new GraphQLSchema({ query: QueryType });
-
-function createContext() {
- return {
- userLoader: new DataLoader(async (userIds) => {
- const users = await getUsersByIds(userIds);
- return userIds.map(id => users.find(user => user.id === id));
- }),
- };
-}
```
With this setup, all `.load(authorId)` calls are automatically collected and batched
diff --git a/website/pages/docs/resolver-anatomy.mdx b/website/pages/docs/resolver-anatomy.mdx
index 8b7195790a..6799d021a0 100644
--- a/website/pages/docs/resolver-anatomy.mdx
+++ b/website/pages/docs/resolver-anatomy.mdx
@@ -33,15 +33,16 @@ When GraphQL.js executes a resolver, it calls the resolver function
with four arguments:
```js
-function resolver(source, args, context, info) { ... }
+function resolve(source, args, context, info) { ... }
```
Each argument provides information that can help the resolver return the
correct value:
- `source`: The result from the parent field's resolver. In nested fields,
-`source` contains the value returned by the parent object. For root fields,
-it is often `undefined`.
+`source` contains the value returned by the parent object (after resolving any
+lists). For root fields, it is the `rootValue` passed to GraphQL, which is often
+left `undefined`.
- `args`: An object containing the arguments passed to the field in the
query. For example, if a field is defined to accept an `id` argument, you can
access it as `args.id`.
@@ -85,7 +86,7 @@ A custom resolver is a function you define to control exactly how a field's
value is fetched or computed. You can add a resolver by specifying a `resolve`
function when defining a field in your schema:
-```js
+```js {6-8}
const UserType = new GraphQLObjectType({
name: 'User',
fields: {