Using GraphQL Directives

1. Understanding Built-in Directives

DirectiveLocationsPurpose
@skip(if: Boolean!)FIELD, FRAGMENT_SPREAD, INLINE_FRAGMENTSkip field if true
@include(if: Boolean!)FIELD, FRAGMENT_SPREAD, INLINE_FRAGMENTInclude only if true
@deprecated(reason: String)FIELD_DEFINITION, ENUM_VALUE, ARGUMENT_DEFINITION, INPUT_FIELD_DEFINITIONMark deprecated
@specifiedBy(url: String!)SCALARCustom scalar spec URL
@oneOf NEWINPUT_OBJECTExactly 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 }
  }
}
RuleDetail
Mutually exclusiveIf both used: skip wins
Selection-timeApplied 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
StepDetail
1. DeclareSDL directive @name on Locations
2. ApplyAnnotate schema or operations
3. ImplementSchema visitor / mapper / middleware

6. Defining Directive Locations

CategoryLocations
ExecutableQUERY, MUTATION, SUBSCRIPTION, FIELD, FRAGMENT_DEFINITION, FRAGMENT_SPREAD, INLINE_FRAGMENT, VARIABLE_DEFINITION
Type SystemSCHEMA, 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

PatternExample
Field-levelemail: String! @auth(requires: ADMIN)
Type-leveltype AdminPanel @auth(requires: ADMIN) { ... }
Argumentpassword: 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

LibraryProvides
graphql-constraint-directive@constraint(minLength, maxLength, pattern, min, max, format)
CustomImplement via field/argument visitor

11. Creating Formatting Directives

DirectiveEffect
@upperString → 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")
}
KeywordEffect
repeatableAllow multiple applications on same location
DefaultSingle application per location