-
Notifications
You must be signed in to change notification settings - Fork 2k
docs: editorial for recent documentation updates #4395
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
base: 16.x.x
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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; | ||
}, | ||
Comment on lines
+195
to
211
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We commented that it was essential to implement |
||
}); | ||
``` | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We renamed this spec to the GraphQL Cursor Connections Specification by agreement at the GraphQL WG a couple years back. Relay hosts it, but it's not really a "relay" spec any more - it's very widely used. |
||
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)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is me being a bit pedantic on the security front, but it's possible that a user could pass something that when you |
||
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. | ||
|
||
<Callout type='info'> | ||
|
||
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. | ||
|
||
</Callout> | ||
|
||
## 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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wanted to soften the wording here, don't want people saying "See! Even the GraphQL documentation says that it has major performance issues!" without bothering to read on. Wanted to make this clear this is a mistake you might make early on, but once you've built a few GraphQL schemas using batching will be your bread and butter. |
||
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 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
Comment on lines
+43
to
+44
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This parenthesis might not be the best way of saying this, but |
||
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: { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
buildSchema
approach didn't allow the customDateTime
scalar to be implemented, so I replaced it with the class-based approach.