nodejsのAPIをGraphQLで実装してみた

## はじめに
GraphQLというものをなんとなく知っていたものの、ちゃんと理解できてなかったなと思い
今回簡単な実装を交えて勉強しました。

## GraphQLってなに？
GraphQLは、Web APIのためのクエリ言語であり、データ操作と取得のためのランタイムです。
Facebookによって2015年に開発され、以降、データを効率的に取得する方法として急速に普及しています。
GraphQLの主な特徴は、クライアントが必要とするデータの形をクエリで明確に指定できる点にあります。
これにより、過剰または不足なデータの取得を避け、アプリケーションのパフォーマンスを最適化できます。

### Restとの違いは？
下記の違いがあります。
| 特徴           | GraphQL                                  | REST                                     |
|--------------|-----------------------------------------|------------------------------------------|
| リクエストの柔軟性 | クライアントが必要なデータのみを指定可能             | 固定されたデータ構造に基づくリクエスト              |
| データ取得       | 一回のリクエストで複数のリソースを取得可能         | 各リソースごとに個別のリクエストが必要               |
| パフォーマンス   | 過剰なデータの取得を避けることで最適化可能         | データの過剰取得や不足による複数のリクエストが必要 |
| 開発の柔軟性     | スキーマに基づいてクライアントとサーバー間の契約を定義 | エンドポイントの設計により制約される                 |

GraphQLは、特に複雑なアプリケーションや多様なフロントエンドデバイスを持つシステムにおいて、
その柔軟性と効率性からRESTに代わる魅力的な選択肢となっています。
概要はこんな感じですね。

## 実装について
### 前提
- Restの方が相応しいとかそんなことはここでは気にしないこととする

### 環境
- apollo-server-express: ^3.13.0
- firebase-functions: ^4.2.1
- graphql: ^16.8.1
- typescript: ^4.9.5
- node: v16.16.0
上記のプラグインは入っているものとして以降は説明していきます。

### 作るもの
国名とその人口や首都を取得する簡単なAPIを実装します。


## 実装
### クエリの設定
```schema.ts
import { gql } from 'apollo-server-express';

export const typeDefs = gql`
  type Country {
    name: String!
    capital: String
    population: Int
  }

  type Query {
    countries: [Country!]!
    country(name: String!): Country
  }
`;
```
GraphQLのスキーマ定義では、クエリ（データの取得要求）と
リゾルバ（データを実際に取得するロジック）の設定が必要です。
ここでは、国に関する情報を取得するためのクエリを定義しています。

### リゾルバの設定
```resolvers.ts
export const resolvers = {
  Query: {
    countries: () => {
      return [
        { name: "日本", capital: "東京", population: 126476461 },
        { name: "アメリカ合衆国", capital: "ワシントンD.C.", population: 331002651 }
      ];
    },
    country: (_: any, args: { name: string }) => {
      const countries = [
        { name: "日本", capital: "東京", population: 126476461 },
        { name: "アメリカ合衆国", capital: "ワシントンD.C.", population: 331002651 }
      ];
      return countries.find(country => country.name === args.name);
    }
  }
};
```
この例では、固定のデータセットを返す形でリゾルバを設定しています。
countryクエリでは、引数として受け取った国名に一致する国を返します。
今回は使用していないですが、リゾルバの設定では各クエリに対して引数は4つ持てるようです。
（コンテキストなどを取得できる）

### サーバのセットアップ
このAPIを呼び出しできるようにします。
```index.ts
import express from 'express';
import { ApolloServer } from 'apollo-server-express';
import { typeDefs } from './schema';
import { resolvers } from './resolvers';
import * as functions from 'firebase-functions';

const app = express();

// GraphQLサーバの設定
const server = new ApolloServer({
  typeDefs, resolvers,
});
// Apollo ServerをExpressと統合
server.start().then(() => {
  server.applyMiddleware({
    app,
    path: "/graphql",
  });
});
exports.graphql = functions.https.onRequest(app);
```
今回のサンプルAPIは/graphqlで呼び出せるようにしています。

## 起動
以下のcurlリクエストで動かしてみます。
```
curl -X POST -H "Content-Type: application/json" --data '{ "query": "{ countries { name capital population } }" }' http://127.0.0.1:5001/hoge/graphql
```
何点かGraphQLのルールがあります。
- POSTリクエストであること
- データの指定はqueryの下に入れる必要があること
- 取得したいデータはリクエストに載せる必要がある
```
countries { name capital population }
```
取得した結果です。
```
{"data":{"countries":[{"name":"日本","capital":"東京","population":126476461},{"name":"アメリカ合衆国","capital":"ワシントンD.C.","population":331002651}]}}
```
リクエストで指定した通りのカラムが返却されています。

もう一つのAPIもやってみます。

```
curl -X POST -H "Content-Type: application/json" --data '{ "query": "{ country(name: \"日本\") { name capital } }" }' http://127.0.0.1:5001/hoge/graphql
```
今回は取得対象をname:日本に設定しています。
また、取得結果も名前と首都だけを返却するようにしてみました。以下が結果です。
```
{"data":{"country":{"name":"日本","capital":"東京"}}}
```
日本だけが取得できていますね。
そして返却されたデータもリクエストで指定したものだけが返却されています。
これにより不要な通信を発生させなくなります。

## おわりに
Restで慣れてる人からすると初めはとっつきにくいような気がします。自分がそうでした。
ただ変更にも強そうで結構いいなと感じました。