diff --git a/.prettierignore b/.prettierignore
index f4358f6920..ccace41a42 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -1,2 +1,4 @@
# additive to .gitignore
-/website/**/*.mdx
+/website/next-env.d.ts
+/website/pages/api-v16/**
+/website/pages/api-v17/**
diff --git a/eslint.config.mjs b/eslint.config.mjs
index 13f26417db..a6f08d52c6 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -1296,4 +1296,8 @@ export default defineConfig(
'import/unambiguous': 'off',
},
},
+ {
+ files: ['**/*.mdx'],
+ processor: 'internal-rules/mdx-tabs-spacing',
+ },
);
diff --git a/resources/eslint-internal-rules/index.js b/resources/eslint-internal-rules/index.js
index a3e2a5a911..7be7981af9 100644
--- a/resources/eslint-internal-rules/index.js
+++ b/resources/eslint-internal-rules/index.js
@@ -1,3 +1,4 @@
+import { mdxTabsSpacingProcessor } from './mdx-tabs-spacing.js';
import { noDirImportRule } from './no-dir-import.js';
import { onlyAsciiRule } from './only-ascii.js';
import { requireGraphqlPublicApiDocsRule } from './require-graphql-public-api-docs.js';
@@ -5,6 +6,9 @@ import { requirePublicApiExportsRule } from './require-public-api-exports.js';
import { requireToStringTagRule } from './require-to-string-tag.js';
const internalRulesPlugin = {
+ processors: {
+ 'mdx-tabs-spacing': mdxTabsSpacingProcessor,
+ },
rules: {
...onlyAsciiRule,
...noDirImportRule,
diff --git a/resources/eslint-internal-rules/mdx-tabs-spacing.js b/resources/eslint-internal-rules/mdx-tabs-spacing.js
new file mode 100644
index 0000000000..a39b8a2064
--- /dev/null
+++ b/resources/eslint-internal-rules/mdx-tabs-spacing.js
@@ -0,0 +1,208 @@
+/*
+ * Nextra blocks mix JSX tags with Markdown children. That boundary is
+ * fragile: MDX may accept a Tabs block where the tags, fenced code blocks, and
+ * following prose are adjacent, but Prettier will not necessarily recover the
+ * safer block structure for us.
+ *
+ * For example, this input has the unsafe adjacency we want to reject. The
+ * fenced JavaScript is intentionally unformatted so the Prettier behavior is
+ * visible:
+ *
+ *
+ *
+ * ```js
+ * const value={a:1};
+ * ```
+ *
+ * Next paragraph.
+ *
+ * Running `prettier --parser mdx` on that input produces this:
+ *
+ *
+ *
+ * ```js
+ * const value={a:1};
+ * ```
+ *
+ * Next paragraph.
+ *
+ * The formatting problem is that Prettier does not treat the fenced block as a
+ * normal Markdown code fence in a Tabs panel: the JavaScript stays unformatted,
+ * the fence remains glued to , the closing tags remain collapsed, and
+ * the next paragraph remains glued to .
+ *
+ * With clear JSX/Markdown boundaries, Prettier formats the fenced JavaScript and
+ * keeps the Tabs block readable:
+ *
+ *
+ *
+ *
+ * ```js
+ * const value = { a: 1 };
+ * ```
+ *
+ *
+ *
+ *
+ * Next paragraph.
+ *
+ * This is intentionally not a general Markdown style rule. It only protects the
+ * Nextra Tabs shapes that can survive Prettier in a bad form.
+ */
+
+const mdxSourceByFilename = new Map();
+const mdxTabsSpacingProcessor = {
+ meta: {
+ name: 'internal-rules/mdx-tabs-spacing',
+ },
+ preprocess(text, filename) {
+ mdxSourceByFilename.set(filename, text);
+ return [''];
+ },
+ postprocess(messageLists, filename) {
+ const source = mdxSourceByFilename.get(filename);
+ mdxSourceByFilename.delete(filename);
+
+ return [
+ ...messageLists.flat(),
+ ...(source == null ? [] : checkMdxTabsSpacing(source)),
+ ];
+ },
+ supportsAutofix: false,
+};
+
+function checkMdxTabsSpacing(source) {
+ const lines = source.split(/\r?\n/u);
+ const messages = [];
+
+ for (let index = 0; index < lines.length; index++) {
+ const line = lines[index];
+ const trimmed = line.trim();
+
+ // Prettier can collapse or preserve this one-line close in a way that makes
+ // later conflict resolution hard to read and easy to break again. Keeping the
+ // component close and container close on separate lines gives Markdown a
+ // clear block boundary after the final tab panel.
+ if (/<\/Tabs\.Tab>\s*<\/Tabs>/.test(line)) {
+ report(
+ messages,
+ lines,
+ index,
+ line.indexOf(''),
+ 'Close and on separate lines.',
+ );
+ }
+
+ // A block should start as its own Markdown block. If it is glued to
+ // the previous paragraph/list item, MDX still accepts the file, but Prettier
+ // can treat the JSX and surrounding Markdown as one construct and preserve
+ // surprising layout.
+ if (isTabsOpen(trimmed) && index > 0 && !isBlank(lines[index - 1])) {
+ report(
+ messages,
+ lines,
+ index,
+ line.indexOf(' blocks.',
+ );
+ }
+
+ // The closing needs the same protection in the other direction. The
+ // bug this guard was added for was exactly a closing Tabs block followed by
+ // prose with no blank line; that made the following paragraph part of the
+ // same MDX flow and led Prettier to keep an unsafe shape.
+ if (
+ isTabsClose(trimmed) &&
+ index < lines.length - 1 &&
+ !isBlank(lines[index + 1])
+ ) {
+ report(
+ messages,
+ lines,
+ index,
+ line.indexOf(''),
+ 'Add a blank line after blocks.',
+ );
+ }
+
+ // Fenced code inside a JSX child is only unambiguously Markdown when there is
+ // a blank line after . Without it, MDX/Prettier can handle the
+ // fence as adjacent JSX text, and later formatting may not restore the tab
+ // panel structure we expect.
+ if (
+ isTabsTabOpen(trimmed) &&
+ index < lines.length - 1 &&
+ isCodeFence(lines[index + 1])
+ ) {
+ report(
+ messages,
+ lines,
+ index,
+ line.indexOf(''),
+ 'Add a blank line between and fenced code blocks.',
+ );
+ }
+
+ // Likewise, the end of a fenced code block should be separated from the
+ // closing tab panel tag. This keeps the fence close from being visually and
+ // syntactically glued to JSX during conflict resolution and Prettier passes.
+ if (
+ isCodeFence(line) &&
+ index < lines.length - 1 &&
+ isTabsTabClose(lines[index + 1].trim())
+ ) {
+ report(
+ messages,
+ lines,
+ index,
+ firstNonWhitespaceIndex(line),
+ 'Add a blank line between fenced code blocks and .',
+ );
+ }
+ }
+
+ return messages;
+}
+
+function report(messages, lines, lineIndex, columnIndex, message) {
+ messages.push({
+ ruleId: 'internal-rules/mdx-tabs-spacing',
+ severity: 2,
+ message,
+ line: lineIndex + 1,
+ column: columnIndex + 1,
+ endLine: lineIndex + 1,
+ endColumn: lines[lineIndex].length + 1,
+ });
+}
+
+function isTabsOpen(trimmedLine) {
+ return /^)/u.test(trimmedLine);
+}
+
+function isTabsClose(trimmedLine) {
+ return trimmedLine === '';
+}
+
+function isTabsTabOpen(trimmedLine) {
+ return trimmedLine === '';
+}
+
+function isTabsTabClose(trimmedLine) {
+ return trimmedLine === '';
+}
+
+function isCodeFence(line) {
+ return /^`{3,}/u.test(line.trim());
+}
+
+function isBlank(line) {
+ return line.trim() === '';
+}
+
+function firstNonWhitespaceIndex(line) {
+ const match = /\S/u.exec(line);
+ return match == null ? 0 : match.index;
+}
+
+export { mdxTabsSpacingProcessor };
diff --git a/website/pages/docs/advanced-custom-scalars.mdx b/website/pages/docs/advanced-custom-scalars.mdx
index 930284b283..c2eff04869 100644
--- a/website/pages/docs/advanced-custom-scalars.mdx
+++ b/website/pages/docs/advanced-custom-scalars.mdx
@@ -6,23 +6,23 @@ import { Callout } from 'nextra/components';
# Custom Scalars: Best Practices and Testing
-Custom scalars must behave predictably and clearly. To maintain a consistent, reliable
+Custom scalars must behave predictably and clearly. To maintain a consistent, reliable
schema, follow these best practices.
GraphQL.js v17 renames the scalar hooks to the coercion terms used by the
- specification. The v16 names still work in v17, but are deprecated for
- removal in v18.
+ specification. The v16 names still work in v17, but are deprecated for removal
+ in v18.
### Map v16 names to v17 names
-| v16 name | v17 name | Purpose |
-| --- | --- | --- |
-| `serialize` | `coerceOutputValue` | Convert resolver values into response values. |
-| `parseValue` | `coerceInputValue` | Convert variable values into internal values. |
-| `parseLiteral` | `coerceInputLiteral` | Convert constant GraphQL literals into internal values. |
-| `astFromValue()` | `valueToLiteral()` | Convert external input values into GraphQL literals. |
+| v16 name | v17 name | Purpose |
+| ---------------- | -------------------- | ------------------------------------------------------- |
+| `serialize` | `coerceOutputValue` | Convert resolver values into response values. |
+| `parseValue` | `coerceInputValue` | Convert variable values into internal values. |
+| `parseLiteral` | `coerceInputLiteral` | Convert constant GraphQL literals into internal values. |
+| `astFromValue()` | `valueToLiteral()` | Convert external input values into GraphQL literals. |
If your scalar supports both v16 and v17, keep the v16 method names for now.
When your minimum version is v17, implement the v17 names and add
@@ -37,7 +37,7 @@ tooling calls `coerceInputLiteral()` directly outside execution, it must call
### Document expected formats and validation
-Provide a clear description of the scalar's accepted input and output formats. For example, a
+Provide a clear description of the scalar's accepted input and output formats. For example, a
`DateTime` scalar should explain that it expects [ISO-8601](https://www.iso.org/iso-8601-date-and-time-format.html) strings ending with `Z`.
Clear descriptions help clients understand valid input and reduce mistakes.
@@ -77,7 +77,7 @@ Clear error messages speed up debugging and make mistakes easier to fix.
### Serialize consistently
Always serialize internal values into a predictable format.
-For example, a `DateTime` scalar should always produce an ISO string, even if its
+For example, a `DateTime` scalar should always produce an ISO string, even if its
internal value is a `Date` object.
```js
@@ -167,7 +167,7 @@ async function testQuery() {
testQuery();
```
-Schema-level tests verify that the scalar behaves correctly during execution, not just
+Schema-level tests verify that the scalar behaves correctly during execution, not just
in isolation.
## Common use cases for custom scalars
@@ -181,7 +181,9 @@ Custom scalars solve real-world needs by handling types that built-in scalars do
function validateEmail(value) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(value)) {
- throw new TypeError(`Email cannot represent invalid email address: ${value}`);
+ throw new TypeError(
+ `Email cannot represent invalid email address: ${value}`,
+ );
}
return value;
}
@@ -200,21 +202,21 @@ function validateURL(value) {
}
```
-- `JSON`: Represents arbitrary JSON structures, but use carefully because it bypasses
-GraphQL's strict type checking.
+- `JSON`: Represents arbitrary JSON structures, but use carefully because it bypasses
+ GraphQL's strict type checking.
## When to use existing libraries
-Writing scalars is deceptively tricky. Validation edge cases can lead to subtle bugs if
+Writing scalars is deceptively tricky. Validation edge cases can lead to subtle bugs if
not handled carefully.
-Whenever possible, use trusted libraries like [`graphql-scalars`](https://www.npmjs.com/package/graphql-scalars). They offer production-ready
+Whenever possible, use trusted libraries like [`graphql-scalars`](https://www.npmjs.com/package/graphql-scalars). They offer production-ready
scalars for DateTime, EmailAddress, URL, UUID, and many others.
### Example: Handling email validation
-Handling email validation correctly requires dealing with Unicode, quoted local parts, and
-domain validation. Rather than writing your own regex, it's better to use a library scalar
+Handling email validation correctly requires dealing with Unicode, quoted local parts, and
+domain validation. Rather than writing your own regex, it's better to use a library scalar
that's already validated against standards.
If you need domain-specific behavior, you can wrap an existing scalar with custom rules:
@@ -242,12 +244,12 @@ const StrictEmailAddress = new GraphQLScalarType({
});
```
-By following these best practices and using trusted tools where needed, you can build custom
+By following these best practices and using trusted tools where needed, you can build custom
scalars that are reliable, maintainable, and easy for clients to work with.
## Additional resources
-- [GraphQL Scalars by The Guild](https://the-guild.dev/graphql/scalars): A production-ready
-library of common custom scalars.
-- [GraphQL Scalars Specification](https://github.com/graphql/graphql-scalars): This
-specification is no longer actively maintained, but useful for historical context.
+- [GraphQL Scalars by The Guild](https://the-guild.dev/graphql/scalars): A production-ready
+ library of common custom scalars.
+- [GraphQL Scalars Specification](https://github.com/graphql/graphql-scalars): This
+ specification is no longer actively maintained, but useful for historical context.
diff --git a/website/pages/docs/advanced-execution-pipelines.mdx b/website/pages/docs/advanced-execution-pipelines.mdx
index 85a82059e5..c8897bfca3 100644
--- a/website/pages/docs/advanced-execution-pipelines.mdx
+++ b/website/pages/docs/advanced-execution-pipelines.mdx
@@ -53,7 +53,9 @@ function executeWithTiming(args) {
};
};
- return typeof result.then === 'function' ? result.then(addTiming) : addTiming(result);
+ return typeof result.then === 'function'
+ ? result.then(addTiming)
+ : addTiming(result);
}
```
@@ -101,7 +103,9 @@ function subscribeWithHostPipeline(args) {
return mapSourceToResponseEvent(validatedArgs, sourceEventStream);
};
- return typeof source.then === 'function' ? source.then(mapSource) : mapSource(source);
+ return typeof source.then === 'function'
+ ? source.then(mapSource)
+ : mapSource(source);
}
```
diff --git a/website/pages/docs/authentication-and-express-middleware.mdx b/website/pages/docs/authentication-and-express-middleware.mdx
index a633168f52..5c493b7107 100644
--- a/website/pages/docs/authentication-and-express-middleware.mdx
+++ b/website/pages/docs/authentication-and-express-middleware.mdx
@@ -15,6 +15,7 @@ For example, let's say we wanted our server to log the IP address of every reque
+
```js
import express from 'express';
import { createHandler } from 'graphql-http/lib/use/express';
@@ -47,29 +48,26 @@ app.all(
);
app.listen(4000);
console.log('Running a GraphQL API server at localhost:4000/graphql');
-
```
+
+
```js
import express from 'express';
import { createHandler } from 'graphql-http/lib/use/express';
-import {
- GraphQLObjectType,
- GraphQLSchema,
- GraphQLString,
-} from 'graphql';
+import { GraphQLObjectType, GraphQLSchema, GraphQLString } from 'graphql';
const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
- fields: {
- ip: {
+ fields: {
+ ip: {
type: GraphQLString,
resolve: (_, args, context) => {
return context.ip;
- }
- }
+ },
+ },
},
}),
});
@@ -92,8 +90,8 @@ app.all(
);
app.listen(4000);
console.log('Running a GraphQL API server at localhost:4000/graphql');
-
```
+
@@ -103,4 +101,4 @@ If you aren't familiar with any of these authentication mechanisms, we recommend
If you've read through the docs linearly to get to this point, congratulations! You now know everything you need to build a practical GraphQL API server.
-Want to control access to specific operations or fields? See [Authorization Strategies](./authorization-strategies).
\ No newline at end of file
+Want to control access to specific operations or fields? See [Authorization Strategies](./authorization-strategies).
diff --git a/website/pages/docs/authorization-strategies.mdx b/website/pages/docs/authorization-strategies.mdx
index d43e19f123..9c73342c34 100644
--- a/website/pages/docs/authorization-strategies.mdx
+++ b/website/pages/docs/authorization-strategies.mdx
@@ -2,25 +2,26 @@
title: Authorization Strategies
---
-import { Callout } from 'nextra/components'
+import { Callout } from 'nextra/components';
-GraphQL gives you complete control over how to define and enforce access control.
-That flexibility means it's up to you to decide where authorization rules live and
+GraphQL gives you complete control over how to define and enforce access control.
+That flexibility means it's up to you to decide where authorization rules live and
how they're enforced.
-This guide covers common strategies for implementing authorization in GraphQL
-servers using GraphQL.js. It assumes you're authenticating requests and passing a user or
+This guide covers common strategies for implementing authorization in GraphQL
+servers using GraphQL.js. It assumes you're authenticating requests and passing a user or
session object into the `context`.
- In production systems authorization should be handled in your business logic layer, not your
- GraphQL resolvers. GraphQL is intended to be a thin execution layer that calls into your application's
- domain logic, which enforces access control.
+ In production systems authorization should be handled in your business logic
+ layer, not your GraphQL resolvers. GraphQL is intended to be a thin execution
+ layer that calls into your application's domain logic, which enforces access
+ control.
## What is authorization?
-Authorization determines what a user is allowed to do. It's different from
+Authorization determines what a user is allowed to do. It's different from
authentication, which verifies who a user is.
In GraphQL, authorization typically involves restricting:
@@ -47,13 +48,13 @@ export const resolvers = {
```
This approach can help when you're learning how context works or building quick prototypes.
-However, for production systems, you should enforce access control in your business logic layer
+However, for production systems, you should enforce access control in your business logic layer
rather than in GraphQL resolvers.
## Centralizing access control logic
-If you're experimenting or building a small project, repeating checks like
-`context.user.role !== 'admin'` across resolvers can become error-prone. One
+If you're experimenting or building a small project, repeating checks like
+`context.user.role !== 'admin'` across resolvers can become error-prone. One
way to manage that duplication is by extracting shared logic into utility functions:
```js
@@ -86,15 +87,15 @@ export const resolvers = {
};
```
-This pattern improves readability and reusability, but like all resolver-based authorization,
+This pattern improves readability and reusability, but like all resolver-based authorization,
it's best suited for prototypes or early-stage development.
-For production use, move authorization into your business logic layer. These helpers can still be useful
+For production use, move authorization into your business logic layer. These helpers can still be useful
there, but they should be applied outside the GraphQL execution layer.
## Field-level access control
-You can also conditionally return or hide data at the field level. This
+You can also conditionally return or hide data at the field level. This
is useful when, for example, users should only see their own private data:
```js
@@ -134,7 +135,7 @@ function withAuthCheck(resolverFn, schema, fieldNode, variableValues, context) {
const directive = getDirectiveValues(
schema.getDirective('auth'),
fieldNode,
- variableValues
+ variableValues,
);
if (directive?.role && context.user?.role !== directive.role) {
@@ -162,27 +163,25 @@ resolver-based checks and adopt directives later if needed.
- Keep authorization logic in your business logic layer, not in your GraphQL resolvers.
- Use shared helper functions to reduce duplication and improve clarity.
- Avoid tightly coupling authorization logic to your schema. Make it
-reusable where possible.
+ reusable where possible.
- Consider using `null` to hide fields from unauthorized users, rather than
-throwing errors.
+ throwing errors.
- Be mindful of tools like introspection or GraphQL Playground that can
-expose your schema. Use caution when deploying introspection in production
-environments.
+ expose your schema. Use caution when deploying introspection in production
+ environments.
## Additional resources
-- [Anatomy of a Resolver](./resolver-anatomy): Shows how resolvers work and how the `context`
-object is passed in. Helpful if you're new to writing custom resolvers or
-want to understand where authorization logic fits.
-- [GraphQL Specification, Execution section](https://spec.graphql.org/October2021/#sec-Execution): Defines how fields are
-resolved, including field-level error propagation and execution order. Useful
-background when building advanced authorization patterns that rely on the
-structure of GraphQL execution.
-- [`graphql-shield`](https://github.com/dimatill/graphql-shield): A community library for adding rule-based
-authorization as middleware to resolvers.
-- [`graphql-auth-directives`](https://github.com/the-guild-org/graphql-auth-directives): Adds support for custom directives like
-`@auth(role: "admin")`, letting you declare access control rules in SDL.
-Helpful if you're building a schema-first API and prefer declarative access
-control.
-
-
+- [Anatomy of a Resolver](./resolver-anatomy): Shows how resolvers work and how the `context`
+ object is passed in. Helpful if you're new to writing custom resolvers or
+ want to understand where authorization logic fits.
+- [GraphQL Specification, Execution section](https://spec.graphql.org/October2021/#sec-Execution): Defines how fields are
+ resolved, including field-level error propagation and execution order. Useful
+ background when building advanced authorization patterns that rely on the
+ structure of GraphQL execution.
+- [`graphql-shield`](https://github.com/dimatill/graphql-shield): A community library for adding rule-based
+ authorization as middleware to resolvers.
+- [`graphql-auth-directives`](https://github.com/the-guild-org/graphql-auth-directives): Adds support for custom directives like
+ `@auth(role: "admin")`, letting you declare access control rules in SDL.
+ Helpful if you're building a schema-first API and prefer declarative access
+ control.
diff --git a/website/pages/docs/basic-types.mdx b/website/pages/docs/basic-types.mdx
index 4913b5a11e..d051a1f767 100644
--- a/website/pages/docs/basic-types.mdx
+++ b/website/pages/docs/basic-types.mdx
@@ -18,6 +18,7 @@ Each of these types maps straightforwardly to JavaScript, so you can just return
+
```js
import express from 'express';
import { createHandler } from 'graphql-http/lib/use/express';
@@ -55,10 +56,11 @@ app.all(
);
app.listen(4000);
console.log('Running a GraphQL API server at localhost:4000/graphql');
-
```
+
+
```js
import express from 'express';
import { createHandler } from 'graphql-http/lib/use/express';
@@ -75,17 +77,18 @@ const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
fields: {
- quoteOfTheDay: {
+ quoteOfTheDay: {
type: GraphQLString,
- resolve: () => Math.random() < 0.5 ? 'Take it easy' : 'Salvation lies within'
+ resolve: () =>
+ Math.random() < 0.5 ? 'Take it easy' : 'Salvation lies within',
},
- random: {
+ random: {
type: GraphQLFloat,
- resolve: () => Math.random()
+ resolve: () => Math.random(),
},
- rollThreeDice: {
+ rollThreeDice: {
type: new GraphQLList(GraphQLFloat),
- resolve: () => [1, 2, 3].map((_) => 1 + Math.floor(Math.random() * 6))
+ resolve: () => [1, 2, 3].map((_) => 1 + Math.floor(Math.random() * 6)),
},
},
}),
@@ -101,8 +104,8 @@ app.all(
app.listen(4000);
console.log('Running a GraphQL API server at localhost:4000/graphql');
-
```
+
diff --git a/website/pages/docs/caching-strategies.mdx b/website/pages/docs/caching-strategies.mdx
index b33cdb4d6e..d77e0ff5b7 100644
--- a/website/pages/docs/caching-strategies.mdx
+++ b/website/pages/docs/caching-strategies.mdx
@@ -24,7 +24,7 @@ There are several opportunities to apply caching within a GraphQL server:
- **Resolver-level caching**: Cache the result of specific fields.
- **Request-level caching**: Batch and cache repeated access to backend resources
-within a single operation.
+ within a single operation.
- **Operation result caching**: Reuse the entire response for repeated identical queries.
- **Schema caching**: Cache the compiled schema when startup cost is high.
- **Transport/middleware caching**: Leverage caching behavior in HTTP servers or proxies.
@@ -34,9 +34,9 @@ without overcomplicating your system.
## Resolver-level caching
-Resolver-level caching is useful when a specific field’s value is expensive to compute and
-commonly requested with the same arguments. Instead of recomputing or refetching the data on
-every request, you can store the result temporarily in memory and return it directly when the
+Resolver-level caching is useful when a specific field’s value is expensive to compute and
+commonly requested with the same arguments. Instead of recomputing or refetching the data on
+every request, you can store the result temporarily in memory and return it directly when the
same input appears again.
### Use cases
@@ -75,15 +75,15 @@ export const resolvers = {
};
```
-This example uses [`lru-cache`](https://www.npmjs.com/package/lru-cache), which limits the
-number of stored items and support TTL-based expiration. You can replace it with Redis or
+This example uses [`lru-cache`](https://www.npmjs.com/package/lru-cache), which limits the
+number of stored items and support TTL-based expiration. You can replace it with Redis or
another cache if you need cross-process consistency.
### Guidelines
- Resolver-level caches are global. Be careful with authorization-sensitive data.
- This technique works best for data that doesn't change often or can tolerate short-lived
-staleness.
+ staleness.
- TTL should match how often the underlying data is expected to change.
## Request-level caching with DataLoader
@@ -105,7 +105,8 @@ The following example defines a DataLoader instance that batches user lookups by
import DataLoader from 'dataloader';
import { batchGetUsers } from '../services/users.js';
-export const createUserLoader = () => new DataLoader(ids => batchGetUsers(ids));
+export const createUserLoader = () =>
+ new DataLoader((ids) => batchGetUsers(ids));
```
You can then include the loader in the per-request context to isolate it from other
@@ -146,8 +147,8 @@ see [Solving the N+1 Problem with DataLoader](./n1-dataloader).
## Operation result caching
-Operation result caching stores the complete response of a query, keyed by the query string, variables, and potentially
-HTTP headers. It can dramatically improve performance when the same query is sent frequently, particularly for read-heavy
+Operation result caching stores the complete response of a query, keyed by the query string, variables, and potentially
+HTTP headers. It can dramatically improve performance when the same query is sent frequently, particularly for read-heavy
applications.
### Use cases
@@ -192,7 +193,7 @@ const inflight = new Map();
export function executeWithCache({ cacheKey, executeFn }) {
const existing = inflight.get(cacheKey);
if (existing) return existing;
-
+
const promise = _executeWithCacheUnbatched({ cacheKey, executeFn });
inflight.set(cacheKey, promise);
return promise.finally(() => inflight.delete(cacheKey));
@@ -244,7 +245,7 @@ export function getSchema() {
## Cache invalidation
No caching strategy is complete without an invalidation plan. Cached data can
-become stale or incorrect, and serving outdated information can lead to bugs or a
+become stale or incorrect, and serving outdated information can lead to bugs or a
degraded user experience.
The following are common invalidation techniques:
@@ -254,13 +255,13 @@ The following are common invalidation techniques:
- **Key versioning**: Encode version or timestamp metadata into cache keys
- **Stale-while-revalidate**: Serve stale data while refreshing it in the background
-Design your invalidation strategy based on your data’s volatility and your clients’
+Design your invalidation strategy based on your data’s volatility and your clients’
tolerance for staleness.
## Third-party and edge caching
-While GraphQL.js does not include built-in support for third-party or edge caching, it integrates
-well with external tools and middleware that handle full response caching or caching by query
+While GraphQL.js does not include built-in support for third-party or edge caching, it integrates
+well with external tools and middleware that handle full response caching or caching by query
signature.
### Use cases
@@ -283,8 +284,8 @@ The following tools and layers are commonly used:
## Client-side caching
-GraphQL clients include sophisticated client-side caches that store
-normalized query results and reuse them across views or components. While this is out of scope for GraphQL.js
+GraphQL clients include sophisticated client-side caches that store
+normalized query results and reuse them across views or components. While this is out of scope for GraphQL.js
itself, server-side caching should be designed with client behavior in mind.
### When to consider it in server design
diff --git a/website/pages/docs/constructing-types.mdx b/website/pages/docs/constructing-types.mdx
index 2744ab7c81..28df5d3b2c 100644
--- a/website/pages/docs/constructing-types.mdx
+++ b/website/pages/docs/constructing-types.mdx
@@ -14,6 +14,7 @@ For example, let's say we are building a simple API that lets you fetch user dat
+
```js
import express from 'express';
import { createHandler } from 'graphql-http/lib/use/express';
@@ -58,10 +59,11 @@ app.all(
);
app.listen(4000);
console.log('Running a GraphQL API server at localhost:4000/graphql');
+```
-````
+
```js
import express from 'express';
import { createHandler } from 'graphql-http/lib/use/express';
@@ -116,7 +118,7 @@ app.all(
);
app.listen(4000);
console.log('Running a GraphQL API server at localhost:4000/graphql');
-````
+```
diff --git a/website/pages/docs/cursor-based-pagination.mdx b/website/pages/docs/cursor-based-pagination.mdx
index 93eefdd98a..a663fe23e8 100644
--- a/website/pages/docs/cursor-based-pagination.mdx
+++ b/website/pages/docs/cursor-based-pagination.mdx
@@ -2,7 +2,7 @@
title: Implementing Cursor-based Pagination
---
-import { Callout } from "nextra/components";
+import { Callout } from 'nextra/components';
# Implementing Cursor-based Pagination
@@ -14,8 +14,8 @@ remove items between requests.
GraphQL.js doesn't include cursor pagination out of the box, but you can implement
it using custom types and resolvers. This guide shows how to build a paginated field
-using the connection pattern popularized by [Relay](https://relay.dev). By the end of this
-guide, you will be able to define cursors and return results in a consistent structure
+using the connection pattern popularized by [Relay](https://relay.dev). By the end of this
+guide, you will be able to define cursors and return results in a consistent structure
that works well with clients.
## The connection pattern
@@ -130,7 +130,7 @@ const UserConnectionType = new GraphQLObjectType({
fields: {
edges: {
type: new GraphQLNonNull(
- new GraphQLList(new GraphQLNonNull(UserEdgeType))
+ new GraphQLList(new GraphQLNonNull(UserEdgeType)),
),
},
pageInfo: { type: new GraphQLNonNull(PageInfoType) },
@@ -254,7 +254,7 @@ async function resolveUsers(_, args) {
const result = await db.query(
'SELECT id, name FROM users ORDER BY id ASC LIMIT $1 OFFSET $2',
- [limit + 1, offset] // Fetch one extra row to compute hasNextPage
+ [limit + 1, offset], // Fetch one extra row to compute hasNextPage
);
const slice = result.rows.slice(0, limit);
@@ -307,11 +307,11 @@ onwards.
When implementing pagination, consider how your resolver should handle the following scenarios:
- **Empty result sets**: Return an empty `edges` array and a `pageInfo` object with
-`hasNextPage: false` and `endCursor: null`.
+ `hasNextPage: false` and `endCursor: null`.
- **Invalid cursors**: If decoding a cursor fails, treat it as a `null` or return an error,
-depending on your API's behavior.
+ depending on your API's behavior.
- **End of list**: If the requested `first` exceeds the available data, return all remaining
-items and set `hasNextPage: false`.
+ items and set `hasNextPage: false`.
Always test your pagination with multiple boundaries: beginning, middle, end, and out-of-bounds
errors.
@@ -323,4 +323,4 @@ To learn more about cursor-based pagination patterns and best practices, see:
- [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
+ 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 d50c8fbf6c..064c6cbaf9 100644
--- a/website/pages/docs/custom-scalars.mdx
+++ b/website/pages/docs/custom-scalars.mdx
@@ -6,8 +6,8 @@ import { Callout } from 'nextra/components';
# Custom Scalars: When and How to Use Them
-In GraphQL, scalar types represent primitive data like strings, numbers, and booleans.
-The GraphQL specification defines five built-in scalars: `Int`, `Float`,
+In GraphQL, scalar types represent primitive data like strings, numbers, and booleans.
+The GraphQL specification defines five built-in scalars: `Int`, `Float`,
`String`, `Boolean`, and `ID`.
However, these default types don't cover all the formats or domain-specific values real-world
@@ -45,23 +45,24 @@ const DateTime = new GraphQLScalarType({
},
});
```
-Custom scalars offer flexibility, but they also shift responsibility onto you. You're
-defining not just the format of a value, but also how it is validated and how it moves
+
+Custom scalars offer flexibility, but they also shift responsibility onto you. You're
+defining not just the format of a value, but also how it is validated and how it moves
through your schema.
This guide covers when to use custom scalars and how to define them in GraphQL.js.
## When to use custom scalars
-Define a custom scalar when you need to enforce a specific format, encapsulate domain-specific
+Define a custom scalar when you need to enforce a specific format, encapsulate domain-specific
logic, or standardize a primitive value across your schema. For example:
-- Validation: Ensure that inputs like email addresses, URLs, or date strings match a
-strict format.
-- Serialization and parsing: Normalize how values are converted between internal and
-client-facing formats.
-- Domain primitives: Represent domain-specific values that behave like scalars, such as
-UUIDs or currency codes.
+- Validation: Ensure that inputs like email addresses, URLs, or date strings match a
+ strict format.
+- Serialization and parsing: Normalize how values are converted between internal and
+ client-facing formats.
+- Domain primitives: Represent domain-specific values that behave like scalars, such as
+ UUIDs or currency codes.
Common examples of useful custom scalars include:
@@ -84,7 +85,7 @@ system, not to replace structured types altogether.
## How to define a custom scalar in GraphQL.js
-In GraphQL.js, a custom scalar is defined by creating an instance of `GraphQLScalarType`,
+In GraphQL.js, a custom scalar is defined by creating an instance of `GraphQLScalarType`,
providing a name, description, and three functions:
- `serialize`: How the server sends internal values to clients.
@@ -105,7 +106,7 @@ 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)) {
throw new TypeError('DateTime can only serialize Date instances');
@@ -116,18 +117,24 @@ const DateTime = new GraphQLScalarType({
parseValue(value) {
const date = new Date(value);
if (isNaN(date.getTime())) {
- throw new TypeError(`DateTime cannot represent an invalid date: ${value}`);
+ throw new TypeError(
+ `DateTime cannot represent an invalid date: ${value}`,
+ );
}
return date;
},
parseLiteral(ast) {
if (ast.kind !== Kind.STRING) {
- throw new TypeError(`DateTime can only parse string values, but got: ${ast.kind}`);
+ throw new TypeError(
+ `DateTime can only parse string values, but got: ${ast.kind}`,
+ );
}
const date = new Date(ast.value);
if (isNaN(date.getTime())) {
- throw new TypeError(`DateTime cannot represent an invalid date: ${ast.value}`);
+ throw new TypeError(
+ `DateTime cannot represent an invalid date: ${ast.value}`,
+ );
}
return date;
},
diff --git a/website/pages/docs/defer-stream.mdx b/website/pages/docs/defer-stream.mdx
index 5b5cf85048..d02ac8825d 100644
--- a/website/pages/docs/defer-stream.mdx
+++ b/website/pages/docs/defer-stream.mdx
@@ -8,8 +8,8 @@ import { Callout } from 'nextra/components';
# Enabling Defer and Stream
- `@defer`, `@stream`, and `experimentalExecuteIncrementally()` are available
- in GraphQL.js v17 and newer. Incremental delivery is pending GraphQL
+ `@defer`, `@stream`, and `experimentalExecuteIncrementally()` are available in
+ GraphQL.js v17 and newer. Incremental delivery is pending GraphQL
specification work.
diff --git a/website/pages/docs/development-mode.mdx b/website/pages/docs/development-mode.mdx
index 194b274ed1..6a014c757f 100644
--- a/website/pages/docs/development-mode.mdx
+++ b/website/pages/docs/development-mode.mdx
@@ -7,14 +7,14 @@ import { Callout } from 'nextra/components';
# Development Mode
- In v16 and earlier, development mode is enabled by default. Starting in v17, it
- is disabled by default.
+ In v16 and earlier, development mode is enabled by default. Starting in v17,
+ it is disabled by default.
- In v16 and earlier, `NODE_ENV=production` disables development checks. Starting
- in v17, development mode is controlled by exports conditions and `NODE_ENV` is
- ignored.
+ In v16 and earlier, `NODE_ENV=production` disables development checks.
+ Starting in v17, development mode is controlled by exports conditions and
+ `NODE_ENV` is ignored.
In development mode, GraphQL.js can provide an additional runtime check appropriate
diff --git a/website/pages/docs/execution-hooks.mdx b/website/pages/docs/execution-hooks.mdx
index f14b5c11c7..bdd4581e07 100644
--- a/website/pages/docs/execution-hooks.mdx
+++ b/website/pages/docs/execution-hooks.mdx
@@ -121,10 +121,7 @@ tracked async cleanup has finished. For example, a test harness may want the
operation to leave no pending execution work before assertions run.
```js
-import {
- executeRootSelectionSet,
- validateExecutionArgs,
-} from 'graphql';
+import { executeRootSelectionSet, validateExecutionArgs } from 'graphql';
async function executeAndWaitForAsyncWork(args) {
const validatedArgs = validateExecutionArgs(args);
diff --git a/website/pages/docs/getting-started.mdx b/website/pages/docs/getting-started.mdx
index f5de66e289..b91d2f8a81 100644
--- a/website/pages/docs/getting-started.mdx
+++ b/website/pages/docs/getting-started.mdx
@@ -34,6 +34,7 @@ npm install graphql --save
```
After running these commands, you'll have:
+
- `package.json` - your project configuration file
- `node_modules/` - directory where npm packages are installed
@@ -62,6 +63,7 @@ To handle GraphQL queries, we need a schema that defines the `Query` type, and w
+
```javascript
import { graphql, buildSchema } from 'graphql';
@@ -80,24 +82,30 @@ graphql({
schema,
source: '{ hello }',
rootValue,
- }).then((response) => {
- console.log(response);
- });
-
+}).then((response) => {
+ console.log(response);
+});
```
+
+
```javascript
-import { graphql, GraphQLSchema, GraphQLObjectType, GraphQLString } from 'graphql';
+import {
+ graphql,
+ GraphQLSchema,
+ GraphQLObjectType,
+ GraphQLString,
+} from 'graphql';
// Construct a schema
const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
fields: {
- hello: {
+ hello: {
type: GraphQLString,
- resolve: () => 'Hello world!'
+ resolve: () => 'Hello world!',
},
},
}),
@@ -110,6 +118,7 @@ graphql({
console.log(response);
});
```
+
diff --git a/website/pages/docs/going-to-production.mdx b/website/pages/docs/going-to-production.mdx
index 5dd0fc674f..3cf9765bb2 100644
--- a/website/pages/docs/going-to-production.mdx
+++ b/website/pages/docs/going-to-production.mdx
@@ -19,9 +19,10 @@ and how to disable them. Starting in v17, development mode is disabled by defaul
be explicitly enabled for development environments.
- In v16 and earlier, development mode is enabled by default and must be explicitly disabled
- in production. Starting in v17, development mode is disabled by default and may require
- explicit enabling. See [Development Mode](./development-mode) for details and instructions.
+ In v16 and earlier, development mode is enabled by default and must be
+ explicitly disabled in production. Starting in v17, development mode is
+ disabled by default and may require explicit enabling. See [Development
+ Mode](./development-mode) for details and instructions.
## Secure your schema
@@ -78,7 +79,7 @@ potentially open for anyone to use.
Additional resources:
- [GraphQL over HTTP Appendix
-A: Persisted Documents](https://github.com/graphql/graphql-over-http/pull/264)
+ A: Persisted Documents](https://github.com/graphql/graphql-over-http/pull/264)
- [GraphQL Trusted Documents](https://benjie.dev/graphql/trusted-documents) and
[Techniques to Protect Your GraphQL API](https://benjie.dev/talks/techniques-to-protect) at benjie.dev
- [@graphql-codegen/client-preset persisted documents](https://the-guild.dev/graphql/codegen/plugins/presets/preset-client#persisted-documents) or [graphql-codegen-persisted-query-ids](https://github.com/valu-digital/graphql-codegen-persisted-query-ids#integrating-with-apollo-client) for Apollo Client
@@ -98,7 +99,11 @@ production and disabling it may reduce your API's attack surface.
You can disable introspection in production, or only for unauthenticated users:
```js
-import { validate, specifiedRules, NoSchemaIntrospectionCustomRule } from 'graphql';
+import {
+ validate,
+ specifiedRules,
+ NoSchemaIntrospectionCustomRule,
+} from 'graphql';
const validationRules = isPublicRequest
? [...specifiedRules, NoSchemaIntrospectionCustomRule]
@@ -120,10 +125,7 @@ The following example shows how to limit query depth:
```js
import depthLimit from 'graphql-depth-limit';
-const validationRules = [
- depthLimit(10),
- ...specifiedRules,
-];
+const validationRules = [depthLimit(10), ...specifiedRules];
```
Instead of depth, you can assign each field a cost and reject queries that exceed a total budget.
@@ -139,17 +141,17 @@ authorization should take place:
// From your business logic
const postRepository = {
getBody({ user, post }) {
- if (user?.id && (user.id === post.authorId)) {
- return post.body
+ if (user?.id && user.id === post.authorId) {
+ return post.body;
}
- return null
- }
-}
+ return null;
+ },
+};
// Resolver for the `Post.body` field:
function Post_body(source, args, context, info) {
// return the post body only if the user is the post's author
- return postRepository.getBody({ user: context.user, post: source })
+ return postRepository.getBody({ user: context.user, post: source });
}
```
@@ -176,7 +178,7 @@ The most common performance issue in GraphQL is the N+1 query problem, where nes
make repeated calls for related data. `DataLoader` helps avoid this by batching and caching
field-level fetches within a single request.
-For more information on this issue and how to resolve it, see
+For more information on this issue and how to resolve it, see
[Solving the N+1 Problem with DataLoader](./n1-dataloader/).
### Apply caching where appropriate
@@ -185,7 +187,7 @@ You can apply caching at several levels, depending on your server architecture:
- **Resolver-level caching**: Cache the results of expensive operations for a short duration.
- **HTTP caching**: Use persisted queries and edge caching to avoid re-processing
-common queries.
+ common queries.
- **Schema caching**: If your schema is static, avoid rebuilding it on every request.
For larger applications, consider request-scoped caching or external systems like Redis to avoid
@@ -216,7 +218,7 @@ Avoid logging sensitive data like passwords or access tokens.
### Collect metrics
-Operational metrics help track the health and behavior of your server over time.
+Operational metrics help track the health and behavior of your server over time.
You can use tools like [Prometheus](https://prometheus.io) or [OpenTelemetry](https://opentelemetry.io)
to capture query counts, resolver durations, and error rates.
@@ -320,7 +322,7 @@ Integrate these checks into your CI/CD pipeline to catch issues before they reac
## Use environment-aware configuration
-You should tailor your GraphQL server's behavior based on the runtime environment.
+You should tailor your GraphQL server's behavior based on the runtime environment.
- Disable introspection and show minimal error messages in production.
- Enable playgrounds like GraphiQL or Apollo Sandbox only in development.
@@ -337,7 +339,7 @@ app.use(
schema,
graphiql: isDev,
customFormatErrorFn: formatError,
- })
+ }),
);
```
@@ -347,10 +349,12 @@ Use this checklist to verify that your GraphQL.js server is ready for production
Before deploying, confirm the following checks are complete:
### Build and environment
+
- Bundler sets `process.env.NODE_ENV` to `'production'`
- Development-only checks are removed from the production build
### Schema security
+
- Authentication is required for requests
- Authorization is enforced via business logic
- Rate limiting is applied
@@ -360,28 +364,33 @@ Before deploying, confirm the following checks are complete:
- Query cost limits are in place
### Performance
+
- `DataLoader` is used to batch data fetching
- Expensive resolvers use caching (request-scoped or shared)
- Public queries use HTTP or CDN caching
- Schema is reused across requests (not rebuilt each time)
### Monitoring and observability
+
- Logs are structured and machine-readable
- Metrics are collected (e.g., with Prometheus or OpenTelemetry)
- Tracing is enabled with a supported tool
- Logs do not include sensitive data
### Error handling
+
- Stack traces and internal messages are hidden in production
- Custom error types are used for common cases
- Errors include `extensions.code` for consistent client handling
- A `formatError` function is used to control error output
### Schema lifecycle
+
- Deprecated fields are marked with `@deprecated` and a clear reason
- Schema changes are validated before deployment
- CI/CD includes schema diff checks
### Environment configuration
+
- Playground tools (e.g., GraphiQL) are only enabled in development
- Error formatting, logging, and introspection are environment-specific
diff --git a/website/pages/docs/graphql-errors.mdx b/website/pages/docs/graphql-errors.mdx
index 6c0b4d263d..779e308fa3 100644
--- a/website/pages/docs/graphql-errors.mdx
+++ b/website/pages/docs/graphql-errors.mdx
@@ -1,16 +1,17 @@
---
title: Understanding GraphQL.js Errors
---
-import { Callout, GitHubNoteIcon } from "nextra/components";
+
+import { Callout, GitHubNoteIcon } from 'nextra/components';
# Understanding GraphQL.js Errors
-When executing a GraphQL operation, a server might encounter problems, such as failing to fetch
-data, encountering invalid arguments, or running into unexpected internal issues. Instead of
-crashing or halting execution, GraphQL.js collects these problems as structured errors
+When executing a GraphQL operation, a server might encounter problems, such as failing to fetch
+data, encountering invalid arguments, or running into unexpected internal issues. Instead of
+crashing or halting execution, GraphQL.js collects these problems as structured errors
and includes them in the response.
-This guide explains how GraphQL.js represents errors internally, how errors propagate through a
+This guide explains how GraphQL.js represents errors internally, how errors propagate through a
query, and how you can customize error behavior.
## How GraphQL.js represents errors in a response
@@ -41,7 +42,7 @@ Each error object can include the following fields:
- `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.
+ codes or debugging information.
@@ -94,7 +95,7 @@ Each option helps tie the error to specific parts of the GraphQL execution:
When a resolver throws an error:
- If the thrown value is a `GraphQLError` and contains the required information
-(`path`), GraphQL.js uses it as-is.
+ (`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.
@@ -109,10 +110,10 @@ Errors in GraphQL don't necessarily abort the entire operation. How an error aff
depends on the nullability of the field where the error occurs.
- **Nullable fields**: If a resolver for a nullable field throws an error, GraphQL.js records
-the error and sets the field's value to `null` in the `data` payload.
+ 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`).
+ 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:
@@ -165,9 +166,9 @@ throw new GraphQLError('Unauthorized', {
extensions: {
code: 'UNAUTHORIZED',
http: {
- status: 401
- }
- }
+ status: 401,
+ },
+ },
});
```
@@ -179,20 +180,20 @@ Common use cases for `extensions` include:
- Specifying HTTP status codes
- Including internal debug information (hidden from production clients)
-Libraries like [Apollo Server](https://www.apollographql.com/docs/apollo-server/data/errors/) and
-[Envelop](https://the-guild.dev/graphql/envelop/plugins/use-error-handler) offer conventions for
+Libraries like [Apollo Server](https://www.apollographql.com/docs/apollo-server/data/errors/) and
+[Envelop](https://the-guild.dev/graphql/envelop/plugins/use-error-handler) offer conventions for
structured error extensions, if you want to adopt standardized patterns.
## Best practices for error handling
-- Write clear, actionable messages. Error messages should help developers understand what went
-wrong and how to fix it.
-- Use error codes in extensions. Define a set of stable, documented error codes for your API
-to make client-side error handling easier.
-- Avoid leaking internal details. Do not expose stack traces, database errors, or other
-sensitive information to clients.
-- Wrap unexpected errors. Catch and wrap low-level exceptions to ensure that all errors passed
-through your GraphQL server follow the `GraphQLError` structure.
+- Write clear, actionable messages. Error messages should help developers understand what went
+ wrong and how to fix it.
+- Use error codes in extensions. Define a set of stable, documented error codes for your API
+ to make client-side error handling easier.
+- Avoid leaking internal details. Do not expose stack traces, database errors, or other
+ sensitive information to clients.
+- Wrap unexpected errors. Catch and wrap low-level exceptions to ensure that all errors passed
+ through your GraphQL server follow the `GraphQLError` structure.
In larger servers, you might centralize error handling with a custom error formatting function
to enforce these best practices consistently.
diff --git a/website/pages/docs/graphql-http.mdx b/website/pages/docs/graphql-http.mdx
index 4d4c92636c..0bfebd0cb2 100644
--- a/website/pages/docs/graphql-http.mdx
+++ b/website/pages/docs/graphql-http.mdx
@@ -53,4 +53,4 @@ for sample usage.
See [graphql-http.com](https://graphql-http.com/) and the
[GitHub README](https://github.com/graphql/graphql-http) for more extensive
documentation, including how to use `graphql-http` with other server
-frameworks and runtimes.
\ No newline at end of file
+frameworks and runtimes.
diff --git a/website/pages/docs/migrating-from-express-graphql.mdx b/website/pages/docs/migrating-from-express-graphql.mdx
index 358ba4eb82..fe7af1dfa3 100644
--- a/website/pages/docs/migrating-from-express-graphql.mdx
+++ b/website/pages/docs/migrating-from-express-graphql.mdx
@@ -5,14 +5,14 @@ sidebarTitle: Migrate from Express GraphQL
# Migrate from Express GraphQL to GraphQL over HTTP
-When GraphQL was open-sourced in 2015, `express-graphql` quickly became the standard way to run a GraphQL server in Node.js.
-Built as middleware for Express, it offered a simple and reliable development experience. However, it hasn’t received a
-feature update since 2018 and is no longer actively maintained. For modern applications, it lacks support for new transport
+When GraphQL was open-sourced in 2015, `express-graphql` quickly became the standard way to run a GraphQL server in Node.js.
+Built as middleware for Express, it offered a simple and reliable development experience. However, it hasn’t received a
+feature update since 2018 and is no longer actively maintained. For modern applications, it lacks support for new transport
features, fine-grained request handling, and deployment flexibility.
-[`graphql-http`](https://github.com/graphql/graphql-http) is a lightweight implementation of
-the [GraphQL over HTTP specification](https://graphql.github.io/graphql-over-http/draft/). It's framework-agnostic, built to be
-composable, and easy to integrate into different server environments. Unlike `express-graphql`, it can run in a wide range of
+[`graphql-http`](https://github.com/graphql/graphql-http) is a lightweight implementation of
+the [GraphQL over HTTP specification](https://graphql.github.io/graphql-over-http/draft/). It's framework-agnostic, built to be
+composable, and easy to integrate into different server environments. Unlike `express-graphql`, it can run in a wide range of
environments, not just Express.
This guide is for developers currently using `express-graphql` who want to
@@ -47,13 +47,13 @@ server will support future capabilities without relying on workarounds.
Although `graphql-http` is a strong foundation for modern GraphQL servers, it's important to note what it doesn't include:
- It doesn't support subscriptions or experimental features like incremental delivery (`@defer` / `@stream`) out of the box.
-- These limitations are by design. `graphql-http` strictly adheres to the current
-[GraphQL over HTTP specification](https://graphql.github.io/graphql-over-http/draft/), which does
-not yet define behavior for those features.
-- If your application needs support for subscriptions or live queries, consider using complementary libraries like
-[`graphql-ws`](https://github.com/enisdenjo/graphql-ws) or [`graphql-sse`](https://github.com/enisdenjo/graphql-sse).
+- These limitations are by design. `graphql-http` strictly adheres to the current
+ [GraphQL over HTTP specification](https://graphql.github.io/graphql-over-http/draft/), which does
+ not yet define behavior for those features.
+- If your application needs support for subscriptions or live queries, consider using complementary libraries like
+ [`graphql-ws`](https://github.com/enisdenjo/graphql-ws) or [`graphql-sse`](https://github.com/enisdenjo/graphql-sse).
-These are not limitations unique to `graphql-http`. `express-graphql` does not support these features either, but it's important
+These are not limitations unique to `graphql-http`. `express-graphql` does not support these features either, but it's important
to set the right expectations about extensibility.
## Migration guide
@@ -86,10 +86,13 @@ In your Express server file, remove the `express-graphql` middleware:
// Before (using express-graphql)
import { graphqlHTTP } from 'express-graphql';
-app.use('/graphql', graphqlHTTP({
- schema,
- graphiql: true,
-}));
+app.use(
+ '/graphql',
+ graphqlHTTP({
+ schema,
+ graphiql: true,
+ }),
+);
```
### Step 3: Add graphql-http middleware with createHandler
@@ -109,9 +112,9 @@ app.listen(4000);
```
- Use `app.all()` to allow both `GET` and `POST` requests.
-- The handler accepts an options object for GraphQL-specific settings like `schema`,
-`rootValue`, and `context`, but doesn’t handle server-level features such as middleware
-or request preprocessing like `express-graphql` did.
+- The handler accepts an options object for GraphQL-specific settings like `schema`,
+ `rootValue`, and `context`, but doesn’t handle server-level features such as middleware
+ or request preprocessing like `express-graphql` did.
### Step 4: Handle context, error formatting, and extensions
@@ -120,28 +123,31 @@ You can provide options like `context`, `rootValue`, and `formatError`:
```js
import { GraphQLError } from 'graphql';
-app.all('/graphql', createHandler({
- schema,
- context: async (req, res) => {
- const user = await authenticate(req);
- return { user };
- },
- formatError: (error) =>
- new GraphQLError(error.message, {
- nodes: error.nodes,
- path: error.path,
- extensions: {
- code: 'INTERNAL_SERVER_ERROR',
- timestamp: Date.now(),
- },
- }),
-}));
+app.all(
+ '/graphql',
+ createHandler({
+ schema,
+ context: async (req, res) => {
+ const user = await authenticate(req);
+ return { user };
+ },
+ formatError: (error) =>
+ new GraphQLError(error.message, {
+ nodes: error.nodes,
+ path: error.path,
+ extensions: {
+ code: 'INTERNAL_SERVER_ERROR',
+ timestamp: Date.now(),
+ },
+ }),
+ }),
+);
```
- `context` can be a static object or an async function.
- You can also pass `rootValue` or other GraphQL-specific options.
-- To modify the HTTP response, such as adding headers or extensions, you’ll need
-to do that outside of graphql-http, using Express middleware or route handlers.
+- To modify the HTTP response, such as adding headers or extensions, you’ll need
+ to do that outside of graphql-http, using Express middleware or route handlers.
### Step 5: Add a GraphQL IDE
@@ -196,20 +202,20 @@ This gives you more control but requires a bit more setup.
### Understand streaming and file upload limitations
-`graphql-http` aims to support the GraphQL over HTTP spec, including eventually supporting response streaming. However,
-support for features like `@defer` and `@stream` is still evolving. These capabilities are experimental in `graphql-js`
+`graphql-http` aims to support the GraphQL over HTTP spec, including eventually supporting response streaming. However,
+support for features like `@defer` and `@stream` is still evolving. These capabilities are experimental in `graphql-js`
and not yet supported by `graphql-http`.
- Some GraphQL clients have begun adding support for multipart responses, but broad adoption is still evolving.
-- `graphql-http` does not support streaming features such as `@defer` or `@stream`, as these are not part of the
-current GraphQL over HTTP specification.
-- If your app relies on incremental delivery, use a transport library like [`graphql-sse`](https://github.com/enisdenjo/graphql-sse),
-but note that it replaces `graphql-http` and must be used as your server handler.
+- `graphql-http` does not support streaming features such as `@defer` or `@stream`, as these are not part of the
+ current GraphQL over HTTP specification.
+- If your app relies on incremental delivery, use a transport library like [`graphql-sse`](https://github.com/enisdenjo/graphql-sse),
+ but note that it replaces `graphql-http` and must be used as your server handler.
## What's next
`graphql-http` is the reference implementation of the GraphQL-over-HTTP specification,
-but there are many other servers you can use to serve your GraphQL API, each with
+but there are many other servers you can use to serve your GraphQL API, each with
different features and trade-offs. For a list of other spec-compliant server
implementations see the
[`graphql-http` server list](https://github.com/graphql/graphql-http?tab=readme-ov-file#servers), and don't forget to check out the
diff --git a/website/pages/docs/mutations-and-input-types.mdx b/website/pages/docs/mutations-and-input-types.mdx
index f659d2f6f9..71067eaa74 100644
--- a/website/pages/docs/mutations-and-input-types.mdx
+++ b/website/pages/docs/mutations-and-input-types.mdx
@@ -12,6 +12,7 @@ Let's say we have a "message of the day" server, where anyone can update the mes
+
```graphql
type Mutation {
setMessage(message: String): String
@@ -21,14 +22,12 @@ type Query {
getMessage: String
}
```
+
+
```js
-import {
- GraphQLObjectType,
- GraphQLString,
- GraphQLSchema,
-} from 'graphql';
+import { GraphQLObjectType, GraphQLString, GraphQLSchema } from 'graphql';
const schema = new GraphQLSchema({
query: new GraphQLObjectType({
@@ -72,6 +71,7 @@ For example, instead of a single message of the day, let's say we have many mess
+
```graphql
input MessageInput {
content: String
@@ -92,10 +92,11 @@ type Mutation {
createMessage(input: MessageInput): Message
updateMessage(id: ID!, input: MessageInput): Message
}
-
```
+
+
```js
import {
GraphQLObjectType,
@@ -140,12 +141,14 @@ const schema = new GraphQLSchema({
if (!fakeDatabase[id]) {
throw new Error('no message exists with id ' + id);
}
- return fakeDatabase[id] ? {
- id,
- content: fakeDatabase[id].content,
- author: fakeDatabase[id].author,
- } : null;
- }
+ return fakeDatabase[id]
+ ? {
+ id,
+ content: fakeDatabase[id].content,
+ author: fakeDatabase[id].author,
+ }
+ : null;
+ },
},
},
}),
@@ -166,7 +169,7 @@ const schema = new GraphQLSchema({
content: input.content,
author: input.author,
};
- }
+ },
},
updateMessage: {
type: Message,
@@ -185,7 +188,7 @@ const schema = new GraphQLSchema({
content: input.content,
author: input.author,
};
- }
+ },
},
},
}),
@@ -240,6 +243,7 @@ Here's some runnable code that implements this schema, keeping the data in memor
+
```js
import express from 'express';
import { createHandler } from 'graphql-http/lib/use/express';
@@ -282,38 +286,40 @@ class Message {
const root = {
getMessage: ({ id }) => {
- return fakeDatabase[id]
+ return fakeDatabase[id];
},
getMessages: () => {
- return Object.values(fakeDatabase)
+ return Object.values(fakeDatabase);
},
createMessage: ({ input }) => {
- const id = String(Object.keys(fakeDatabase).length + 1)
- const message = new Message(id, input)
- fakeDatabase[id] = message
- return message
+ const id = String(Object.keys(fakeDatabase).length + 1);
+ const message = new Message(id, input);
+ fakeDatabase[id] = message;
+ return message;
},
updateMessage: ({ id, input }) => {
- const message = fakeDatabase[id]
- Object.assign(message, input)
- return message
- }
-}
+ const message = fakeDatabase[id];
+ Object.assign(message, input);
+ return message;
+ },
+};
const app = express();
app.all(
'/graphql',
createHandler({
schema: schema,
- rootValue: root,
+ rootValue: root,
}),
);
app.listen(4000, () => {
-console.log('Running a GraphQL API server at localhost:4000/graphql');
+ console.log('Running a GraphQL API server at localhost:4000/graphql');
});
```
+
+
```js
import express from 'express';
import { createHandler } from 'graphql-http/lib/use/express';
@@ -368,12 +374,14 @@ const schema = new GraphQLSchema({
if (!fakeDatabase[id]) {
throw new Error('no message exists with id ' + id);
}
- return fakeDatabase[id] ? {
- id,
- content: fakeDatabase[id].content,
- author: fakeDatabase[id].author,
- } : null;
- }
+ return fakeDatabase[id]
+ ? {
+ id,
+ content: fakeDatabase[id].content,
+ author: fakeDatabase[id].author,
+ }
+ : null;
+ },
},
},
}),
@@ -395,7 +403,7 @@ const schema = new GraphQLSchema({
content: input.content,
author: input.author,
};
- }
+ },
},
updateMessage: {
type: Message,
@@ -414,7 +422,7 @@ const schema = new GraphQLSchema({
content: input.content,
author: input.author,
};
- }
+ },
},
},
}),
diff --git a/website/pages/docs/n1-dataloader.mdx b/website/pages/docs/n1-dataloader.mdx
index 00601ed783..e4d9a06840 100644
--- a/website/pages/docs/n1-dataloader.mdx
+++ b/website/pages/docs/n1-dataloader.mdx
@@ -5,8 +5,8 @@ title: Solving the N+1 Problem with DataLoader
# Solving the N+1 Problem with DataLoader
When building a 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,
+performance issues related to the N+1 problem: a pattern that
+results in many unnecessary database or service calls,
especially in nested query structures.
This guide explains what the N+1 problem is, why it's relevant in
@@ -54,16 +54,16 @@ could be grouped.
## Solving the problem with DataLoader
[`DataLoader`](https://github.com/graphql/dataloader) is a utility library designed
-to solve this problem. It batches multiple `.load(key)` calls into a single `batchLoadFn(keys)`
+to solve this problem. It batches multiple `.load(key)` calls into a single `batchLoadFn(keys)`
call and caches results during the life of a request. This means you can reduce redundant data
fetches and group related lookups into efficient operations.
To use `DataLoader` in a `graphql-js` server:
1. Create `DataLoader` instances for each request.
-2. Attach the instance to the `contextValue` passed to GraphQL execution. You can attach the
-loader when calling [`graphql()`](https://graphql.org/graphql-js/graphql/#graphql) directly, or
-when setting up a GraphQL HTTP server such as [express-graphql](https://github.com/graphql/express-graphql).
+2. Attach the instance to the `contextValue` passed to GraphQL execution. You can attach the
+ loader when calling [`graphql()`](https://graphql.org/graphql-js/graphql/#graphql) directly, or
+ when setting up a GraphQL HTTP server such as [express-graphql](https://github.com/graphql/express-graphql).
3. Use `.load(id)` in resolvers to fetch data through the loader.
### Example: Batching author lookups
@@ -136,14 +136,14 @@ additional fetches.
## Best practices
- Create a new `DataLoader` instance per request. This ensures that caching is scoped
-correctly and avoids leaking data between users.
+ correctly and avoids leaking data between users.
- Always return results in the same order as the input keys. This is required by the
-`DataLoader` contract. If a key is not found, return `null` or throw depending on
-your policy.
+ `DataLoader` contract. If a key is not found, return `null` or throw depending on
+ your policy.
- Keep batch functions focused. Each loader should handle a specific data access pattern.
- Use `.loadMany()` sparingly. While it's useful when you already have a list of IDs, it's
-typically not needed in field resolvers, since `.load()` already batches individual calls
-made within the same execution cycle.
+ typically not needed in field resolvers, since `.load()` already batches individual calls
+ made within the same execution cycle.
## Additional resources
diff --git a/website/pages/docs/nullability.mdx b/website/pages/docs/nullability.mdx
index 0e5ce6f0ff..01a79e45fb 100644
--- a/website/pages/docs/nullability.mdx
+++ b/website/pages/docs/nullability.mdx
@@ -17,10 +17,10 @@ and how to design schemas with nullability in mind.
In GraphQL, fields are nullable by default. This means if a resolver function
returns `null`, the result will include a `null` value unless the field is
-explicitly marked as non-nullable.
+explicitly marked as non-nullable.
-When a non-nullable field resolves to `null`, the GraphQL execution engine
-raises a runtime error and attempts to recover by replacing the nearest
+When a non-nullable field resolves to `null`, the GraphQL execution engine
+raises a runtime error and attempts to recover by replacing the nearest
nullable parent field with `null`. This behavior is known formally as "error
propagation" but more commonly as null bubbling.
@@ -33,11 +33,7 @@ GraphQL.js represents non-nullability using the `GraphQLNonNull` wrapper type.
By default, all fields are nullable:
```js
-import {
- GraphQLObjectType,
- GraphQLString,
- GraphQLNonNull,
-} from 'graphql';
+import { GraphQLObjectType, GraphQLString, GraphQLNonNull } from 'graphql';
const UserType = new GraphQLObjectType({
name: 'User',
@@ -61,7 +57,7 @@ You can also combine it with other types to create more
specific constraints. For example:
```js
-new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(UserType)))
+new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(UserType)));
```
This structure corresponds to [User!]! in SDL: a non-nullable list of non-null
@@ -74,9 +70,9 @@ GraphQL.js uses nullability rules to determine how to handle `null` values
at runtime:
- If a nullable field returns `null`, the result includes that field with
-a `null` value.
+ a `null` value.
- If a non-nullable field returns `null`, GraphQL throws an error and
-sets the nearest nullable parent field to `null`.
+ sets the nearest nullable parent field to `null`.
This bubbling behavior prevents partial data from being returned in cases where
a non-nullable guarantee is violated.
@@ -123,11 +119,11 @@ also less forgiving. When deciding whether to use `GraphQLNonNull`, keep
the following in mind:
- Use non-null types when a value is always expected. This reflects intent
-and reduces ambiguity for clients.
+ and reduces ambiguity for clients.
- Avoid aggressive use of non-null types in early schema versions. It limits
-your ability to evolve the API later.
+ your ability to evolve the API later.
- Be cautious of error bubbling. A `null` return from a deeply nested non-nullable
-field can affect large portions of the response.
+ field can affect large portions of the response.
### Versioning
@@ -204,32 +200,32 @@ const schema = new GraphQLSchema({ query: QueryType });
In this example, the `product` resolver returns an object with `name: null`.
Because the `name` field is non-nullable, GraphQL.js responds by
-nullifying the entire `product` field and appending a
+nullifying the entire `product` field and appending a
corresponding error to the response.
## Best practices
- Default to nullable. Start with nullable fields and introduce non-null
-constraints when data consistency is guaranteed.
+ constraints when data consistency is guaranteed.
- Express intent. Use non-null when a field must always be present for logical
-correctness.
+ correctness.
- Validate early. Add checks in resolvers to prevent returning `null` for
-non-null fields.
+ non-null fields.
- Consider error boundaries. Were an error to occur, where should it stop
bubbling?
- Watch for nesting. Distinguish between:
- - `[User!]` - nullable list of non-null users
- - `[User]!` - non-null list of nullable users
- - `[User!]!` - non-null list of non-null users
+ - `[User!]` - nullable list of non-null users
+ - `[User]!` - non-null list of nullable users
+ - `[User!]!` - non-null list of non-null users
## Additional resources
- [GraphQL Specification: Non-null](https://spec.graphql.org/draft/#sec-Non-Null):
-Defines the formal behavior of non-null types in the GraphQL type system and
-execution engine.
+ Defines the formal behavior of non-null types in the GraphQL type system and
+ execution engine.
- [Understanding GraphQL.js Errors](./graphql-errors): Explains
-how GraphQL.js propagates and formats execution-time errors.
+ how GraphQL.js propagates and formats execution-time errors.
- [Anatomy of a Resolver](./resolver-anatomy): Breaks down
-how resolvers work in GraphQL.js.
+ how resolvers work in GraphQL.js.
- [Constructing Types](./constructing-types): Shows how
-to define and compose types in GraphQL.js.
\ No newline at end of file
+ to define and compose types in GraphQL.js.
diff --git a/website/pages/docs/object-types.mdx b/website/pages/docs/object-types.mdx
index 961d34e4d4..4484a569ec 100644
--- a/website/pages/docs/object-types.mdx
+++ b/website/pages/docs/object-types.mdx
@@ -12,13 +12,16 @@ In GraphQL schema language, the way you define a new object type is the same way
+
```graphql
type Query {
rollDice(numDice: Int!, numSides: Int): [Int]
}
```
+
+
```js
import {
GraphQLObjectType,
@@ -26,7 +29,7 @@ import {
GraphQLInt,
GraphQLString,
GraphQLList,
- GraphQLFloat
+ GraphQLFloat,
} from 'graphql';
new GraphQLObjectType({
@@ -36,16 +39,17 @@ new GraphQLObjectType({
type: new GraphQLList(GraphQLFloat),
args: {
numDice: {
- type: new GraphQLNonNull(GraphQLInt)
+ type: new GraphQLNonNull(GraphQLInt),
},
numSides: {
- type: new GraphQLNonNull(GraphQLInt)
+ type: new GraphQLNonNull(GraphQLInt),
},
},
},
},
-})
+});
```
+
@@ -53,6 +57,7 @@ If we wanted to have more and more methods based on a random die over time, we c
+
```graphql
type RandomDie {
roll(numRolls: Int!): [Int]
@@ -65,6 +70,7 @@ type Query {
+
```js
import {
GraphQLObjectType,
@@ -73,7 +79,7 @@ import {
GraphQLString,
GraphQLList,
GraphQLFloat,
- GraphQLSchema
+ GraphQLSchema,
} from 'graphql';
const RandomDie = new GraphQLObjectType({
@@ -81,32 +87,32 @@ const RandomDie = new GraphQLObjectType({
fields: {
numSides: {
type: new GraphQLNonNull(GraphQLInt),
- resolve: function(die) {
+ resolve: function (die) {
return die.numSides;
- }
+ },
},
rollOnce: {
type: new GraphQLNonNull(GraphQLInt),
- resolve: function(die) {
+ resolve: function (die) {
return 1 + Math.floor(Math.random() * die.numSides);
- }
+ },
},
roll: {
type: new GraphQLList(GraphQLInt),
args: {
numRolls: {
- type: new GraphQLNonNull(GraphQLInt)
+ type: new GraphQLNonNull(GraphQLInt),
},
},
- resolve: function(die, { numRolls }) {
+ resolve: function (die, { numRolls }) {
const output = [];
for (let i = 0; i < numRolls; i++) {
output.push(1 + Math.floor(Math.random() * die.numSides));
}
return output;
- }
- }
- }
+ },
+ },
+ },
});
const schema = new GraphQLSchema({
@@ -117,19 +123,20 @@ const schema = new GraphQLSchema({
type: RandomDie,
args: {
numSides: {
- type: GraphQLInt
- }
+ type: GraphQLInt,
+ },
},
resolve: (_, { numSides }) => {
return {
numSides: numSides || 6,
- }
- }
- }
- }
- })
+ };
+ },
+ },
+ },
+ }),
});
```
+
@@ -165,6 +172,7 @@ For fields that don't use any arguments, you can use either properties on the ob
+
```graphql
type RandomDie {
numSides: Int!
@@ -176,8 +184,10 @@ type Query {
getDie(numSides: Int): RandomDie
}
```
+
+
```js
import {
GraphQLObjectType,
@@ -186,7 +196,7 @@ import {
GraphQLString,
GraphQLList,
GraphQLFloat,
- GraphQLSchema
+ GraphQLSchema,
} from 'graphql';
const RandomDie = new GraphQLObjectType({
@@ -194,17 +204,17 @@ const RandomDie = new GraphQLObjectType({
fields: {
numSides: {
type: new GraphQLNonNull(GraphQLInt),
- resolve: (die) => die.numSides
+ resolve: (die) => die.numSides,
},
rollOnce: {
type: new GraphQLNonNull(GraphQLInt),
- resolve: (die) => 1 + Math.floor(Math.random() * die.numSides)
+ resolve: (die) => 1 + Math.floor(Math.random() * die.numSides),
},
roll: {
type: new GraphQLList(GraphQLInt),
args: {
numRolls: {
- type: new GraphQLNonNull(GraphQLInt)
+ type: new GraphQLNonNull(GraphQLInt),
},
},
resolve: (die, { numRolls }) => {
@@ -213,9 +223,9 @@ const RandomDie = new GraphQLObjectType({
output.push(1 + Math.floor(Math.random() * die.numSides));
}
return output;
- }
- }
- }
+ },
+ },
+ },
});
const schema = new GraphQLSchema({
@@ -226,19 +236,20 @@ const schema = new GraphQLSchema({
type: RandomDie,
args: {
numSides: {
- type: GraphQLInt
- }
+ type: GraphQLInt,
+ },
},
resolve: (_, { numSides }) => {
return {
numSides: numSides || 6,
- }
- }
- }
- }
- })
+ };
+ },
+ },
+ },
+ }),
});
```
+
@@ -246,6 +257,7 @@ Putting this all together, here is some sample code that runs a server with this
+
```js
import express from 'express';
import { createHandler } from 'graphql-http/lib/use/express';
@@ -301,8 +313,10 @@ app.all(
app.listen(4000);
console.log('Running a GraphQL API server at localhost:4000/graphql');
```
+
+
```js
import express from 'express';
import { createHandler } from 'graphql-http/lib/use/express';
@@ -312,7 +326,7 @@ import {
GraphQLInt,
GraphQLList,
GraphQLFloat,
- GraphQLSchema
+ GraphQLSchema,
} from 'graphql';
const RandomDie = new GraphQLObjectType({
@@ -320,17 +334,17 @@ const RandomDie = new GraphQLObjectType({
fields: {
numSides: {
type: new GraphQLNonNull(GraphQLInt),
- resolve: (die) => die.numSides
+ resolve: (die) => die.numSides,
},
rollOnce: {
type: new GraphQLNonNull(GraphQLInt),
- resolve: (die) => 1 + Math.floor(Math.random() * die.numSides)
+ resolve: (die) => 1 + Math.floor(Math.random() * die.numSides),
},
roll: {
type: new GraphQLList(GraphQLInt),
args: {
numRolls: {
- type: new GraphQLNonNull(GraphQLInt)
+ type: new GraphQLNonNull(GraphQLInt),
},
},
resolve: (die, { numRolls }) => {
@@ -339,9 +353,9 @@ const RandomDie = new GraphQLObjectType({
output.push(1 + Math.floor(Math.random() * die.numSides));
}
return output;
- }
- }
- }
+ },
+ },
+ },
});
const schema = new GraphQLSchema({
@@ -352,17 +366,17 @@ const schema = new GraphQLSchema({
type: RandomDie,
args: {
numSides: {
- type: GraphQLInt
- }
+ type: GraphQLInt,
+ },
},
resolve: (_, { numSides }) => {
return {
numSides: numSides || 6,
- }
- }
- }
- }
- })
+ };
+ },
+ },
+ },
+ }),
});
const app = express();
@@ -375,6 +389,7 @@ app.all(
app.listen(4000);
console.log('Running a GraphQL API server at localhost:4000/graphql');
```
+
diff --git a/website/pages/docs/oneof-input-objects.mdx b/website/pages/docs/oneof-input-objects.mdx
index 8ac5bd7ac9..bf0b736241 100644
--- a/website/pages/docs/oneof-input-objects.mdx
+++ b/website/pages/docs/oneof-input-objects.mdx
@@ -14,6 +14,7 @@ can create this "exactly one option" constraint without sacrificing the strictly
+
```js
const schema = buildSchema(`
type Product {
@@ -38,8 +39,10 @@ const schema = buildSchema(`
}
`);
```
+
+
```js
const Product = new GraphQLObjectType({
name: 'Product',
@@ -84,7 +87,9 @@ const schema = new GraphQLSchema({
}),
});
```
+
+
It doesn't matter whether you have 2 or more inputs here, all that matters is
diff --git a/website/pages/docs/operation-complexity-controls.mdx b/website/pages/docs/operation-complexity-controls.mdx
index 9aff4dc67e..08282e0b85 100644
--- a/website/pages/docs/operation-complexity-controls.mdx
+++ b/website/pages/docs/operation-complexity-controls.mdx
@@ -2,12 +2,12 @@
title: Operation Complexity Controls
---
-import { Callout } from 'nextra/components'
+import { Callout } from 'nextra/components';
# Operation Complexity Controls
GraphQL gives clients a lot of flexibility to shape responses, but that
-flexibility can also introduce risk. Clients can request deeply nested fields or
+flexibility can also introduce risk. Clients can request deeply nested fields or
large volumes of data in a single operation. Without controls, these operations can slow
down your server or expose security vulnerabilities.
@@ -16,14 +16,15 @@ using static analysis. You'll learn how to estimate the cost
of an operation before execution and reject it if it exceeds a safe limit.
- In production, we recommend using [trusted documents](/docs/going-to-production#only-allow-trusted-documents)
- rather than analyzing arbitrary documents at runtime. Complexity analysis can still be
+ In production, we recommend using [trusted
+ documents](/docs/going-to-production#only-allow-trusted-documents) rather than
+ analyzing arbitrary documents at runtime. Complexity analysis can still be
useful at build time to catch expensive operations before they're deployed.
## Why complexity control matters
-GraphQL lets clients choose exactly what data they want. That flexibility is powerful,
+GraphQL lets clients choose exactly what data they want. That flexibility is powerful,
but it also makes it hard to predict the runtime cost of a query just by looking
at the schema.
@@ -59,14 +60,14 @@ No resolvers are called unless the operation passes these checks.
Fragment cycles or deep nesting can cause some complexity analyzers to perform
- poorly or get stuck. Always run complexity analysis after validation unless your analyzer
- explicitly handles cycles safely.
+ poorly or get stuck. Always run complexity analysis after validation unless
+ your analyzer explicitly handles cycles safely.
## Simple complexity calculation
There are several community-maintained tools for complexity analysis. The examples in this
-guide use [`graphql-query-complexity`](https://github.com/slicknode/graphql-query-complexity) as
+guide use [`graphql-query-complexity`](https://github.com/slicknode/graphql-query-complexity) as
an option, but we recommend choosing the approach that best fits your project.
Here's a basic example using its `simpleEstimator`, which assigns a flat cost to every field:
@@ -144,8 +145,8 @@ complexity of each child field.
Most validation steps don't have access to variable values. If your complexity
- calculation depends on variables (like `limit`), make sure it runs after validation, not
- as part of it.
+ calculation depends on variables (like `limit`), make sure it runs after
+ validation, not as part of it.
To evaluate the cost before execution, you can combine estimators like this:
@@ -198,7 +199,10 @@ to your GraphQL.js server. For example, `graphql-query-complexity` provides a
```js
import { graphql, specifiedRules, parse } from 'graphql';
-import { createComplexityRule, simpleEstimator } from 'graphql-query-complexity';
+import {
+ createComplexityRule,
+ simpleEstimator,
+} from 'graphql-query-complexity';
import { schema } from './schema.js';
const source = `
@@ -233,8 +237,9 @@ console.log(result);
```
- Only use complexity rules in validation if you're sure the analysis is cycle-safe.
- Otherwise, run complexity checks after validation and before execution.
+ Only use complexity rules in validation if you're sure the analysis is
+ cycle-safe. Otherwise, run complexity checks after validation and before
+ execution.
## Complexity in trusted environments
@@ -254,7 +259,7 @@ useful, just in a different way. You can run it at build time to:
- Account for list fields and abstract types, which can significantly increase cost.
- Avoid estimating complexity before validation unless you're confident in your tooling.
- Use complexity analysis as part of your layered security strategy, alongside depth limits,
-field guards, and authentication.
+ field guards, and authentication.
## Additional resources
diff --git a/website/pages/docs/passing-arguments.mdx b/website/pages/docs/passing-arguments.mdx
index 87e6a3f1f1..e2f9233ffd 100644
--- a/website/pages/docs/passing-arguments.mdx
+++ b/website/pages/docs/passing-arguments.mdx
@@ -18,13 +18,16 @@ Instead of hard coding "three", we might want a more general function that rolls
+
```graphql
type Query {
rollDice(numDice: Int!, numSides: Int): [Int]
}
```
+
+
```js
import express from 'express';
import { createHandler } from 'graphql-http/lib/use/express';
@@ -34,7 +37,7 @@ import {
GraphQLList,
GraphQLFloat,
GraphQLInt,
- GraphQLNonNull
+ GraphQLNonNull,
} from 'graphql';
const schema = new GraphQLSchema({
@@ -53,7 +56,7 @@ const schema = new GraphQLSchema({
output.push(1 + Math.floor(Math.random() * (numSides || 6)));
}
return output;
- }
+ },
},
},
}),
@@ -69,6 +72,7 @@ app.all(
app.listen(4000);
console.log('Running a GraphQL API server at localhost:4000/graphql');
```
+
@@ -155,13 +159,18 @@ The entire code for a server that hosts this `rollDice` API is:
+
```js
import express from 'express';
import { createHandler } from 'graphql-http/lib/use/express';
import { buildSchema } from 'graphql';
// Construct a schema, using GraphQL schema language
-const schema = buildSchema(/_ GraphQL _/ ` type Query { rollDice(numDice: Int!, numSides: Int): [Int] }`);
+const schema = buildSchema(/* GraphQL */ `
+ type Query {
+ rollDice(numDice: Int!, numSides: Int): [Int]
+ }
+`);
// The root provides a resolver function for each API endpoint
const root = {
@@ -185,8 +194,10 @@ app.all(
app.listen(4000);
console.log('Running a GraphQL API server at localhost:4000/graphql');
```
+
+
```js
import express from 'express';
import { createHandler } from 'graphql-http/lib/use/express';
@@ -197,7 +208,7 @@ import {
GraphQLString,
GraphQLList,
GraphQLFloat,
- GraphQLSchema
+ GraphQLSchema,
} from 'graphql';
// Construct a schema, using GraphQL schema language
@@ -209,10 +220,10 @@ const schema = new GraphQLSchema({
type: new GraphQLList(GraphQLFloat),
args: {
numDice: {
- type: new GraphQLNonNull(GraphQLInt)
+ type: new GraphQLNonNull(GraphQLInt),
},
numSides: {
- type: new GraphQLNonNull(GraphQLInt)
+ type: new GraphQLNonNull(GraphQLInt),
},
},
resolve: (_, { numDice, numSides }) => {
@@ -221,11 +232,11 @@ const schema = new GraphQLSchema({
output.push(1 + Math.floor(Math.random() * (numSides || 6)));
}
return output;
- }
+ },
},
},
- })
-})
+ }),
+});
const app = express();
app.all(
@@ -237,6 +248,7 @@ app.all(
app.listen(4000);
console.log('Running a GraphQL API server at localhost:4000/graphql');
```
+
diff --git a/website/pages/docs/resolver-anatomy.mdx b/website/pages/docs/resolver-anatomy.mdx
index 6799d021a0..28de4cd52a 100644
--- a/website/pages/docs/resolver-anatomy.mdx
+++ b/website/pages/docs/resolver-anatomy.mdx
@@ -4,32 +4,32 @@ title: Anatomy of a Resolver
# Anatomy of a Resolver
-In GraphQL.js, a resolver is a function that returns the value for a
-specific field in a schema. Resolvers connect a GraphQL query to the
+In GraphQL.js, a resolver is a function that returns the value for a
+specific field in a schema. Resolvers connect a GraphQL query to the
underlying data or logic needed to fulfill it.
-This guide breaks down the anatomy of a resolver, how GraphQL.js uses
+This guide breaks down the anatomy of a resolver, how GraphQL.js uses
them during query execution, and best practices for writing them effectively.
## What is a resolver?
-A resolver is responsible for returning the value for a specific field in a
-GraphQL query. During execution, GraphQL.js calls a resolver for each field,
-either using a custom function you provide or falling back to a default
+A resolver is responsible for returning the value for a specific field in a
+GraphQL query. During execution, GraphQL.js calls a resolver for each field,
+either using a custom function you provide or falling back to a default
behavior.
-If no resolver is provided, GraphQL.js tries to retrieve a property from the
-parent object that matches the field name. If the property is a function, it
-calls the function and uses the result. Otherwise, it returns the property
+If no resolver is provided, GraphQL.js tries to retrieve a property from the
+parent object that matches the field name. If the property is a function, it
+calls the function and uses the result. Otherwise, it returns the property
value directly.
-You can think of a resolver as a translator between the schema and the
-actual data. The schema defines what can be queried, while resolvers
+You can think of a resolver as a translator between the schema and the
+actual data. The schema defines what can be queried, while resolvers
determine how to fetch or compute the data at runtime.
## Resolver function signature
-When GraphQL.js executes a resolver, it calls the resolver function
+When GraphQL.js executes a resolver, it calls the resolver function
with four arguments:
```js
@@ -40,19 +40,19 @@ 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 (after resolving any
-lists). For root fields, it is the `rootValue` passed to GraphQL, which is often
-left `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`.
-- `context`: A shared object available to every resolver in an operation.
-It is commonly used to store per-request state like authentication
-information, database connections, or caching utilities.
+ query. For example, if a field is defined to accept an `id` argument, you can
+ access it as `args.id`.
+- `context`: A shared object available to every resolver in an operation.
+ It is commonly used to store per-request state like authentication
+ information, database connections, or caching utilities.
- `info`: Information about the current execution state, including
-the field name, path to the field from the root, the return type, the parent
-type, and the full schema. It is mainly useful for advanced scenarios such
-as query optimization or logging.
+ the field name, path to the field from the root, the return type, the parent
+ type, and the full schema. It is mainly useful for advanced scenarios such
+ as query optimization or logging.
Resolvers can use any combination of these arguments, depending on the needs
of the field they are resolving.
@@ -65,25 +65,25 @@ default resolver called `defaultFieldResolver`.
The default behavior is simple:
- It looks for a property on the `source` object that matches the name of
-the field.
+ the field.
- If the property exists and is a function, it calls the function and uses the
-result.
+ result.
- Otherwise, it returns the property value directly.
-This default resolution makes it easy to build simple schemas without
-writing custom resolvers for every field. For example, if your `source` object
-already contains fields with matching names, GraphQL.js can resolve them
+This default resolution makes it easy to build simple schemas without
+writing custom resolvers for every field. For example, if your `source` object
+already contains fields with matching names, GraphQL.js can resolve them
automatically.
-You can override the default behavior by specifying a `resolve` function when
-defining a field in the schema. This is necessary when the field’s value
-needs to be computed dynamically, fetched from an external service, or
+You can override the default behavior by specifying a `resolve` function when
+defining a field in the schema. This is necessary when the field’s value
+needs to be computed dynamically, fetched from an external service, or
otherwise requires custom logic.
## Writing a custom resolver
-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`
+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 {6-8}
@@ -100,8 +100,8 @@ const UserType = new GraphQLObjectType({
});
```
-Resolvers can be synchronous or asynchronous. If a resolver returns a
-Promise, GraphQL.js automatically waits for the Promise to resolve before
+Resolvers can be synchronous or asynchronous. If a resolver returns a
+Promise, GraphQL.js automatically waits for the Promise to resolve before
continuing execution:
```js
@@ -110,30 +110,30 @@ resolve(source, args, context) {
}
```
-Custom resolvers are often used to implement patterns such as batching,
-caching, or delegation. For example, a resolver might use a batching utility
-like DataLoader to fetch multiple related records efficiently, or delegate
+Custom resolvers are often used to implement patterns such as batching,
+caching, or delegation. For example, a resolver might use a batching utility
+like DataLoader to fetch multiple related records efficiently, or delegate
part of the query to another API or service.
## Best practices
When writing resolvers, it's important to keep them focused and maintainable:
-- Keep business logic separate. A resolver should focus on fetching or
-computing the value for a field, not on implementing business rules or
-complex workflows. Move business logic into separate service layers
-whenever possible.
-- Handle errors carefully. Resolvers should catch and handle errors
-appropriately, either by throwing GraphQL errors or returning `null` values
-when fields are nullable. Avoid letting unhandled errors crash the server.
-- Use context effectively. Store shared per-request information, such as
-authentication data or database connections, in the `context` object rather
-than passing it manually between resolvers.
-- Prefer batching over nested requests. For fields that trigger multiple
-database or API calls, use batching strategies to minimize round trips and
-improve performance. A common solution for batching in GraphQL is [dataloader](https://github.com/graphql/dataloader).
-- Keep resolvers simple. Aim for resolvers to be small, composable functions
-that are easy to read, test, and reuse.
-
-Following these practices helps keep your GraphQL server reliable, efficient,
-and easy to maintain as your schema grows.
\ No newline at end of file
+- Keep business logic separate. A resolver should focus on fetching or
+ computing the value for a field, not on implementing business rules or
+ complex workflows. Move business logic into separate service layers
+ whenever possible.
+- Handle errors carefully. Resolvers should catch and handle errors
+ appropriately, either by throwing GraphQL errors or returning `null` values
+ when fields are nullable. Avoid letting unhandled errors crash the server.
+- Use context effectively. Store shared per-request information, such as
+ authentication data or database connections, in the `context` object rather
+ than passing it manually between resolvers.
+- Prefer batching over nested requests. For fields that trigger multiple
+ database or API calls, use batching strategies to minimize round trips and
+ improve performance. A common solution for batching in GraphQL is [dataloader](https://github.com/graphql/dataloader).
+- Keep resolvers simple. Aim for resolvers to be small, composable functions
+ that are easy to read, test, and reuse.
+
+Following these practices helps keep your GraphQL server reliable, efficient,
+and easy to maintain as your schema grows.
diff --git a/website/pages/docs/running-an-express-graphql-server.mdx b/website/pages/docs/running-an-express-graphql-server.mdx
index 8768a76480..d47b0384a8 100644
--- a/website/pages/docs/running-an-express-graphql-server.mdx
+++ b/website/pages/docs/running-an-express-graphql-server.mdx
@@ -17,6 +17,7 @@ Let's modify our "hello world" example so that it's an API server rather than a
+
```javascript
import { buildSchema } from 'graphql';
import { createHandler } from 'graphql-http/lib/use/express';
@@ -46,10 +47,11 @@ app.all(
// Start the server at port
app.listen(4000);
console.log('Running a GraphQL API server at http://localhost:4000/graphql');
-
```
+
+
```javascript
import { GraphQLObjectType, GraphQLSchema, GraphQLString } from 'graphql';
import { createHandler } from 'graphql-http/lib/use/express';
@@ -60,9 +62,9 @@ const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
fields: {
- hello: {
+ hello: {
type: GraphQLString,
- resolve: () => 'Hello world!'
+ resolve: () => 'Hello world!',
},
},
}),
@@ -81,8 +83,8 @@ app.all(
// Start the server at port
app.listen(4000);
console.log('Running a GraphQL API server at http://localhost:4000/graphql');
-
```
+
diff --git a/website/pages/docs/scaling-graphql.mdx b/website/pages/docs/scaling-graphql.mdx
index 320ef23dcf..f22f9e0959 100644
--- a/website/pages/docs/scaling-graphql.mdx
+++ b/website/pages/docs/scaling-graphql.mdx
@@ -57,10 +57,7 @@ The following example stitches two subschemas into a single stitched schema:
import { stitchSchemas } from '@graphql-tools/stitch';
export const schema = stitchSchemas({
- subschemas: [
- { schema: userSchema },
- { schema: productSchema },
- ],
+ subschemas: [{ schema: userSchema }, { schema: productSchema }],
});
```
@@ -100,7 +97,7 @@ GraphQL.js, you'll need to:
- Implement resolvers for reference types
- Output a schema that conforms to a federation-compliant format
-Most federation tooling today is based on
+Most federation tooling today is based on
[Apollo Federation](https://www.apollographql.com/docs/graphos/schema-design/federated-schemas/federation) and [here you can find a list of Apollo Federation compatible gateways](https://the-guild.dev/graphql/hive/federation-gateway-audit).
However, other approaches exist:
@@ -116,11 +113,11 @@ independently under a shared contract.
The best structure for your GraphQL API depends on your team size, deployment model, and how your
schema is expected to grow.
-| Pattern | Best for | GraphQL.js support | Tooling required |
-|---|---|---|---|
-| Monolith | Default choice for most projects; simpler, faster, easier to reason about | Built-in | None |
-| Schema stitching | Aggregating services you control | External tooling required | `@graphql-tools/stitch`
-| Federation | Large enterprises; many teams contributing to a distributed graph independently | Manual implementation | Significant tooling and infrastructure |
+| Pattern | Best for | GraphQL.js support | Tooling required |
+| ---------------- | ------------------------------------------------------------------------------- | ------------------------- | -------------------------------------- |
+| Monolith | Default choice for most projects; simpler, faster, easier to reason about | Built-in | None |
+| Schema stitching | Aggregating services you control | External tooling required | `@graphql-tools/stitch` |
+| Federation | Large enterprises; many teams contributing to a distributed graph independently | Manual implementation | Significant tooling and infrastructure |
## Migration paths
@@ -149,12 +146,12 @@ experience to support it.
The following guidelines can help you choose and evolve your architecture over time:
- Start simple. If you're building a new API, a monolithic schema is usually the right place
-to begin. It's easier to reason about, test, and iterate on.
+ to begin. It's easier to reason about, test, and iterate on.
- Split only when needed. Don't reach for composition tools prematurely. Schema stitching or federation
-should be introduced in response to real organizational or scalability needs.
+ should be introduced in response to real organizational or scalability needs.
- Favor clarity over flexibility. Stitching and federation add power, but they also increase complexity.
-Make sure your team has the operational maturity to support the tooling and patterns involved.
+ Make sure your team has the operational maturity to support the tooling and patterns involved.
- Define ownership boundaries. Federation is most useful when teams need clear control over parts of
-the schema. Without strong ownership models, a federated graph can become harder to manage.
+ the schema. Without strong ownership models, a federated graph can become harder to manage.
- Consider alternatives. Not all use cases need stitching or federation. Sometimes, versioning, modular
-schema design, or schema delegation patterns within a monolith are sufficient.
+ schema design, or schema delegation patterns within a monolith are sufficient.
diff --git a/website/pages/docs/schema-evolution.mdx b/website/pages/docs/schema-evolution.mdx
index 5212f4a1d7..faeaf32c2e 100644
--- a/website/pages/docs/schema-evolution.mdx
+++ b/website/pages/docs/schema-evolution.mdx
@@ -111,11 +111,7 @@ This example fails a build on breaking changes and prints dangerous changes for
review.
```js
-import {
- BreakingChangeType,
- buildSchema,
- findSchemaChanges,
-} from 'graphql';
+import { BreakingChangeType, buildSchema, findSchemaChanges } from 'graphql';
import { readFile } from 'node:fs/promises';
const oldSchema = buildSchema(await readFile('schema-old.graphql', 'utf8'));
diff --git a/website/pages/docs/testing-approaches.mdx b/website/pages/docs/testing-approaches.mdx
index 6b30e92c12..8932f1903f 100644
--- a/website/pages/docs/testing-approaches.mdx
+++ b/website/pages/docs/testing-approaches.mdx
@@ -3,19 +3,19 @@ title: Testing Approaches
sidebarTitle: Testing Approaches
---
-import { Callout } from 'nextra/components'
+import { Callout } from 'nextra/components';
# Testing Approaches
-Testing is essential for building reliable GraphQL servers. But not every test
-gives you the same kind of feedback, and not every test belongs at every stage of development.
-This guide explains the differences between unit tests, integration tests, and
+Testing is essential for building reliable GraphQL servers. But not every test
+gives you the same kind of feedback, and not every test belongs at every stage of development.
+This guide explains the differences between unit tests, integration tests, and
end-to-end (E2E) tests, so you can choose the right approach for your project.
## Unit tests
-Unit tests focus on testing resolver functions in isolation. You run the resolver directly
-with mocked arguments and context. These tests do not involve your schema or run actual
+Unit tests focus on testing resolver functions in isolation. You run the resolver directly
+with mocked arguments and context. These tests do not involve your schema or run actual
GraphQL queries.
When you write unit tests, you’re checking that the resolver:
@@ -33,8 +33,8 @@ Unit tests are fast and provide tight feedback loops. They're especially useful
- Validating error handling and edge cases
- Refactoring resolver logic and needing immediate verification
-However, unit tests have limitations. Because you're mocking inputs and skipping the schema
-entirely, you won’t catch issues with how your schema connects to your resolver functions.
+However, unit tests have limitations. Because you're mocking inputs and skipping the schema
+entirely, you won’t catch issues with how your schema connects to your resolver functions.
There's also a risk of false positives if your mocks drift from real usage over time.
### Example: Unit test for a resolver
@@ -48,12 +48,12 @@ expect(result).toEqual(expectedResult);
## Integration tests
-Integration tests go a step further by testing resolvers and the schema together.
+Integration tests go a step further by testing resolvers and the schema together.
You can run actual GraphQL queries and verify the end-to-end behavior within your
application's boundaries.
-You can use the `graphql()` function from the GraphQL package, no HTTP server
-needed. With the `graphql()` function, you can test the full flow: request > schema >
+You can use the `graphql()` function from the GraphQL package, no HTTP server
+needed. With the `graphql()` function, you can test the full flow: request > schema >
resolver > data source (mocked or real).
Integration tests confirm that:
@@ -82,8 +82,8 @@ Trade-offs to consider:
- If you're preparing to onboard frontend clients or exposing your API to consumers,
- integration tests catch mismatches early before they reach production.
+If you're preparing to onboard frontend clients or exposing your API to consumers,
+integration tests catch mismatches early before they reach production.
@@ -128,7 +128,7 @@ E2E tests simulate production-like conditions and are especially valuable when:
- You need to test network-level behaviors such as timeouts and error handling
- You're coordinating multiple services together
-E2E tests offer high confidence but come at the cost of speed and complexity.
+E2E tests offer high confidence but come at the cost of speed and complexity.
They’re best used sparingly for critical paths, not as your primary testing approach.
Trade-offs to consider:
@@ -140,8 +140,8 @@ Trade-offs to consider:
- In the following guides, we focus on unit and integration tests. E2E tests are
- valuable, but they require different tooling and workflows.
+In the following guides, we focus on unit and integration tests. E2E tests are
+valuable, but they require different tooling and workflows.
@@ -149,14 +149,14 @@ Trade-offs to consider:
Unit and integration tests are complementary, not competing.
-| Factor | Unit tests | Integration tests |
-|:-------|:--------------------|:-------------------------|
-| Speed | Fast | Moderate |
-| Scope | Resolver logic only | Schema and resolver flow |
-| Setup | Minimal | Schema, mocks, context |
+| Factor | Unit tests | Integration tests |
+| :------- | :---------------------------------------------- | :---------------------------------------- |
+| Speed | Fast | Moderate |
+| Scope | Resolver logic only | Schema and resolver flow |
+| Setup | Minimal | Schema, mocks, context |
| Best for | Isolated business logic, fast development loops | Verifying resolver wiring, operation flow |
-Start with unit tests when building new features, then layer in integration tests
+Start with unit tests when building new features, then layer in integration tests
to validate schema wiring and catch regressions as your API grows.
## Choose a testing approach
diff --git a/website/pages/docs/testing-best-practices.mdx b/website/pages/docs/testing-best-practices.mdx
index 94f04dae5e..cb7964a89c 100644
--- a/website/pages/docs/testing-best-practices.mdx
+++ b/website/pages/docs/testing-best-practices.mdx
@@ -5,24 +5,24 @@ sidebarTitle: Testing Best Practices
# Testing Best Practices
-As your GraphQL server grows, so does the risk of regressions, inconsistencies,
-and slow development feedback. A thoughtful testing strategy helps you catch
-problems early and ship with confidence—without overwhelming your team with
+As your GraphQL server grows, so does the risk of regressions, inconsistencies,
+and slow development feedback. A thoughtful testing strategy helps you catch
+problems early and ship with confidence—without overwhelming your team with
redundant or brittle tests.
-This guide outlines practical testing patterns for GraphQL servers,
-including schema safety, test coverage, data management, performance, and
+This guide outlines practical testing patterns for GraphQL servers,
+including schema safety, test coverage, data management, performance, and
continuous integration.
## Schema stability
-Your schema is a contract with every client and consumer. Changes should
+Your schema is a contract with every client and consumer. Changes should
be intentional and reviewed.
### Best practices
-- Use snapshot tests to catch unintended schema changes
- - Tool: [`jest-serializer-graphql-schema`](https://www.npmjs.com/package/jest-serializer-graphql-schema)
+- Use snapshot tests to catch unintended schema changes
+ - Tool: [`jest-serializer-graphql-schema`](https://www.npmjs.com/package/jest-serializer-graphql-schema)
- Example:
```ts
expect(schema).toMatchSnapshot();
@@ -36,33 +36,33 @@ be intentional and reviewed.
- Apollo Rover
- GraphQL Hive
- Require review or approval for breaking changes
-- Treat schema changes like semver: breaking changes should be explicit and
-intentional
+- Treat schema changes like semver: breaking changes should be explicit and
+ intentional
## Focus test coverage
-You don’t need 100% coverage, you need meaningful coverage. Prioritize
+You don’t need 100% coverage, you need meaningful coverage. Prioritize
behavior that matters.
### Best practices
- Test high-value paths:
- - Business logic and custom resolver behavior
- - Error cases: invalid input, auth failures, fallback logic
- - Nullable fields and partial success cases
- - Integration between fields, arguments, and data dependencies
+ - Business logic and custom resolver behavior
+ - Error cases: invalid input, auth failures, fallback logic
+ - Nullable fields and partial success cases
+ - Integration between fields, arguments, and data dependencies
- Coverage strategy:
- - Unit test resolvers with significant logic
- - Integration test operations end-to-end
- - Avoid duplicating tests across layers
- - Use tools to identify gaps:
- - `graphql-coverage`
- - Jest `--coverage`
- - Static analysis for unused fields/resolvers
+ - Unit test resolvers with significant logic
+ - Integration test operations end-to-end
+ - Avoid duplicating tests across layers
+ - Use tools to identify gaps:
+ - `graphql-coverage`
+ - Jest `--coverage`
+ - Static analysis for unused fields/resolvers
## Managing test data
-Clean, flexible test data makes your tests easier to write, read, and
+Clean, flexible test data makes your tests easier to write, read, and
maintain.
### Best practices
@@ -77,18 +77,18 @@ maintain.
- Share fixtures:
- ```ts
- export function createTestContext(overrides = {}) {
- return {
- db: { findUser: jest.fn() },
- ...overrides,
- };
- }
- ```
+ ```ts
+ export function createTestContext(overrides = {}) {
+ return {
+ db: { findUser: jest.fn() },
+ ...overrides,
+ };
+ }
+ ```
- Keep test data small and expressive
-- Avoid coupling test data to real database structures
-unless explicitly testing integration
+- Avoid coupling test data to real database structures
+ unless explicitly testing integration
## Keep tests fast and isolated
@@ -114,17 +114,17 @@ Tests are only useful if they run consistently and early.
### Best practices
- Run tests on every push or PR:
- - Lint GraphQL files and scalars
- - Run resolver and operation tests
- - Validate schema via snapshot or diff
+ - Lint GraphQL files and scalars
+ - Run resolver and operation tests
+ - Validate schema via snapshot or diff
- Fail fast:
- - Break the build on schema snapshot diffs
- - Block breaking changes without a migration plan
+ - Break the build on schema snapshot diffs
+ - Block breaking changes without a migration plan
- Use GitHub annotations or reporters to surface failures in PRs
## Lint your schema
-Testing behavior is only the start. Clean, consistent schemas are
+Testing behavior is only the start. Clean, consistent schemas are
easier to maintain and consume.
Use schema linting to enforce:
diff --git a/website/pages/docs/testing-graphql-servers.mdx b/website/pages/docs/testing-graphql-servers.mdx
index 03dfbf15a3..dbad765847 100644
--- a/website/pages/docs/testing-graphql-servers.mdx
+++ b/website/pages/docs/testing-graphql-servers.mdx
@@ -26,7 +26,7 @@ behaves as expected.
## Risks of schema-first development
Schema-first development starts with designing your API upfront and letting
-the schema drive implementation. This is a solid foundation, but treating
+the schema drive implementation. This is a solid foundation, but treating
the schema as the only source of truth creates risks as your API evolves.
For example, if you rename a field, any client still expecting the original name
@@ -45,7 +45,7 @@ you can catch breaking changes before they reach production. A strong testing st
A correct schema doesn't guarantee safe execution. Resolvers are still a risk surface. Resolvers connect the schema to your business logic and data sources. They decide how data is fetched, transformed, and returned to clients.
-Resolver errors are dangerous because failures often return `null` in part of the response, without failing the entire operation. Errors at the resolver level
+Resolver errors are dangerous because failures often return `null` in part of the response, without failing the entire operation. Errors at the resolver level
don't necessarily break the whole response. Clients may receive incomplete data without realizing something went wrong. Tests should assert on complete and correct responses, not just that there was a response.
Unit tests of resolver ensure:
@@ -64,7 +64,7 @@ example:
- An unavailable database can cause the resolver to fail.
- A slow third-party API can lead to timeouts.
- An external service returning incomplete data can result in `null` values or
-errors in your response.
+ errors in your response.
APIs should fail in predictable ways. Good tests don't just check happy paths, they simulate timeouts, network failures, corrupted data, and empty responses. Building these scenarios into your testing strategy helps you catch issues early and keep your API reliable.
@@ -75,4 +75,4 @@ Beyond simulating failures, consider testing resilience patterns like retries or
- Learn different [testing approaches](./testing-approaches.mdx) to choose the right strategy for your project.
- Explore how to [test operations](./testing-operations.mdx) without running a server.
- Understand how to [test resolvers](./testing-resolvers.mdx) to catch logic errors early.
-- Apply [best practices](./testing-best-practices.mdx) to scale testing to production.
\ No newline at end of file
+- Apply [best practices](./testing-best-practices.mdx) to scale testing to production.
diff --git a/website/pages/docs/testing-operations.mdx b/website/pages/docs/testing-operations.mdx
index 02df6f5cbf..5f079daf2a 100644
--- a/website/pages/docs/testing-operations.mdx
+++ b/website/pages/docs/testing-operations.mdx
@@ -5,33 +5,33 @@ sidebarTitle: Testing Operations
# Testing Operations
-Integration tests are the most effective way to test GraphQL operations.
-These tests run actual queries and mutations against your schema and
-resolvers to verify the full execution path. They validate how
-operations interact with the schema, resolver logic, and any
+Integration tests are the most effective way to test GraphQL operations.
+These tests run actual queries and mutations against your schema and
+resolvers to verify the full execution path. They validate how
+operations interact with the schema, resolver logic, and any
connected systems.
-Compared to unit tests, which focus on isolated resolver functions,
-integration tests exercise the full request flow. This makes them
-ideal for catching regressions in schema design, argument handling,
+Compared to unit tests, which focus on isolated resolver functions,
+integration tests exercise the full request flow. This makes them
+ideal for catching regressions in schema design, argument handling,
nested resolution, and interaction with data sources.
-To run integration tests, you don’t need a running server.
-Instead, use the `graphql()` function from `graphql-js`, which
+To run integration tests, you don’t need a running server.
+Instead, use the `graphql()` function from `graphql-js`, which
directly executes operations against a schema.
Integration testing for operations includes the following steps:
-1. **Build an executable schema:** Use `GraphQLSchema` or a schema
-construction utility like `makeExecutableSchema()`.
-2. **Provide resolver implementations:** Use real or mocked resolvers
-depending on what you're testing.
-3. **Set up a context if needed:** Include things like authorization tokens,
-data loaders, or database access in the context.
-4. **Call `graphql()` with your operation:** Pass the schema, query or mutation,
-optional variables, and context.
-5. **Validate the result:** Assert that the `data` is correct and `errors`
-is either `undefined` or matches expected failure cases.
+1. **Build an executable schema:** Use `GraphQLSchema` or a schema
+ construction utility like `makeExecutableSchema()`.
+2. **Provide resolver implementations:** Use real or mocked resolvers
+ depending on what you're testing.
+3. **Set up a context if needed:** Include things like authorization tokens,
+ data loaders, or database access in the context.
+4. **Call `graphql()` with your operation:** Pass the schema, query or mutation,
+ optional variables, and context.
+5. **Validate the result:** Assert that the `data` is correct and `errors`
+ is either `undefined` or matches expected failure cases.
## What you can test with operations
@@ -43,7 +43,7 @@ Use integration tests to verify:
- Error states and nullability
- Real vs. mocked resolver behavior
-These tests help ensure your GraphQL operations behave as expected across a wide
+These tests help ensure your GraphQL operations behave as expected across a wide
range of scenarios.
## Writing tests
@@ -100,8 +100,8 @@ const result = await graphql({
### Nested queries
-Nested queries validate how parent and child resolvers interact. This ensures
-the response shape aligns with your schema and the data flows correctly through
+Nested queries validate how parent and child resolvers interact. This ensures
+the response shape aligns with your schema and the data flows correctly through
resolvers:
```ts
@@ -130,7 +130,7 @@ When validating results, test both data and errors:
- Use `toEqual` for strict matches.
- Use `toMatchObject` for partial structure checks.
- Use `toBeUndefined()` or `toHaveLength(n)` for specific validations.
-- For large results, consider using snapshot tests.
+- For large results, consider using snapshot tests.
You can also test failure modes:
@@ -152,15 +152,15 @@ expect(result.data?.user).toBeNull();
## Using real data sources vs. mocked resolvers
-You can run integration tests against real or mocked resolvers. The
+You can run integration tests against real or mocked resolvers. The
right approach depends on what you're testing.
-| Approach | Pros | Cons | Setup |
-|----------|------|------|-------|
-| Real data sources | Catches real bugs, validates resolver integration and schema usage | Slower, needs data setup and teardown | Use in-memory DB or test DB, reset state between tests |
-| Mocked resolvers | Fast, controlled, ideal for schema validation | Doesn’t catch real resolver bugs | Stub resolvers or inject mocks into context or resolver |
+| Approach | Pros | Cons | Setup |
+| ----------------- | ------------------------------------------------------------------ | ------------------------------------- | ------------------------------------------------------- |
+| Real data sources | Catches real bugs, validates resolver integration and schema usage | Slower, needs data setup and teardown | Use in-memory DB or test DB, reset state between tests |
+| Mocked resolvers | Fast, controlled, ideal for schema validation | Doesn’t catch real resolver bugs | Stub resolvers or inject mocks into context or resolver |
-Use mocked resolvers when you're testing schema shape, field availability, or
-operation structure in isolation. Use real data sources when testing
-application logic, integration with databases, or when preparing for
-production-like behavior.
\ No newline at end of file
+Use mocked resolvers when you're testing schema shape, field availability, or
+operation structure in isolation. Use real data sources when testing
+application logic, integration with databases, or when preparing for
+production-like behavior.
diff --git a/website/pages/docs/testing-resolvers.mdx b/website/pages/docs/testing-resolvers.mdx
index ff310443f9..2135478bee 100644
--- a/website/pages/docs/testing-resolvers.mdx
+++ b/website/pages/docs/testing-resolvers.mdx
@@ -5,9 +5,9 @@ sidebarTitle: Testing Resolvers
# Testing Resolvers
-Resolvers are the core of GraphQL execution. They bridge the schema with
-your application logic and data sources. Unit testing resolvers helps you
-validate the behavior of individual resolver functions in isolation, without
+Resolvers are the core of GraphQL execution. They bridge the schema with
+your application logic and data sources. Unit testing resolvers helps you
+validate the behavior of individual resolver functions in isolation, without
needing to run GraphQL operations or construct a full schema.
Resolvers are good candidates for unit testing when they:
@@ -17,16 +17,16 @@ Resolvers are good candidates for unit testing when they:
- Call external services or APIs
- Perform transformations or mappings
-Unit tests for resolvers are fast, focused, and easy to debug.
-They help you verify logic at the function level before wiring everything
+Unit tests for resolvers are fast, focused, and easy to debug.
+They help you verify logic at the function level before wiring everything
together in integration tests.
-> **Tip:** Keep resolvers thin. Move heavy logic into service functions or
-utilities that can also be tested independently.
+> **Tip:** Keep resolvers thin. Move heavy logic into service functions or
+> utilities that can also be tested independently.
## Setup
-You don't need a schema or GraphQL server to test resolvers. You just need
+You don't need a schema or GraphQL server to test resolvers. You just need
a test runner and a function.
### Test runners
@@ -34,30 +34,31 @@ a test runner and a function.
You can use any JavaScript or TypeScript test runner. Two popular options are:
- `node:test` (built-in)
- - Native test runner in Node.js 18 and later
- - Use `--experimental-strip-types` if you're using TypeScript without
+ - Native test runner in Node.js 18 and later
+ - Use `--experimental-strip-types` if you're using TypeScript without
a transpiler
- - Minimal setup
+ - Minimal setup
```bash
node --test --experimental-strip-types
```
+
- Jest
- - Widely used across JavaScript applications
- - Built-in support for mocks, timers, and coverage
- - Better ecosystem support for mocking and configuration
+ - Widely used across JavaScript applications
+ - Built-in support for mocks, timers, and coverage
+ - Better ecosystem support for mocking and configuration
-Choose the runner that best fits your tooling and workflow.
+Choose the runner that best fits your tooling and workflow.
Both options support testing resolvers effectively.
## Writing resolver tests
-Unit tests for resolvers treat the resolver as a plain function.
+Unit tests for resolvers treat the resolver as a plain function.
You provide the `args`, `context`, and `info`, then assert on the result.
### Basic resolver function test
-You do not need the GraphQL schema or `graphql()` to write a basic resolver
+You do not need the GraphQL schema or `graphql()` to write a basic resolver
function test, just call the resolver directly:
```ts
@@ -67,7 +68,9 @@ function getUser(_, { id }, context) {
test('returns the expected user', async () => {
const context = {
- db: { findUserById: jest.fn().mockResolvedValue({ id: '1', name: 'Alice' }) },
+ db: {
+ findUserById: jest.fn().mockResolvedValue({ id: '1', name: 'Alice' }),
+ },
};
const result = await getUser(null, { id: '1' }, context);
@@ -94,7 +97,7 @@ test('resolves a user from async function', async () => {
### Error handling
-Resolvers often throw errors intentionally. Use `expect(...).rejects` to
+Resolvers often throw errors intentionally. Use `expect(...).rejects` to
test these cases:
```ts
@@ -103,9 +106,9 @@ test('throws an error for missing user', async () => {
db: { findUserById: jest.fn().mockResolvedValue(null) },
};
- await expect(getUser(null, { id: 'missing' }, context))
- .rejects
- .toThrow('User not found');
+ await expect(getUser(null, { id: 'missing' }, context)).rejects.toThrow(
+ 'User not found',
+ );
});
```
@@ -113,7 +116,7 @@ Also consider testing custom error classes or error extensions.
### Custom scalars
-Custom scalars often include serialization, parsing, and validation logic.
+Custom scalars often include serialization, parsing, and validation logic.
You can test them directly:
```ts
@@ -124,8 +127,9 @@ test('parses ISO string to Date', () => {
});
test('serializes Date to ISO string', () => {
- expect(GraphQLDate.serialize(new Date('2023-10-10')))
- .toBe('2023-10-10T00:00:00.000Z');
+ expect(GraphQLDate.serialize(new Date('2023-10-10'))).toBe(
+ '2023-10-10T00:00:00.000Z',
+ );
});
```
@@ -133,11 +137,15 @@ You can also test how your resolver behaves when working with scalar values:
```ts
test('returns a serialized date string', async () => {
- const result = await getPost(null, { id: '1' }, {
- db: {
- findPostById: () => ({ createdAt: new Date('2023-10-10') }),
+ const result = await getPost(
+ null,
+ { id: '1' },
+ {
+ db: {
+ findPostById: () => ({ createdAt: new Date('2023-10-10') }),
+ },
},
- });
+ );
expect(result.createdAt).toBe('2023-10-10T00:00:00.000Z');
});
@@ -150,7 +158,7 @@ test('returns a serialized date string', async () => {
Resolvers often rely on a `context` object. In tests, treat it as an injected
dependency, not a global.
-Inject mock services, database clients, or loaders into `context`. This pattern
+Inject mock services, database clients, or loaders into `context`. This pattern
makes resolvers easy to isolate and test:
```ts
@@ -163,15 +171,15 @@ const context = {
### Mocking vs. real data
-Use mocks to unit test logic without external systems. If you're testing logic
-that depends on specific external behavior, use a stub or fake—but avoid hitting
-real services in unit tests. Unit tests should not make real database or API
+Use mocks to unit test logic without external systems. If you're testing logic
+that depends on specific external behavior, use a stub or fake—but avoid hitting
+real services in unit tests. Unit tests should not make real database or API
calls. Save real data for integration tests.
### Testing resolver-level batching
If you use `DataLoader` or a custom batching layer, test that batching works as
-expected:
+expected:
```ts
const userLoader = {
@@ -190,4 +198,4 @@ expect(userLoader.load).toHaveBeenCalledTimes(1);
```
You can also simulate timing conditions by resolving batches manually
-or using fake timers in Jest.
\ No newline at end of file
+or using fake timers in Jest.
diff --git a/website/pages/docs/tracing-channels.mdx b/website/pages/docs/tracing-channels.mdx
index 92c6707d66..bc514c27ab 100644
--- a/website/pages/docs/tracing-channels.mdx
+++ b/website/pages/docs/tracing-channels.mdx
@@ -73,15 +73,15 @@ overhead and no error.
## The Channels
-| Channel | Fires for | Context type |
-| --- | --- | --- |
-| `graphql:parse` | Each `parse()` call | [`GraphQLParseContext`](/api-v17/graphql#graphqlparsecontext) |
-| `graphql:validate` | Each `validate()` call | [`GraphQLValidateContext`](/api-v17/graphql#graphqlvalidatecontext) |
-| `graphql:execute` | Each `execute()` / `graphql()` operation | [`GraphQLExecuteContext`](/api-v17/graphql#graphqlexecutecontext) |
-| `graphql:execute:variableCoercion` | Variable coercion within an operation | [`GraphQLExecuteVariableCoercionContext`](/api-v17/graphql#graphqlexecutevariablecoercioncontext) |
+| Channel | Fires for | Context type |
+| ---------------------------------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
+| `graphql:parse` | Each `parse()` call | [`GraphQLParseContext`](/api-v17/graphql#graphqlparsecontext) |
+| `graphql:validate` | Each `validate()` call | [`GraphQLValidateContext`](/api-v17/graphql#graphqlvalidatecontext) |
+| `graphql:execute` | Each `execute()` / `graphql()` operation | [`GraphQLExecuteContext`](/api-v17/graphql#graphqlexecutecontext) |
+| `graphql:execute:variableCoercion` | Variable coercion within an operation | [`GraphQLExecuteVariableCoercionContext`](/api-v17/graphql#graphqlexecutevariablecoercioncontext) |
| `graphql:execute:rootSelectionSet` | Root selection set execution (also once per emitted subscription event) | [`GraphQLExecuteRootSelectionSetContext`](/api-v17/graphql#graphqlexecuterootselectionsetcontext) |
-| `graphql:subscribe` | Each `subscribe()` setup | [`GraphQLSubscribeContext`](/api-v17/graphql#graphqlsubscribecontext) |
-| `graphql:resolve` | Each field resolver invocation | [`GraphQLResolveContext`](/api-v17/graphql#graphqlresolvecontext) |
+| `graphql:subscribe` | Each `subscribe()` setup | [`GraphQLSubscribeContext`](/api-v17/graphql#graphqlsubscribecontext) |
+| `graphql:resolve` | Each field resolver invocation | [`GraphQLResolveContext`](/api-v17/graphql#graphqlresolvecontext) |
`graphql:resolve` fires for every resolved field, including fields served by
@@ -154,18 +154,18 @@ stays entered across the full async lifecycle. This is what lets a tracer open
a span in `start` and close it in `asyncEnd`.
- A subscriber that attaches partway through an in-flight operation will not
- see a matching `start`, and any `AsyncLocalStorage` context it expects will be
+ A subscriber that attaches partway through an in-flight operation will not see
+ a matching `start`, and any `AsyncLocalStorage` context it expects will be
missing. Attach subscribers before the operations you want to observe.
- For incremental delivery (`@defer` and `@stream`), `graphql:execute`
- completes when the initial result is ready, not when the deferred and
- streamed payloads finish. Those later payloads are not `graphql:execute`
- events; the deferred fields still fire `graphql:resolve` as they execute. Do
- not treat the `graphql:execute` duration as the full request lifetime for
- incremental responses.
+ For incremental delivery (`@defer` and `@stream`), `graphql:execute` completes
+ when the initial result is ready, not when the deferred and streamed payloads
+ finish. Those later payloads are not `graphql:execute` events; the deferred
+ fields still fire `graphql:resolve` as they execute. Do not treat the
+ `graphql:execute` duration as the full request lifetime for incremental
+ responses.
## How the Channels Fit Together
@@ -262,7 +262,7 @@ each handler) is what keeps the asynchronous path from being counted twice.
## Tracing Subscriptions
-`graphql:subscribe` wraps the subscription *setup*: building the event source
+`graphql:subscribe` wraps the subscription _setup_: building the event source
from the subscription's root field. The result on success is the response
stream.
diff --git a/website/pages/docs/type-generation.mdx b/website/pages/docs/type-generation.mdx
index fdcaf8e3a4..f8ebb280ee 100644
--- a/website/pages/docs/type-generation.mdx
+++ b/website/pages/docs/type-generation.mdx
@@ -5,15 +5,15 @@ sidebarTitle: Type Generation
# Type Generation for GraphQL
-Writing a GraphQL server in JavaScript or TypeScript often involves managing complex
-types. As your API grows, keeping these types accurate and aligned with your schema
+Writing a GraphQL server in JavaScript or TypeScript often involves managing complex
+types. As your API grows, keeping these types accurate and aligned with your schema
becomes increasingly difficult.
-Type generation tools automate this process. Instead of manually defining or maintaining
-TypeScript types for your schema and operations, these tools can generate them for you.
+Type generation tools automate this process. Instead of manually defining or maintaining
+TypeScript types for your schema and operations, these tools can generate them for you.
This improves safety, reduces bugs, and makes development easier to scale.
-This guide walks through common type generation workflows for projects using
+This guide walks through common type generation workflows for projects using
`graphql-js`, including when and how to use them effectively.
## Why use type generation?
@@ -43,7 +43,7 @@ functions return values that match their expected shapes.
However, code-first development has tradeoffs:
- You won't get automatic type definitions for your resolvers unless you generate
-them manually or infer them through wrappers.
+ them manually or infer them through wrappers.
- Schema documentation, testing, and tool compatibility may require you to provide
a description of the schema in SDL first.
@@ -69,7 +69,7 @@ In a schema-first workflow, your GraphQL schema is written in SDL, for example,
or `.gql` (discouraged) files. This serves as the source of truth for your server. This approach
emphasizes clarity because your schema is defined independently from your business logic.
-Schema-first development pairs well with type generation because the schema is
+Schema-first development pairs well with type generation because the schema is
serializable and can be directly used by tools like [GraphQL Code Generator](https://the-guild.dev/graphql/codegen).
With a schema-first workflow, you can:
@@ -89,7 +89,7 @@ npm install graphql @graphql-codegen/cli @eddeee888/gcg-typescript-resolver-file
This scoped package is published by a community maintainer and is widely used for GraphQL server
type generation.
-We recommend using the [Server Preset](https://www.npmjs.com/package/@eddeee888/gcg-typescript-resolver-files) for a
+We recommend using the [Server Preset](https://www.npmjs.com/package/@eddeee888/gcg-typescript-resolver-files) for a
managed workflow. It automatically generates types and files based on your schema without needing extra plugin setup.
The Server Preset generates:
@@ -114,14 +114,14 @@ This setup expects your schema is split into modules to improve readability and
Here's an example `codegen.ts` file using the Server Preset:
```ts filename="codegen.ts"
-import type { CodegenConfig } from "@graphql-codegen/cli";
-import { defineConfig } from "@eddeee888/gcg-typescript-resolver-files";
+import type { CodegenConfig } from '@graphql-codegen/cli';
+import { defineConfig } from '@eddeee888/gcg-typescript-resolver-files';
const config: CodegenConfig = {
- schema: "src/**/schema.graphql",
+ schema: 'src/**/schema.graphql',
generates: {
- "src/schema": defineConfig({
- resolverGeneration: "minimal",
+ 'src/schema': defineConfig({
+ resolverGeneration: 'minimal',
}),
},
};
@@ -138,9 +138,9 @@ npx graphql-codegen
This creates resolver files like:
```ts filename="src/schema/user/resolvers/Query/user.ts"
-import type { QueryResolvers } from "./../../../types.generated";
+import type { QueryResolvers } from './../../../types.generated';
-export const user: NonNullable = async (
+export const user: NonNullable = async (
_parent,
_arg,
_ctx,
@@ -153,7 +153,7 @@ The user query resolver is typed to ensure that the user resolver expects an id
User, giving you confidence and autocomplete while implementing your server logic, which may look like this:
```ts filename="src/schema/user/resolvers/Query/user.ts"
-export const user: NonNullable = async (
+export const user: NonNullable = async (
parent,
args,
context,
@@ -166,8 +166,8 @@ See the official [Server Preset guide](https://the-guild.dev/graphql/codegen/doc
## Generating operation types
-In addition to resolver types, you can generate types for GraphQL operations such as queries, mutations, and
-fragments. This is especially useful for shared integration tests or client logic that needs to match the schema
+In addition to resolver types, you can generate types for GraphQL operations such as queries, mutations, and
+fragments. This is especially useful for shared integration tests or client logic that needs to match the schema
precisely.
To get started, install the required packages:
@@ -184,17 +184,17 @@ We recommend using the GraphQL Code Generator [Client Preset](https://the-guild.
Here's an example configuration using the Client Preset:
```ts filename="codegen.ts"
-import type { CodegenConfig } from "@graphql-codegen/cli";
+import type { CodegenConfig } from '@graphql-codegen/cli';
const config: CodegenConfig = {
- schema: "src/**/schema.graphql",
- documents: ["src/**/*.ts"],
+ schema: 'src/**/schema.graphql',
+ documents: ['src/**/*.ts'],
ignoreNoDocuments: true,
generates: {
- "./src/graphql/": {
- preset: "client",
+ './src/graphql/': {
+ preset: 'client',
config: {
- documentMode: "string",
+ documentMode: 'string',
},
},
},
@@ -209,11 +209,11 @@ To keep generated types up to date as you edit your code, run the generator in w
npx graphql-codegen --config codegen.ts --watch
```
-Once generated, import the `graphql` function from `src/graphql/` to write GraphQL operations
+Once generated, import the `graphql` function from `src/graphql/` to write GraphQL operations
directly in your TypeScript files:
```ts filename="src/index.ts"
-import { graphql } from "./graphql";
+import { graphql } from './graphql';
const UserQuery = graphql(`
query User($id: ID!) {
@@ -224,20 +224,20 @@ const UserQuery = graphql(`
}
`);
-const response = await fetch("https://graphql.org/graphql/", {
- method: "POST",
+const response = await fetch('https://graphql.org/graphql/', {
+ method: 'POST',
headers: {
- "Content-Type": "application/json",
- Accept: "application/graphql-response+json",
+ 'Content-Type': 'application/json',
+ Accept: 'application/graphql-response+json',
},
body: JSON.stringify({
query: UserQuery,
- variables: { id: "1" },
+ variables: { id: '1' },
}),
});
if (!response.ok) {
- throw new Error("Network response was not ok");
+ throw new Error('Network response was not ok');
}
const result: ResultOf = await response.json();
@@ -256,8 +256,8 @@ For guides on using the Client Preset with popular frameworks and tools, see:
To keep your type generation reliable and consistent:
- Check in generated files to version control so teammates and CI systems don't produce
-divergent results.
+ divergent results.
- Run type generation in CI to ensure types stay in sync with schema changes.
- Use schema diffing tools like `graphql-inspector` to catch breaking changes before
-they're merged.
-- Automate regeneration with pre-commit hooks, GitHub Actions, or lint-staged workflows.
\ No newline at end of file
+ they're merged.
+- Automate regeneration with pre-commit hooks, GitHub Actions, or lint-staged workflows.
diff --git a/website/pages/docs/using-directives.mdx b/website/pages/docs/using-directives.mdx
index 4e24dfe643..fe323e18ce 100644
--- a/website/pages/docs/using-directives.mdx
+++ b/website/pages/docs/using-directives.mdx
@@ -22,14 +22,14 @@ GraphQL defines several built-in directives, each serving a specific purpose dur
execution or in the schema. These include:
- `@include` and `@skip`: Used in the execution language to conditionally include or skip
-fields and fragments at runtime.
-- `@deprecated`: Used in Schema Definition Language (SDL) to mark fields or enum values as
-deprecated, with an optional reason. It appears in introspection but doesn't affect query execution.
+ fields and fragments at runtime.
+- `@deprecated`: Used in Schema Definition Language (SDL) to mark fields or enum values as
+ deprecated, with an optional reason. It appears in introspection but doesn't affect query execution.
For example, the `@include` directive conditionally includes a field based on a Boolean variable:
```graphql
-query($shouldInclude: Boolean!) {
+query ($shouldInclude: Boolean!) {
greeting @include(if: $shouldInclude)
}
```
@@ -190,11 +190,7 @@ Inside a resolver, use the `info` object to access AST nodes and inspect directi
You can check whether a directive is present and change behavior accordingly.
```js
-import {
- graphql,
- buildSchema,
- getDirectiveValues,
-} from 'graphql';
+import { graphql, buildSchema, getDirectiveValues } from 'graphql';
const schema = buildSchema(`
directive @uppercase(enabled: Boolean = true) on FIELD
@@ -209,7 +205,7 @@ const rootValue = {
const directive = getDirectiveValues(
schema.getDirective('uppercase'),
info.fieldNodes[0],
- info.variableValues
+ info.variableValues,
);
const result = 'Hello, world';
@@ -265,10 +261,7 @@ using arguments instead we maximize compatibility and consistency. For example,
our `@uppercase` directive would fit naturally as a field argument:
```js
-import {
- graphql,
- buildSchema,
-} from 'graphql';
+import { graphql, buildSchema } from 'graphql';
const schema = buildSchema(`
enum Format {
@@ -284,7 +277,7 @@ const rootValue = {
greeting: (source, args) => {
const result = 'Hello, world';
- if (args.format === "UPPERCASE") {
+ if (args.format === 'UPPERCASE') {
return result.toUpperCase();
}
@@ -312,16 +305,16 @@ console.log(result);
When working with custom directives in GraphQL.js, keep the following best practices in mind:
- GraphQL.js doesn't have a directive middleware system. All custom directive logic must be implemented
-manually.
-- Weigh schema-driven logic against resolver logic. Directives can make queries more expressive, but they
-may also hide behavior from developers reading the schema or resolvers.
+ manually.
+- Weigh schema-driven logic against resolver logic. Directives can make queries more expressive, but they
+ may also hide behavior from developers reading the schema or resolvers.
- Always prefer field arguments over directives where possible.
-- Keep directive behavior transparent and debuggable. Since directives are invisible at runtime unless
-logged or documented, try to avoid magic behavior.
+- Keep directive behavior transparent and debuggable. Since directives are invisible at runtime unless
+ logged or documented, try to avoid magic behavior.
- Use directives when they offer real value. Avoid overusing directives to replace things that could be
-handled more clearly in schema design or resolvers.
-- Validate directive usage explicitly if needed. If your directive has rules or side effects, consider
-writing custom validation rules to enforce correct usage.
+ handled more clearly in schema design or resolvers.
+- Validate directive usage explicitly if needed. If your directive has rules or side effects, consider
+ writing custom validation rules to enforce correct usage.
## Additional resources
diff --git a/website/pages/upgrade-guides/v14-v15.mdx b/website/pages/upgrade-guides/v14-v15.mdx
index 94611397be..1aacaf4017 100644
--- a/website/pages/upgrade-guides/v14-v15.mdx
+++ b/website/pages/upgrade-guides/v14-v15.mdx
@@ -13,8 +13,8 @@ import { Callout } from 'nextra/components';
GraphQL.js v15 is an older release line. GraphQL.js v16 is the current stable
release line, and a v17 release candidate is available for final testing and
feedback. If you are upgrading from v14, use this guide first, then continue
- through [v15 to v16](/upgrade-guides/v15-v16) and
- [v16 to v17](/upgrade-guides/v16-v17).
+ through [v15 to v16](/upgrade-guides/v15-v16) and [v16 to
+ v17](/upgrade-guides/v16-v17).
GraphQL.js v15 is mainly a compatibility cleanup and SDL modernization release.
@@ -29,17 +29,26 @@ major-version guides so failures stay tied to one version boundary.