Using GraphQL Directives
1. Understanding Built-in Directives
| Directive | Locations | Purpose |
|---|---|---|
| @skip(if: Boolean!) | FIELD, FRAGMENT_SPREAD, INLINE_FRAGMENT | Skip field if true |
| @include(if: Boolean!) | FIELD, FRAGMENT_SPREAD, INLINE_FRAGMENT | Include only if true |
| @deprecated(reason: String) | FIELD_DEFINITION, ENUM_VALUE, ARGUMENT_DEFINITION, INPUT_FIELD_DEFINITION | Mark deprecated |
| @specifiedBy(url: String!) | SCALAR | Custom scalar spec URL |
| @oneOf NEW | INPUT_OBJECT | Exactly one input field set |
2. Using @skip Directive
Example: Conditional skip
query GetUser($skipEmail: Boolean!) {
user {
id
name
email @skip(if: $skipEmail)
}
}
3. Using @include Directive
Example: Conditional include
query GetUser($withPosts: Boolean!) {
user {
id
posts @include(if: $withPosts) { id title }
}
}
| Rule | Detail |
|---|---|
| Mutually exclusive | If both used: skip wins |
| Selection-time | Applied during field collection, not execution |
4. Using @deprecated Directive
Example: Field deprecation
type User {
fullName: String! @deprecated(reason: "Use firstName + lastName")
firstName: String!
lastName: String!
}
5. Creating Custom Directives
Example: Custom directive declaration
directive @auth(requires: Role = USER) on FIELD_DEFINITION | OBJECT
directive @upper on FIELD_DEFINITION
directive @rateLimit(max: Int!, window: Int!) on FIELD_DEFINITION
| Step | Detail |
|---|---|
| 1. Declare | SDL directive @name on Locations |
| 2. Apply | Annotate schema or operations |
| 3. Implement | Schema visitor / mapper / middleware |
6. Defining Directive Locations
| Category | Locations |
|---|---|
| Executable | QUERY, MUTATION, SUBSCRIPTION, FIELD, FRAGMENT_DEFINITION, FRAGMENT_SPREAD, INLINE_FRAGMENT, VARIABLE_DEFINITION |
| Type System | SCHEMA, SCALAR, OBJECT, FIELD_DEFINITION, ARGUMENT_DEFINITION, INTERFACE, UNION, ENUM, ENUM_VALUE, INPUT_OBJECT, INPUT_FIELD_DEFINITION |
7. Implementing Directive Logic
Example: @upper via schema mapper
import { mapSchema, getDirective, MapperKind } from "@graphql-tools/utils";
function upperDirective(schema, directiveName) {
return mapSchema(schema, {
[MapperKind.OBJECT_FIELD]: (fieldConfig) => {
const dir = getDirective(schema, fieldConfig, directiveName)?.[0];
if (dir) {
const { resolve } = fieldConfig;
fieldConfig.resolve = async (...args) => {
const v = await resolve(...args);
return typeof v === "string" ? v.toUpperCase() : v;
};
}
return fieldConfig;
}
});
}
8. Using Schema Directives
| Pattern | Example |
|---|---|
| Field-level | email: String! @auth(requires: ADMIN) |
| Type-level | type AdminPanel @auth(requires: ADMIN) { ... } |
| Argument | password: String @constraint(minLength: 8) |
9. Creating Auth Directives
Example: @auth implementation
function authDirective(schema) {
return mapSchema(schema, {
[MapperKind.OBJECT_FIELD]: (fc) => {
const auth = getDirective(schema, fc, "auth")?.[0];
if (!auth) return fc;
const { resolve = defaultFieldResolver } = fc;
fc.resolve = (src, args, ctx, info) => {
if (!ctx.user) throw new GraphQLError("Unauthenticated", { extensions: { code: "UNAUTHENTICATED" } });
if (auth.requires && !ctx.user.roles.includes(auth.requires))
throw new GraphQLError("Forbidden", { extensions: { code: "FORBIDDEN" } });
return resolve(src, args, ctx, info);
};
return fc;
}
});
}
10. Creating Validation Directives
| Library | Provides |
|---|---|
| graphql-constraint-directive | @constraint(minLength, maxLength, pattern, min, max, format) |
| Custom | Implement via field/argument visitor |
11. Creating Formatting Directives
| Directive | Effect |
|---|---|
| @upper | String → uppercase |
| @formatDate(format) | DateTime → formatted string |
| @currency(code) | Number → localized money |
| @truncate(length) | String → shortened with ellipsis |
12. Repeating Directives
Example: Repeatable directive
directive @tag(name: String!) repeatable on FIELD_DEFINITION
type Query {
internalTool: String @tag(name: "internal") @tag(name: "preview")
}
| Keyword | Effect |
|---|---|
| repeatable | Allow multiple applications on same location |
| Default | Single application per location |