Skip to content

Commit 1f1d89d

Browse files
docs: add guide on directives (#4401)
Adds guide "Using Directives in GraphQL.js"
1 parent a7db60b commit 1f1d89d

File tree

3 files changed

+277
-0
lines changed

3 files changed

+277
-0
lines changed

cspell.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,3 +118,4 @@ words:
118118
- XXXF
119119
- bfnrt
120120
- wrds
121+
- debuggable

website/pages/docs/_meta.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ const meta = {
2626
'n1-dataloader': '',
2727
'resolver-anatomy': '',
2828
'graphql-errors': '',
29+
'using-directives': '',
2930
'-- 3': {
3031
type: 'separator',
3132
title: 'FAQ',
Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
---
2+
title: Using Directives in GraphQL.js
3+
sidebarTitle: Using Directives
4+
---
5+
6+
# Using Directives in GraphQL.js
7+
8+
Directives let you customize query execution at a fine-grained level. They act like
9+
annotations in a GraphQL document, giving the server instructions about whether to
10+
include a field, how to format a response, or how to apply custom behavior.
11+
12+
GraphQL.js supports built-in directives like `@include`, `@skip`, and `@deprecated` out
13+
of the box. If you want to create your own directives and apply custom behavior, you'll
14+
need to implement the logic yourself.
15+
16+
This guide covers how GraphQL.js handles built-in directives, how to define and apply
17+
custom directives, and how to implement directive behavior during execution.
18+
19+
## How GraphQL.js handles built-in directives
20+
21+
GraphQL defines several built-in directives, each serving a specific purpose during
22+
execution or in the schema. These include:
23+
24+
- `@include` and `@skip`: Used in the execution language to conditionally include or skip
25+
fields and fragments at runtime.
26+
- `@deprecated`: Used in Schema Definition Language (SDL) to mark fields or enum values as
27+
deprecated, with an optional reason. It appears in introspection but doesn't affect query execution.
28+
29+
For example, the `@include` directive conditionally includes a field based on a Boolean variable:
30+
31+
```graphql
32+
query($shouldInclude: Boolean!) {
33+
greeting @include(if: $shouldInclude)
34+
}
35+
```
36+
37+
At runtime, GraphQL.js evaluates the `if` argument. If `shouldInclude` is `false`, the
38+
`greeting` field in this example is skipped entirely and your resolver won't run.
39+
40+
```js
41+
import { graphql, buildSchema } from 'graphql';
42+
43+
const schema = buildSchema(`
44+
type Query {
45+
greeting: String
46+
}
47+
`);
48+
49+
const rootValue = {
50+
greeting: () => 'Hello!',
51+
};
52+
53+
const query = `
54+
query($shouldInclude: Boolean!) {
55+
greeting @include(if: $shouldInclude)
56+
}
57+
`;
58+
59+
const variables = { shouldInclude: true };
60+
61+
const result = await graphql({
62+
schema,
63+
source: query,
64+
rootValue,
65+
variableValues: variables,
66+
});
67+
68+
console.log(result);
69+
// → { data: { greeting: 'Hello!' } }
70+
```
71+
72+
If `shouldInclude` is `false`, the result would be `{ data: {} }`.
73+
74+
The `@deprecated` directive is used in the schema to indicate that a field or enum
75+
value should no longer be used. It doesn't affect execution, but is included
76+
in introspection output:
77+
78+
```graphql
79+
{
80+
__type(name: "MyType") {
81+
fields {
82+
name
83+
isDeprecated
84+
deprecationReason
85+
}
86+
}
87+
}
88+
```
89+
90+
GraphQL.js automatically includes deprecation metadata in introspection. Tools like
91+
GraphiQL use this to show warnings, but GraphQL.js itself doesn't block or modify behavior.
92+
You can still query deprecated fields unless you add validation rules yourself.
93+
94+
## Declaring custom directives in GraphQL.js
95+
96+
To use a custom directive, you first define it in your schema using the
97+
`GraphQLDirective` class. This defines the directive's name, where it can
98+
be applied, and any arguments it accepts.
99+
100+
A directive in GraphQL.js is just metadata. It doesn't perform any behavior on its own.
101+
102+
Here's a basic example that declares an `@uppercase` directive that can be applied to fields:
103+
104+
```js
105+
import {
106+
GraphQLDirective,
107+
DirectiveLocation,
108+
GraphQLNonNull,
109+
GraphQLBoolean,
110+
} from 'graphql';
111+
112+
const UppercaseDirective = new GraphQLDirective({
113+
name: 'uppercase',
114+
description: 'Converts the result of a field to uppercase.',
115+
locations: [DirectiveLocation.FIELD],
116+
args: {
117+
enabled: {
118+
type: GraphQLNonNull(GraphQLBoolean),
119+
defaultValue: true,
120+
description: 'Whether to apply the transformation.',
121+
},
122+
},
123+
});
124+
```
125+
126+
To make the directive available to your schema, you must explicitly include it:
127+
128+
```js
129+
import { GraphQLSchema } from 'graphql';
130+
131+
const schema = new GraphQLSchema({
132+
query: QueryType,
133+
directives: [UppercaseDirective],
134+
});
135+
```
136+
137+
Once added, tools like validation and introspection will recognize it.
138+
139+
## Applying directives in queries
140+
141+
After defining and adding your directive to the schema, clients can apply it in queries using
142+
the `@directiveName` syntax. Arguments are passed in parentheses, similar to field arguments.
143+
144+
You can apply directives to:
145+
146+
- Fields
147+
- Fragment spreads
148+
- Inline fragments
149+
150+
The following examples show how to apply directives:
151+
152+
```graphql
153+
# Applied to a field
154+
{
155+
greeting @uppercase
156+
}
157+
```
158+
159+
```graphql
160+
# Applied to a fragment spread
161+
{
162+
...userFields @include(if: true)
163+
}
164+
```
165+
166+
```graphql
167+
# Applied to an inline fragment
168+
{
169+
... on User @skip(if: false) {
170+
email
171+
}
172+
}
173+
```
174+
175+
When a query is parsed, GraphQL.js includes directive nodes in the field's
176+
Abstract Syntax Tree (AST). You can access these via `info.fieldNodes` inside
177+
a resolver.
178+
179+
## Implementing custom directive behavior
180+
181+
GraphQL.js doesn't execute custom directive logic for you. You must handle it during
182+
execution. There are two common approaches:
183+
184+
### 1. Handle directives in resolvers
185+
186+
Inside a resolver, use the `info` object to access AST nodes and inspect directives.
187+
You can check whether a directive is present and change behavior accordingly.
188+
189+
```js
190+
import {
191+
graphql,
192+
buildSchema,
193+
getDirectiveValues,
194+
} from 'graphql';
195+
196+
const schema = buildSchema(`
197+
directive @uppercase(enabled: Boolean = true) on FIELD
198+
199+
type Query {
200+
greeting: String
201+
}
202+
`);
203+
204+
const rootValue = {
205+
greeting: (source, args, context, info) => {
206+
const directive = getDirectiveValues(
207+
schema.getDirective('uppercase'),
208+
info.fieldNodes[0],
209+
info.variableValues
210+
);
211+
212+
const result = 'Hello, world';
213+
214+
if (directive?.enabled) {
215+
return result.toUpperCase();
216+
}
217+
218+
return result;
219+
},
220+
};
221+
222+
const query = `
223+
query {
224+
greeting @uppercase
225+
}
226+
`;
227+
228+
const result = await graphql({ schema, source: query, rootValue });
229+
console.log(result);
230+
// → { data: { greeting: 'HELLO, WORLD' } }
231+
```
232+
233+
### 2. Use AST visitors or schema wrappers
234+
235+
For more complex logic, you can preprocess the schema or query using AST visitors or wrap
236+
field resolvers. This lets you inject directive logic globally across
237+
multiple types or fields.
238+
239+
This approach is useful for:
240+
241+
- Authorization
242+
- Logging
243+
- Schema transformations
244+
- Feature flags
245+
246+
## Use cases for custom directives
247+
248+
Some common use cases for custom directives include:
249+
250+
- **Formatting**: `@uppercase`, `@date(format: "YYYY-MM-DD")`, `@currency`
251+
- **Authorization**: `@auth(role: "admin")` to protect fields
252+
- **Feature flags**: `@feature(name: "newHeader")` to expose experimental features
253+
- **Observability**: `@log`, `@tag(name: "important")`, or `@metrics(id: "xyz")`
254+
- **Execution control**: Mask or transform fields at runtime with schema visitors
255+
256+
## Best practices
257+
258+
When working with custom directives in GraphQL.js, keep the following best practices in mind:
259+
260+
- GraphQL.js doesn't have a directive middleware system. All custom directive logic must be implemented
261+
manually.
262+
- Weigh schema-driven logic against resolver logic. Directives can make queries more expressive, but they
263+
may also hide behavior from developers reading the schema or resolvers.
264+
- Keep directive behavior transparent and debuggable. Since directives are invisible at runtime unless
265+
logged or documented, try to avoid magic behavior.
266+
- Use directives when they offer real value. Avoid overusing directives to replace things that could be
267+
handled more clearly in schema design or resolvers.
268+
- Validate directive usage explicitly if needed. If your directive has rules or side effects, consider
269+
writing custom validation rules to enforce correct usage.
270+
271+
## Additional resources
272+
273+
- [GraphQL Specification: Directives](https://spec.graphql.org/draft/#sec-Language.Directives)
274+
- The Guild's guide on [Schema Directives](https://the-guild.dev/graphql/tools/docs/schema-directives)
275+
- Apollo Server's guide on [Directives](https://www.apollographql.com/docs/apollo-server/schema/directives)

0 commit comments

Comments
 (0)