【Day 15】ブログ一覧を取得し、表示する - GraphQLModule

## はじめに

![スライド16.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985550/0f5840ac-89f4-ad7d-1d3a-8f617c8db10e.png)

---

**2023年アドベントカレンダー15日目**です。

https://qiita.com/advent-calendar/2023/life-sync-app

現在「ユーザーはトップページでブログの一覧を見ることができる」を進めています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985550/272b7c0a-ca22-3db9-acc8-f52a2dd4a280.png)

前回はBFFで使うWiremockの環境構築を行いました。
今回はGraphQLModuleの設定を進めていきます。

## NestJS

NestJSは、Node.js上で動作するバックエンド開発のフレームワークです。

https://nestjs.com/

https://techtionary.jp/3534

## アーキテクチャ

このようなアーキテクチャで作成していこうと思います

```plantuml
@startuml

package App <<Rectangle>> {
  class AppModule {
    +GraphQLModule
  }
}

package GraphQL <<Rectangle>> {
  class GraphQLModule {}
}

package Blog <<Rectangle>> {
  class BlogModule {
  }

  class BlogResolver {
    +blogService: BlogService
    +get(): Promise<Blog[]>
  }
  class BlogService {
    +get(): Promise<Blog[]>
  }
}

AppModule -right-> GraphQLModule : imports
AppModule  --> BlogModule : imports
BlogModule --> BlogResolver : providers
BlogModule --> BlogService : providers
BlogResolver -right-> BlogService : uses
@enduml
```

今回はこの中のこの部分を作っていきます

```plantuml
package App <<Rectangle>> {
  class AppModule {
    +GraphQLModule
  }
}

package GraphQL <<Rectangle>> {
  class GraphQLModule {}
}
AppModule -right-> GraphQLModule : imports
```

## 実装

### GraphQLModule

AppModuleにGraphQLModuleをimportし、schemaなどの設定をしていきます

```diff_typescript:app.module.ts
import { Module } from '@nestjs/common';
+ import { GraphQLModule } from '@nestjs/graphql';
+ import { ApolloDriver } from '@nestjs/apollo';
+ import { join } from 'path';
+ import { BlogModule } from './blog/blog.module';

@Module({
  imports: [
+   GraphQLModule.forRoot({
+     driver: ApolloDriver,
+     typePaths: [join(process.cwd(), 'src/graphql/*.graphql')],
+     path: '/',
+     definitions: {
+       path: join(process.cwd(), 'src/graphql/graphql.ts'),
+       watch: true
+     },
+   }),
    BlogModule
  ],
- controllers: [AppController],
+ controllers: [],
- providers: [AppService, BlogService, BlogResolver],
+ providers: [],
})
export class AppModule {}
```

:::note
**definitionsに設定できる項目**

|プロパティ|説明|
|----|----|
|path|TypeScriptの型定義ファイルを生成する場所を指定します。このファイルは、GraphQLスキーマの各型に対応するTypeScriptの型定義を含んでいます。|
|watch|trueに設定されると、GraphQLスキーマが変更されたときに型定義ファイルが自動的に更新されます。これは開発中に特に便利です。|
|outputAs|型定義の出力形式を指定します。たとえば、classやinterfaceなど。これにより、生成されるTypeScriptの型のスタイルをカスタマイズできます。|
|typeFileNameSuffix|生成される型定義ファイルのファイル名のサフィックス（接尾辞）を指定します。|
|customScalarTypeMapping|カスタムスカラー型をTypeScriptの型にマッピングするために使用します。GraphQLのカスタムスカラー型とそれに対応するTypeScriptの型を定義できます。|
|additionalHeader|生成される型定義ファイルの先頭に追加する追加のヘッダーを指定します。たとえば、特定のインポート文やコメントなど。|
|emitTypenameField|trueに設定されると、各GraphQLオブジェクト型に__typenameフィールドの型定義が含まれます。これは、特定のGraphQLクライアントの要件に基づいて使用されることがあります。|
:::


typePathおよびに設定している`src/graphql/*.graphql`を**Web**側で**Day 7**で設定した`GraphQL Code Generator`で作成していきます

https://qiita.com/maaaashi/items/fbafb74ea98f5cfbeb5f

### GraphQL Code Generator

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

#### Schema

まず、Schemaファイルを生成します

```diff_typescript:web/codegen.ts
import type { CodegenConfig } from '@graphql-codegen/cli'

const config: CodegenConfig = {
  overwrite: true,
  schema: './schema.graphql',
  documents: 'src/graphql/*.graphql',
  generates: {
    'src/graphql/generated.tsx': {
      plugins: [
        'typescript',
        'typescript-operations',
        'typescript-react-apollo',
      ],
    },
    'mock/schema.graphql': {
      plugins: ['schema-ast'],
    },
+   '../bff/src/graphql/schema.graphql': {
+     plugins: ['schema-ast'],
+   },
  },
}

export default config
```

これでSchemaファイルは生成されます

#### Operation

Schemaファイルは↑の方法で生成できますが、operation.graphqlをまとめて、generateするようなpluginは用意されていないので、**custom-plugin**を作成していきます

### Custom Plugin

このあたりを参考にしていきながら、`custom-plugin.js`のファイルを作成していきます

https://the-guild.dev/graphql/codegen/docs/custom-codegen/plugin-structure


```js:custom-plugin.js
const { print } = require('graphql')
module.exports = {
  plugin: (schema, documents, config) => {
    const combinedDocuments = documents
      .map((doc) => {
        const docContent = doc.document.definitions
          .map((def) => print(def))
          .join('\n')

        return docContent
      })
      .join('\n\n')

    return combinedDocuments
  },
}
```

これを`codegen.ts`で指定していきます

```diff_typescript:codegen.ts
import type { CodegenConfig } from '@graphql-codegen/cli'

const config: CodegenConfig = {
  overwrite: true,
  schema: './schema.graphql',
  documents: 'src/graphql/*.graphql',
  generates: {
    'src/graphql/generated.tsx': {
      plugins: [
        'typescript',
        'typescript-operations',
        'typescript-react-apollo',
      ],
    },
    'mock/schema.graphql': {
      plugins: ['schema-ast'],
    },
    '../bff/src/graphql/schema.graphql': {
      plugins: ['schema-ast'],
    },
+   '../bff/src/graphql/operation.graphql': {
+     schema: 'src/graphql/*.graphql',
+     plugins: ['custom-plugin'],
 +  },
  },
}

export default config
```

これで、`src/graphql/`に置いたoperationのgraphqlファイルを参照して、新しくファイルを作成することができるようになりました。

実際に動かしてみます

```bash
web$ npm run codegen

> life-sync-web@0.1.0 codegen
> graphql-codegen --config codegen.ts
✔ Parse Configuration
✔ Generate outputs
```

無事ファイルが作成されていました！

```
├── bff
│   ├── src/
│   │   ├── graphql/
│   │   │   ├── operation.graphql  ・・・ オペレーションファイル
│   │   │   ├── schema.graphql     ・・・ スキーマファイル
```


### アプリ起動

```bash
npm run start
```

アプリが起動し、`src/graphql/graphql.ts`のエンティティファイルが生成されました👍

明日は`BlogModule` `BlogResolver` `BlogService`を作成していきます

```plantuml
package App <<Rectangle>> {
  class AppModule {
    +GraphQLModule
  }
}

package Blog <<Rectangle>> {
  class BlogModule {
  }

  class BlogResolver {
    +blogService: BlogService
    +get(): Promise<Blog[]>
  }
  class BlogService {
    +get(): Promise<BlogJson[]>
  }
}

AppModule  --> BlogModule : imports
BlogModule --> BlogResolver : providers
BlogModule --> BlogService : providers
BlogResolver -right-> BlogService : uses
@enduml
```