NestJSで作成したアプリケーションにGraphQLを導入する

## はじめに
前回の記事では、NestJSとPrismaを使用して、簡易的なSlackクローンのバックエンドを構築しました。RESTfulなAPIを実装し、チャンネルや投稿の管理を行う基本的な機能を実現しました。

https://qiita.com/sergicalsix/items/03151068aeec8d018872

今回は、その続きとしてGraphQLを導入し、より柔軟で効率的なデータ取得を可能にする方法を紹介します。GraphQLを用いることで、クライアント側は必要なデータだけを取得できるため、APIの利用効率が向上します。また、Apollo Serverを使用してGraphQLサーバーを構築し、既存のNestJSアプリケーションと連携させる手順を解説します。



## 環境構築
GraphQLを導入するために、Apollo Serverと関連パッケージをインストールします。また、APIリクエストを簡単に行うためにAxiosも使用します。

```
% npm install apollo-server graphql axios
```



### GraphQL用のAPIの作成
GraphQLサーバーをNestJSアプリケーションに統合するために、チャンネルと投稿のデータを取得するAPIを作成します。以下のコードは、チャンネルとその関連する投稿を取得するエンドポイントを定義しています。

- `src/channels/channels.controller.ts`

```typescript
import { Controller, Get } from '@nestjs/common';
import { ChannelsService } from './channels.service';
import { PostsService } from '../posts/posts.service';

@Controller('channels')
export class ChannelsController {
  constructor(
    private readonly channelsService: ChannelsService,
    private readonly postsService: PostsService,
  ) {}

  @Get()
  findAll() {
    return this.channelsService.findAll();
  }

  @Get('with-posts')
  async getChannelsWithPosts() {
    const channels = await this.channelsService.findAll();
    const channelsWithPosts = await Promise.all(
      channels.map(async (channel) => {
        const posts = await this.postsService.findByChannel(channel.id);
        return { ...channel, posts };
      }),
    );
    return channelsWithPosts;
  }
}
```

- `src/posts/posts.module.ts`

```typescript
import { Module } from '@nestjs/common';
import { PrismaModule } from '../prisma/prisma.module';
import { PostsController } from './posts.controller';
import { PostsService } from './posts.service';

@Module({
  imports: [PrismaModule],
  controllers: [PostsController],
  providers: [PostsService],
  exports: [PostsService]
})
export class PostsModule {}
```

- `src/channels/channels.module.ts`
```typescript
import { Module, Post } from '@nestjs/common';
import { PrismaModule } from '../prisma/prisma.module';
import { PostsModule } from '../posts/posts.module';
import { ChannelsController } from './channels.controller';
import { ChannelsService } from './channels.service';

@Module({
  imports: [PrismaModule, PostsModule],
  controllers: [ChannelsController],
  providers: [ChannelsService]
})
export class ChannelsModule {}
```
これらのモジュールとコントローラを設定することで、チャンネルとその関連する投稿を取得するエンドポイントが整います。


## GraphQLサーバーの実装

次に、GraphQLサーバーを実装します。ここでは、Apollo Serverを使用して簡単なGraphQLサーバーを構築し、既存のREST APIと連携させます。

プロジェクトのディレクトリにgraphqlフォルダを作成します。

```bash 
% mkdir graphql
```

`graphql/index.js`ファイルを以下の内容で作成します。ここでは、GraphQLのスキーマとリゾルバを同一ファイル内に定義しています。

```javascript
const { ApolloServer, gql } = require('apollo-server');
const axios = require('axios');

// GraphQLスキーマの定義
const typeDefs = gql`
  type Post {
    id: ID!
    content: String!
    channelId: ID!
    createdAt: String!
    updatedAt: String!
  }

  type Channel {
    id: ID!
    name: String!
    posts: [Post!]!
  }

  type Query {
    getChannelsWithPosts: [Channel!]!
  }
`;


// リゾルバの定義
const resolvers = {
    Query: {
      getChannelsWithPosts: async () => {
        try {
          const response = await axios.get('http://localhost:3000/channels/with-posts');
          return response.data;
        } catch (error) {
          console.error('Error fetching channels with posts:', error.message);
          throw new Error('Failed to fetch channels with posts');
        }
      },
    },
  };

const server = new ApolloServer({ typeDefs, resolvers });

server.listen().then(({ url }) => {
  console.log(`🚀 Server ready at ${url}`);
});
```

この設定では、`getChannelsWithPosts`というクエリを定義し、NestJSのREST APIからデータを取得して返します。

## GraphQLサーバーの起動と確認
GraphQLサーバーを起動し、正しく動作することを確認します。

```bash 
% cd graphql
% node index.js  
🚀 Server ready at http://localhost:4000/
```

ブラウザで`http://localhost:4000/`にアクセスすると、Apollo ServerのGraphQL Playgroundが表示されます。

![スクリーンショット 2024-09-24 15.29.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/487820/fe47eea0-45cc-f4d3-30db-ab27e3c05270.png)

GraphQL PlaygroundのExplorerタブから、以下のクエリを実行してデータが取得できればOKです！

```graphql
query {
  getChannelsWithPosts {
    id
    name
    posts {
      id
      content
      createdAt
      updatedAt
    }
  }
}
```



![スクリーンショット 2024-09-24 16.33.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/487820/6a3cce09-d691-4f20-fd94-4dadc0d223cd.png)

GraphQLは`curl`でも実験できます。

```bash 
% curl 'http://localhost:4000' \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json' \
  --data-binary '{"query":"query GetChannelsWithPosts {\n  getChannelsWithPosts {\n    id\n    name\n    posts {\n      id\n      content\n      createdAt\n      updatedAt\n    }\n  }\n}"}' \
  --compressed
{"data":{"getChannelsWithPosts":[{"id":"1","name":"general","posts":[{"id":"2","content":"おはよう、世界！","createdAt":"2024-09-20T06:19:34.851Z","updatedAt":"2024-09-20T06:19:34.851Z"}]},{"id":"2","name":"random","posts":[{"id":"3","content":"こんばんは、世界！","createdAt":"2024-09-20T06:34:59.223Z","updatedAt":"2024-09-20T06:34:59.223Z"}]}]}}
```

## おわりに
今回の記事では、NestJSとPrismaを使用したSlackクローンアプリケーションにGraphQLを導入する方法を紹介しました。具体的には、Apollo Serverを用いてGraphQLサーバーを構築し、既存のREST APIと連携させる手順を解説しました。