GraphQLのディレクティブについて

## 概要
GraphqlのdirectiveについてやCustom directiveの導入方法のまとめ

## ディレクティブとは

GraphQLのtype, field, 引数などに付与するデコレータで、必要に応じてカスタムロジックを実行できます。

例えばGraphQLには`@deprecated`というディレクティブが組み込まれていて、装飾した箇所が非推奨だと知らせることができます。

```graphql
type User {
  name: String
  age: Int
  gender: String @deprecated(reason: "このようにコメントを付与できます")
}

type Query {
  user: User
}
```

**Playground**
![スクリーンショット 2021-12-06 1.07.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272185/7c0e9a3a-a5f8-01bc-cb5c-4ede748c4c1e.png)

他には`@skip`や`@include`などがある
**Playground**
![スクリーンショット 2021-12-06 1.45.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272185/d466e9a4-725d-6d87-9bc0-b13e1c43b376.png)

| ディレクディブ | 内容 |
|---|---|
|@deprecated  |型の非推奨フィールドや非推奨の列挙値など  |
|@skip  |条件付きでクエリに含めないことができます。|
|@include  | 条件付きでクエリに含めることができます。|


## カスタムディレクティブ
ディレクティブを独自に定義し、付与できます。
使用するライブラリーごとに実装方法が少し違っているので注意が必要です。

**Apolloによるスキーマファーストでの実装例**

- https://www.apollographql.com/docs/apollo-server/schema/creating-directives/#example-uppercasing-strings

**nestjsの例**

- https://docs.nestjs.com/graphql/directives

**TypeGraphQLの例**

- https://typegraphql.com/docs/directives.html#providing-the-implementation
- https://typegraphql.com/docs/emit-schema.html#emit-schema-with-custom-directives

### サンプル

`@auth`というリクエストのトークンを取得しトークン認証をするカスタムディレクティブ

```authDirective.ts
const authDirectiveTransformer = (
  directiveName: string
) => {
  return {
    authDirectiveTypeDefs: `directive @${directiveName} on FIELD_DEFINITION`,
    authDirective: (schema: GraphQLSchema) => mapSchema(schema, {
      [MapperKind.OBJECT_FIELD]: (fieldConfig) => {
        const directive = getDirective(
          schema,
          fieldConfig,
          directiveName
        )?.[0];

        if (directive) {
          const originalResolve = fieldConfig.resolve || defaultFieldResolver;
          fieldConfig.resolve = async function (
            source,
            args,
            context,
            info
          ) {
            // contextの情報が取得できる
            const token = context.token;
            // トークンチェックなど...
            const isAuthorized = false
            if (!isAuthorized) {
              throw new Error("Unauthrized");
            }

            return originalResolve(source, args, context, info)
          };
          return fieldConfig;
        }
      }
    })
  }
};
```

```index.ts
const typeDefs = gql`
  type Address {
    region: String
    city: String
  }

  type User {
    name: String
    age: Int
    gender: String @deprecated(reason: "このようにコメントを付与できます")
    address: Address 
  }

  type Query {
    user: User @auth
  }
`;

const resolvers = {
  Query: {
    user: () => ({
      name: "taro",
      age: 25,
      gender: "male",
      address: {
        region: "tokyo",
        city: "shibuya"
      }
    })
  },
};

const { authDirective, authDirectiveTypeDefs } = authDirectiveTransformer("auth")
const executableSchema = makeExecutableSchema({
  typeDefs: [
    authDirectiveTypeDefs,
    typeDefs
  ],
  resolvers,
});
const schema = authDirective(executableSchema)
const server = new ApolloServer({
  schema,
  context: ({ req }) => {
    return {
      token: req.headers["authentication-token"] || "",
    };
  },
  plugins: [ApolloServerPluginLandingPageGraphQLPlayground()]
});
```

**Playground**
![スクリーンショット 2021-12-06 2.53.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272185/e56100cb-6ccb-e3a9-1ec4-42260fd59b06.png)

## おわりに

今回は認証しかカスタムディレクティブを使用しませんでしたが、他にこういったことで使うといいよってのがありましたらぜひ教えていただきたいです！