Directives in GraphQL — Complete Guide
In this tutorial, you will learn about Directives in Graphql. We cover key concepts, practical examples, and best practices to help you master this topic.
Directives are annotations in GraphQL that modify the behavior of schema elements or query execution. Built-in directives like @deprecated, @skip, and @include handle common cases. Custom schema directives extend GraphQL for authentication, formatting, and validation.
What You'll Learn
- Built-in directives: @deprecated, @skip, @include, @specifiedBy
- Custom directive definition with directive declarations
- Directive locations: FIELD, FIELD_DEFINITION, OBJECT, etc.
- Implementing custom directives in resolvers
Why It Matters
Directives keep schemas clean by separating cross-cutting concerns from type definitions. @deprecated enables graceful API evolution. Custom directives reduce boilerplate in resolvers.
Real-World Use
Apollo Server uses directives for authentication (@auth), Rate Limiting (@rateLimit), and access control. The GraphQL spec includes built-in directives for conditional execution and deprecation.
flowchart LR
Directive[GraphQL Directive] --> BuiltIn[Built-in]
Directive --> Custom[Custom]
BuiltIn --> deprecated[@deprecated]
BuiltIn --> skip[@skip]
BuiltIn --> include[@include]
Custom --> auth[@auth]
Custom --> format[@format]
Custom --> validate[@validate]
Teacher Mindset
Think of directives as decorators that add metadata or behavior to schema elements without changing their core definition. Schema directives run at query time. Executable directives run on the client side.
Code Examples
# Example 1: Built-in directives
type User {
id: ID!
name: String!
email: String @deprecated(reason: "Use emailAddress instead")
emailAddress: String
}
query GetUsers($showEmail: Boolean!) {
users {
name
email @include(if: $showEmail)
oldField @skip(if: true)
}
}
# Example 2: Custom schema directive declaration
directive @auth(
requires: Role = ADMIN
) on OBJECT | FIELD_DEFINITION
directive @format(
dateFormat: String = "YYYY-MM-DD"
) on FIELD_DEFINITION
directive @lengthRange(
min: Int!
max: Int!
) on INPUT_FIELD_DEFINITION | ARGUMENT_DEFINITION
type Query {
users: [User!]! @auth(requires: USER)
dashboard: Dashboard @auth(requires: ADMIN)
}
// Example 3: Custom directive implementation
class AuthDirective extends SchemaDirectiveVisitor {
visitObject(type) {
this.ensureFieldsWrapped(type);
}
visitFieldDefinition(field) {
const originalResolve = field.resolve || defaultFieldResolver;
const { requires } = this.args;
field.resolve = async function (parent, args, context, info) {
if (!context.user || context.user.role !== requires) {
throw new Error('Not authorized');
}
return originalResolve.call(this, parent, args, context, info);
};
}
}
Common Mistakes
- Overusing @skip and @include when fragments with spread conditions are more readable
- Creating custom directives that could be handled by resolver logic
- Not specifying directive locations correctly in declaration
- Forgetting to implement the Visitor pattern for schema directives
- Making directives too complex with multiple responsibilities
Practice
- Add @deprecated to three fields in your schema with reasons.
- Use @skip and @include in a query with boolean variables.
- Create a custom @uppercase directive that transforms string fields.
- Implement an @auth directive that checks user roles.
- Challenge: Build a @cache directive that sets cache TTL on query responses.
FAQ
Mini Project
Create three custom directives for your schema: @auth for role-based access on sensitive fields, @lowercase for normalizing string inputs, and @log for tracking field access in development.
What's Next
Next, you will learn about interfaces and unions for polymorphic types in GraphQL schemas.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro