|
| 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