iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Directives

Directives are @-prefixed annotations on operations or schema definitions. @include, @skip, @deprecated ship in the spec; servers add their own (@auth, @cacheControl, @constraint).

Built-in + custom directives

EXAMPLE
# 1) Operation-level directives — @include / @skip
query UserProfile($id: ID!, $withPosts: Boolean!) {
    user(id: $id) {
        id
        name
        posts @include(if: $withPosts) {
            title
        }
        followers @skip(if: $withPosts) {
            count
        }
    }
}

# 2) Schema directive — @deprecated
type User {
    name:     String!
    email:    String!
    fullName: String! @deprecated(reason: "Use 'name' instead")
}

# 3) Custom schema directive — @auth
directive @auth(requires: Role = USER) on FIELD_DEFINITION

enum Role { ADMIN, USER, GUEST }

type Query {
    me:      User!  @auth
    secrets: [String!]! @auth(requires: ADMIN)
    public:  String
}

# Apollo Server v4 — custom directive implementation
import { mapSchema, getDirective, MapperKind } from '@graphql-tools/utils';

function authDirectiveTransformer(schema) {
    return mapSchema(schema, {
        [MapperKind.OBJECT_FIELD]: (fieldConfig) => {
            const dir = getDirective(schema, fieldConfig, 'auth')?.[0];
            if (!dir) return;
            const { requires } = dir;
            const { resolve } = fieldConfig;
            fieldConfig.resolve = (src, args, ctx, info) => {
                if (!ctx.user) throw new Error('Unauthenticated');
                if (requires === 'ADMIN' && ctx.user.role !== 'ADMIN') {
                    throw new Error('Forbidden');
                }
                return resolve(src, args, ctx, info);
            };
            return fieldConfig;
        },
    });
}

# 4) @cacheControl — Apollo's response caching
type Post @cacheControl(maxAge: 60) {
    id:    ID!
    title: String!
    body:  String! @cacheControl(maxAge: 300)
}

# 5) Validation directives — @constraint (graphql-constraint-directive)
type Mutation {
    signup(
        email:    String! @constraint(format: "email")
        password: String! @constraint(minLength: 8, maxLength: 72)
        age:      Int!    @constraint(min: 13, max: 120)
    ): User!
}

Why it matters

Custom directives push cross-cutting concerns (auth, rate-limit, caching) out of resolvers and into the schema. Readers see the policy on the field itself, not buried in 40 lines of boilerplate.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
# Built-ins: @include @skip @deprecated
type User { email: String @deprecated(reason: "use contact.email") }
Try it Yourself »

Discussion

Loading…