GraphQL Code GeneratorでTypeScriptのDateを扱う

## この記事の目的
TypeScriptでGraphQL Code Generatorを使うとき、Date関連の型をどう扱えばいいかすぐわかる記事がなかったので少し手間取りました。
わかってしまえばどうと言ったことはなかったのですが、なかなか辿り着けなかったので備忘録として残します。

## 環境
```
"graphql": "^16.6.0",
"graphql-scalars": "^1.20.1",
"graphql-yoga": "^3.1.1",
"@graphql-codegen/cli": "2.16.1",
```

## 最終版のレポジトリ
https://github.com/macoto1995/prisma-graphql-yoga-graphql-code-gen


### GraphQL Code Generaor
https://the-guild.dev/graphql/codegen

GraphQL Code GeneraorはGraphQLのSDLから関連するコードを生成してくれます。
今回はバックエンドのコード生成が主な目的なので、GraphQL Code Generatorの設定ファイルである
[codegen.ts](https://github.com/macoto1995/prisma-graphql-yoga-graphql-code-gen/blob/main/codegen.ts#L6
)の`plugins`に`'typescript-resolvers'`を指定します。

```codegen.ts

const config: CodegenConfig = {
  schema: 'schema.graphql',
    generates: {
      './src/resolvers-types.ts': {    
        plugins: ['typescript', 'typescript-resolvers'],
      },
    },
    ...
  }
}
```

そのように設定すると`yarn graphql-codegen`で下のようなGraphQLのSDLから、Resolverを含む型定義のファイルを生成してくれます。

```scheme.graphql
type Task {
  id: ID
  content: String!
  hasDone: Boolean!
}
type Query {
  drafts: [Task!]
}
```

```resolvers-types.ts
...
export type Query = {
  __typename?: "Query";
  drafts: Maybe<Array<Task>>;
};

export type Task = {
  __typename?: "Task";
  content: Scalars["String"];
  hasDone: Scalars["Boolean"];
  id: Maybe<Scalars["ID"]>;
};

...
export type TaskResolvers<
  ContextType = any,
  ParentType extends ResolversParentTypes["Task"] = ResolversParentTypes["Task"]
> = {
  content?: Resolver<ResolversTypes["String"], ParentType, ContextType>;
  hasDone?: Resolver<ResolversTypes["Boolean"], ParentType, ContextType>;
  id?: Resolver<Maybe<ResolversTypes["ID"]>, ParentType, ContextType>;
  __isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
...
```

この時にResolverで以下のように指定すると生成した型を元にResolverを実装できます。
```task.ts
export const TaskResolever: QueryResolvers['drafts'] = () => {
   /// Taskを返す処理
}

```

下の画像のように型を元に自動補完をすることも可能です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/181998/a735cf7c-4696-79fb-71b9-e925b2ed3123.png)


## Date関連のプロパティをGraphQLのSDLで定義
GraphQLのデフォルトのScalar値は下記のものしか用意されていません。
- Int
- Float
- String
- Boolean
- ID

そのため、DateやDateTimeなどを扱いたい時にはCustom Scalarとして追加する必要があります。
Custom Scalarは以下のように定義します。

```schema.graphql
scalar DateTime # この部分
scalar Date     # この部分

type Task {
  id: ID
  content: String!
  hasDone: Boolean!
  createdAt: DateTime! # Custom Scalarを参照
  createdOn: Date!     # Custom Scalarを参照
}
type Query {
 drafts: [Task!]
}

```

ただし、このように指定してもGraphQL Code Generator側ではこのCustom Scalarの型を参照できないため、
TypeScriptで参照するときにCustom Scalarがany型になってしまいます。
これでは型生成をしている意味がありません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/181998/18bad283-f695-a57d-ad11-8a951ffa73e8.png)

CutomScalarにTypeScriptの型を設定するには、codegen.tsでDateとDateTimeの型を指定してあげる必要があります。
ここの定義の仕方は型のオブジェクトではなく、文字列である点に注意が必要です
つまり`Date`ではなく`'Date'`を指定します。

```codegen.ts
...
const config: CodegenConfig = {
    ...
    scalars: {
      DateTime: 'Date',
      Date: 'Date'
    }
  }
};
...
```
そうすると、ResolverからもDate型であることを参照でき、型チェックが行われます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/181998/e555ca50-a110-e345-e321-c02a610a19a2.png)

このようにCustom Scalarがany型になってしまうことを防ぎたい場合、`strictScalars`という項目を`true`に設定しておくと、コード生成時にCustom Scalarの型がanyの時にエラーを吐いてくれます。

```codegen.ts
...
const config: CodegenConfig = {
    ...
    strictScalars: true, // この部分を追加
    scalars: {} // Date, DateTimeの定義がない場合
  }
};
...
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/181998/27642d5d-d07b-9cb4-c9a5-a2bc9335dbb8.png)


https://the-guild.dev/graphql/codegen/plugins/typescript/typescript

## GraphQL Scalars
TypeScriptの場合、よく使うCustom ScalarはGraphQL Scalarsというパッケージが用意されており、DateやDateTimeもこちらの定義を利用することができます。

https://the-guild.dev/graphql/scalars/docs

上のschema.graphqlではansweredAtはDateTime型、answeredOnはDate型を指定しているのに、TypeScriptのコードではどちらもDate型で返していました。

特に設定をしなければ上記の実装では下のようなレスポンスが返ります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/181998/769e356e-4be8-4958-63d5-9a2fb5b28f9e.png)

どちらもnew Date().toString()の値が返っているように見えます。
期待された通りの値を返すためにはGraphQL Scalarsの型定義とResolverを追加する必要があります。



![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/181998/243f603b-da00-40f2-1f18-7126b0056c0f.png)


そうすると、Date型を指定していた部分には`yyyy-MM-dd`の値が返るようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/181998/6c6f6507-ea88-7cee-0e66-31a2200b604f.png)

## 終わりに
GraphQL Code GeneratorでDate、DateTimeを扱う場合、GraphQL Scalarsを使うと、自分でフォーマットを定義する必要がなく使えてとても便利ですが
公式ドキュメントからこれらの使い方を理解するのはなかなか難しかったため、同じようなところで詰まっている方の助けになれば嬉しいです。


## 追記
以下の理由で一旦GraphQL Code Generatorを扱うのはお休みします。

https://twitter.com/hiyokkotrainee/status/1604349012047499265?ref_src=twsrc%5Etfw%7Ctwcamp%5Etweetembed%7Ctwterm%5E1604349012047499265%7Ctwgr%5E0c412408086741723a7077577fa39a2045b5d36a%7Ctwcon%5Es1_c10&ref_url=https%3A%2F%2Fpublish.twitter.com%2F%3Fquery%3Dhttps3A2F2Ftwitter.com2Fhiyokkotrainee2Fstatus2F1604349012047499265widget%3DTweet