Skip to content

internal: frontport additional amazing docs work #4420

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
May 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion cspell.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ overrides:
- URQL
- tada
- Graphile
- precompiled
- debuggable

validateDirectives: true
ignoreRegExpList:
Expand Down Expand Up @@ -186,4 +188,3 @@ words:
- overcomplicating
- cacheable
- pino
- debuggable
25 changes: 13 additions & 12 deletions website/pages/docs/_meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,43 +2,44 @@ const meta = {
index: '',
'-- 1': {
type: 'separator',
title: 'GraphQL.JS Tutorial',
title: 'Getting Started',
},
'getting-started': '',
'running-an-express-graphql-server': '',
'graphql-clients': '',
'authentication-and-express-middleware': '',
'-- 2': {
type: 'separator',
title: 'Core Concepts',
},
'basic-types': '',
'passing-arguments': '',
'object-types': '',
'mutations-and-input-types': '',
'authentication-and-express-middleware': '',
'authorization-strategies': '',
'-- 2': {
nullability: '',
'abstract-types': '',
'custom-scalars': '',
'-- 3': {
type: 'separator',
title: 'Advanced Guides',
},
'constructing-types': '',
nullability: '',
'abstract-types': '',
'oneof-input-objects': '',
'defer-stream': '',
subscriptions: '',
'type-generation': '',
'cursor-based-pagination': '',
'custom-scalars': '',
'advanced-custom-scalars': '',
'operation-complexity-controls': '',
'n1-dataloader': '',
'caching-strategies': '',
'resolver-anatomy': '',
'graphql-errors': '',
'using-directives': '',
'-- 3': {
type: 'separator',
title: 'Testing',
},
'authorization-strategies': '',
'-- 4': {
type: 'separator',
title: 'FAQ',
title: 'Production & Scaling',
},
'going-to-production': '',
'scaling-graphql': '',
Expand Down
80 changes: 45 additions & 35 deletions website/pages/docs/abstract-types.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
title: Abstract types in GraphQL.js
---

# Abstract types in GraphQL.js

GraphQL includes two kinds of abstract types: interfaces and unions. These types let a single
field return values of different object types, while keeping your schema type-safe.

This guide covers how to define and resolve abstract types using GraphQL.js. It focuses on
This guide covers how to define and resolve abstract types using GraphQL.js. It focuses on
constructing types in JavaScript using the GraphQL.js type system, not the schema definition
language (SDL).

Expand All @@ -22,20 +24,20 @@ flexibility while preserving validation, introspection, and tool support.
GraphQL provides two kinds of abstract types:

- Interfaces define a set of fields that multiple object types must implement.
- Use case: A `ContentItem` interface with fields like `id`, `title`, and `publishedAt`,
- Use case: A `ContentItem` interface with fields like `id`, `title`, and `publishedAt`,
implemented by types such as `Article` and `PodcastEpisode`.
- Unions group together unrelated types that don't share any fields.
- Use case: A `SearchResult` union that includes `Book`, `Author`, and `Publisher` types.
- Use case: A `SearchResult` union that includes `Book`, `Author`, and `Publisher` types.

## Defining interfaces

To define an interface in GraphQL.js, use the `GraphQLInterfaceType` constructor. An interface
must include a `name`, a `fields` function, and a `resolveType` function, which tells GraphQL which
concrete type a given value corresponds to.
must include a `name`, definition of the shared `fields`, and should include a `resolveType`
function telling GraphQL which concrete type a given value corresponds to.

The following example defines a `ContentItem` interface for a publishing platform:

```js
```js filename="ContentItemInterface.js"
import { GraphQLInterfaceType, GraphQLString, GraphQLNonNull } from 'graphql';

const ContentItemInterface = new GraphQLInterfaceType({
Expand All @@ -55,10 +57,13 @@ const ContentItemInterface = new GraphQLInterfaceType({
return null;
},
});

exports.ContentItemInterface = ContentItemInterface;
```

You can return either the type name as a string or the corresponding `GraphQLObjectType` instance.
Returning the instance is recommended when possible for better type safety and tooling support.
The `resolveType` function must return either the string type name corresponding
to the `GraphQLObjectType` of the given `value`, or `null` if the type could not
be determined.

## Implementing interfaces with object types

Expand All @@ -70,6 +75,7 @@ conform to the `ContentItem` interface:

```js
import { GraphQLObjectType, GraphQLString, GraphQLNonNull } from 'graphql';
import { ContentItemInterface } from './ContentItemInterface.js';

const ArticleType = new GraphQLObjectType({
name: 'Article',
Expand Down Expand Up @@ -105,11 +111,8 @@ GraphQL uses `resolveType`.
Use the `GraphQLUnionType` constructor to define a union. A union allows a field to return one
of several object types that don't need to share fields.

A union requires:

- A `name`
- A list of object types (`types`)
- A `resolveType` function
A union requires a name and a list of object types (`types`). It should also be
provided a `resolveType` function the same as explained for interfaces above.

The following example defines a `SearchResult` union:

Expand All @@ -134,39 +137,42 @@ const SearchResultType = new GraphQLUnionType({
});
```

Unlike interfaces, unions don't declare any fields of their own. Clients use inline fragments
to query fields from the concrete types.
Unlike interfaces, unions don't declare any fields their members must implement.
Clients use a fragment with a type condition to query fields from a concrete type.

## Resolving abstract types at runtime

GraphQL resolves abstract types dynamically during execution using the `resolveType` function.
GraphQL resolves abstract types dynamically during execution using the `resolveType` function, if
present.

This function receives the following arguments:

{/* prettier-ignore */}
```js
resolveType(value, context, info)
```

It can return:

- A `GraphQLObjectType` instance (recommended)
- The name of a type as a string
- `null` if the type could not be determined
- A `Promise` resolving to either of the above

If `resolveType` isn't defined, GraphQL falls back to checking each possible type's `isTypeOf`
If `resolveType` isn't defined, GraphQL falls back to checking each possible type's `isTypeOf`
function. This fallback is less efficient and makes type resolution harder to debug. For most cases,
explicitly defining `resolveType` is recommended.

## Querying abstract types

To query a field that returns an abstract type, use inline fragments to select fields from the
possible concrete types. GraphQL evaluates each fragment based on the runtime type of the result.
To query a field that returns an abstract type, use fragments to select fields from the possible
concrete types. GraphQL evaluates each fragment based on the runtime type of the result.

For example:

```graphql
{
search(term: "deep learning") {
query Search($term: String! = "deep learning") {
search(term: $term) {
# Inline fragments with type condition:
... on Book {
title
isbn
Expand All @@ -175,30 +181,34 @@ For example:
name
bio
}
... on Publisher {
name
catalogSize
}
# Named fragment:
...publisherFrag
}
}

fragment publisherFrag on Publisher {
name
catalogSize
}
```

GraphQL's introspection system lists all possible types for each interface and union, which
enables code generation and editor tooling to provide type-aware completions.
enables code generation and editor tooling to provide type-aware completions; however you should
keep in mind the possibility that more types will implement the interface or be included in the
union in future, and thus ensure that you have a default case to handle additional types.

## Best practices

- Always implement `resolveType` for interfaces and unions to handle runtime type resolution.
- Return the `GraphQLObjectType` instance when possible for better clarity and static analysis.
- Keep `resolveType` logic simple, using consistent field shapes or tags to distinguish
types.
- Test `resolveType` logic carefully. Errors in `resolveType` can cause runtime errors that can
be hard to trace.
types.
- Test `resolveType` logic carefully. Errors in `resolveType` can cause runtime errors that can
be hard to trace.
- Use interfaces when types share fields and unions when types are structurally unrelated.

## Additional resources

- [Constructing Types](https://www.graphql-js.org/docs/constructing-types/)
- GraphQL Specification:
- [Interfaces](https://spec.graphql.org/October2021/#sec-Interfaces)
- [Unions](https://spec.graphql.org/October2021/#sec-Unions)
- [Constructing Types](./constructing-types)
- GraphQL Specification:
- [Interfaces](https://spec.graphql.org/October2021/#sec-Interfaces)
- [Unions](https://spec.graphql.org/October2021/#sec-Unions)
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ title: Using Express Middleware with GraphQL.js
sidebarTitle: Using Express Middleware
---

# Authentication and Express Middleware

import { Tabs } from 'nextra/components';

It's simple to use any Express middleware in conjunction with `graphql-http`. In particular, this is a great pattern for handling authentication.
Expand Down Expand Up @@ -101,4 +103,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](\pages\docs\authorization-strategies.mdx).
Want to control access to specific operations or fields? See [Authorization Strategies](./authorization-strategies).
34 changes: 22 additions & 12 deletions website/pages/docs/authorization-strategies.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
title: Authorization Strategies
---

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
how they're enforced.
Expand All @@ -10,6 +12,12 @@ 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`.

<Callout type="info" emoji="ℹ️">
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.
</Callout>

## What is authorization?

Authorization determines what a user is allowed to do. It's different from
Expand All @@ -23,11 +31,7 @@ In GraphQL, authorization typically involves restricting:

## Resolver-based authorization

> **Note:**
> All examples assume you're using Node.js 20 or later with [ES module (ESM) support](https://nodejs.org/api/esm.html) enabled.

The simplest approach is to enforce access rules directly inside resolvers
using the `context.user` value:
You can implement simple authorization checks directly in resolvers using `context.user`:

```js
export const resolvers = {
Expand All @@ -42,12 +46,15 @@ export const resolvers = {
};
```

This works well for smaller schemas or one-off checks.
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
rather than in GraphQL resolvers.

## Centralizing access control logic

As your schema grows, repeating logic like `context.user.role !=='admin'`
becomes error-prone. Instead, extract shared logic into utility functions:
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
export function requireUser(user) {
Expand All @@ -64,7 +71,7 @@ export function requireRole(user, role) {
}
```

You can use these helpers in resolvers:
Then use those helpers in resolvers:

```js
import { requireRole } from './auth.js';
Expand All @@ -79,7 +86,11 @@ export const resolvers = {
};
```

This pattern makes your access rules easier to read, test, and update.
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
there, but they should be applied outside the GraphQL execution layer.

## Field-level access control

Expand Down Expand Up @@ -148,8 +159,7 @@ resolver-based checks and adopt directives later if needed.

## Best practices

- Keep authorization logic close to business logic. Resolvers are often the
right place to keep authorization logic.
- 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.
Expand Down
2 changes: 2 additions & 0 deletions website/pages/docs/basic-types.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
title: Basic Types
---

# Basic Types

import { Tabs } from 'nextra/components';

In most situations, all you need to do is to specify the types for your API using the GraphQL schema language, taken as an argument to the `buildSchema` function.
Expand Down
2 changes: 1 addition & 1 deletion website/pages/docs/caching-strategies.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ export const resolvers = {
- This isn't a long-lived cache. Combine it with other layers for broader coverage.

To read more about DataLoader and the N+1 problem,
see [Solving the N+1 Problem with DataLoader](https://github.com/graphql/dataloader).
see [Solving the N+1 Problem with DataLoader](./n1-dataloader).

## Operation result caching

Expand Down
2 changes: 2 additions & 0 deletions website/pages/docs/constructing-types.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
title: Constructing Types
---

# Constructing Types

import { Tabs } from 'nextra/components';

For many apps, you can define a fixed schema when the application starts, and define it using GraphQL schema language. In some cases, it's useful to construct a schema programmatically. You can do this using the `GraphQLSchema` constructor.
Expand Down
2 changes: 2 additions & 0 deletions website/pages/docs/cursor-based-pagination.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ title: Implementing Cursor-based Pagination

import { Callout } from "nextra/components";

# Implementing Cursor-based Pagination

When a GraphQL API returns a list of data, pagination helps avoid
fetching too much data at once. Cursor-based pagination fetches items
relative to a specific point in the list, rather than using numeric offsets.
Expand Down
2 changes: 2 additions & 0 deletions website/pages/docs/defer-stream.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
title: Enabling Defer & Stream
---

# Enabling Defer and Stream

import { Callout } from 'nextra/components'

<Callout type="info" emoji="ℹ️">
Expand Down
Loading
Loading