Skip to content

Commit cc6dfd2

Browse files
committed
add guide on directives
1 parent 98eff7f commit cc6dfd2

File tree

2 files changed

+275
-0
lines changed

2 files changed

+275
-0
lines changed

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

0 commit comments

Comments
 (0)