express-graphql + TypeScript で始めるGraphQL API Server

# はじめに

この記事は `express-graphql` で`Node.js` + `TypeScript` で簡単にGraphQL APIサーバを実装する
ハンズオンちっくな記事です。
実際に手を動かしてみてください🙏

ディレクトリ構造は下記のようになります。

```
.
├── src
│   ├── data
│   │   └── index.ts
│   ├── fields
│   │   ├── index.ts
│   │   └── member
│   │       ├── index.ts
│   │       ├── query.ts
│   │       ├── mutation.ts
│   │       ├── resolvers.ts
│   │       └── types.ts
│   └── index.ts
├── package.json
└── tsconfig.json

```

# 準備
## パッケージのインストール

実行は `ts-node` で行います。

```
yarn add @types/express cors express express-graphql graphql typescript
```

```
yarn add -D ts-node tsconfig-paths
```

## tsconfig.json

`alias` の登録をします。

```json:tsconfig.json
{
  "compilerOptions": {
    "sourceMap": false,
    "noImplicitAny": true,
    "module": "commonjs",
    "target": "es5",
    "lib": ["es2018", "dom"],
    "moduleResolution": "node",
    "removeComments": true,
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": false,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "strictFunctionTypes": false,
    "baseUrl": "./",
    "paths": {
      "@/*": ["src/*"],
    }
  },
  "include": [
    "./src/**/*.ts"
  ]
}
```

# GraphQL Query

メンバー一覧を取得するAPI を実装します。

## 実装
### Data

実際にこのDataは `SQL` などからDBの値を取得しますが、今回は DB との接続はなしで `JS` で用意します。

```js:src/data/index.ts
export const memberList = [
  {
    id: 1,
    name: 'Rachel',
    age: 29
  },
  {
    id: 2,
    name: 'Ross',
    age: 29
  },
  {
    id: 3,
    name: 'Joey',
    age: 29
  }
];  
```

### Types

型定義を `types.ts` として作成します。

```js:src/fields/member/types.ts
import { GraphQLObjectType, GraphQLNonNull, GraphQLString, GraphQLInt } from 'graphql';

export const memberType = new GraphQLObjectType({
  name: 'member',
  description: 'member',
  fields: {
    id: {
      type: new GraphQLNonNull(GraphQLInt),
      description: 'The Member ID.'
    },
    name: {
      type: new GraphQLNonNull(GraphQLString),
      description: 'The Member name.'
    },
    age: {
      type: new GraphQLNonNull(GraphQLInt),
      description: 'The Member age.'
    }
  }
});

```

### Resolvers

`Resolver` では何をレスポンスするかの処理を書きます。
この例ではメンバーリストを取得したいだけなので、そのまま `memberList` を返します。

```js:src/fields/member/resolvers.ts
import { memberList } from '@/data';

export const getMemberList = () => Promise.resolve(memberList);
```

### Query

`Query` は REST APIの `GET` に相当します。

```js:src/fields/member/query
import { GraphQLList } from 'graphql';
import { getMemberList } from '@/fields/member/resolvers';
import { memberType } from '@/fields/member/types';

export const memberQuery = {
  memberList: {
    type: new GraphQLList(memberType),
    description: 'Get list of members data.',
    resolve: getMemberList
  }
};
```

### src/fieles/member/index.ts

実装した `member` モジュールの `query` をまとめてエクスポートします。

```js:src/fields/member/index.ts
import { memberQuery as query } from '@/fields/member/query';

export const memberField = {
  query
};
```

### src/fields/index

実装したすべてのモジュールを `Root Query` としてまとめてエクスポートします。

```js:src/fields/index.ts
import { GraphQLObjectType } from 'graphql';
import { memberField } from '@/fields/member/';

export const queryType = new GraphQLObjectType({
  name: 'Query',
  description: 'The root query type.',
  fields: {
    ...memberField.query
  }
});
```

### Express

最後に `Express` でサーバを実装します。

```js:src/index.ts
import * as express from 'express';
import * as graphqlHTTP from 'express-graphql';
import { GraphQLSchema } from 'graphql';
import { queryType } from '@/fields/';

const PORT = 4000;
const app = express();

const schema = new GraphQLSchema({
  query: queryType
});

app.use(
  '/graphql',
  express.json(),
  graphqlHTTP({
    schema,
    graphiql: true
  })
);

app.listen(PORT, () => console.log('Listening on :4000'));
```

## 実行

### サーバ起動

下記コマンドでAPIサーバを起動します。

```bash
yarn ts-node -r tsconfig-paths/register src/index.ts
```

## 動作チェック

`localhost:4000/graphql` にアクセスすると、GraphiQL エディタが起動します。

下記クエリを入力して実行。`memberList` が取得できれば成功🎉

```graphql
query getMemberList {
  memberList {
    id
    name
    age
  }
}
```

<img width="883" alt="スクリーンショット 2019-12-09 02.43.18.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/207520/a1c00eaf-9eae-ee77-128b-f102927ee2a0.png">

# GraphQL Mutation

続いて `Mutation` を実装します。
この例では新メンバーを追加する `Mutation` を実装します。

## 実装

### Types

入力側のパラメータの型を追加します。

```js:src/fields/member/types.ts
export const memberCreateInput = new GraphQLInputObjectType({
  name: 'memberCreateInput',
  fields: {
    name: {
      type: new GraphQLNonNull(GraphQLString),
      description: 'The Member name.'
    },
    age: {
      type: new GraphQLNonNull(GraphQLInt),
      description: 'The Member age.'
    }
  }
});
```

### Resolvers

新メンバーを追加する処理を追加します。

```js:src/fields/member/resolvers.ts
export const createMember = ({ name, age }: { name: string; age: number }) => {
  const member = {
    id: memberList.length + 1,
    name,
    age
  };
  memberList.push(member);
  return memberList;
};
```

### Mutation

新たに `Mutation` を作成します。

```js:src/fields/member/mutation.ts
import { GraphQLNonNull, GraphQLList } from 'graphql';
import { createMember } from '@/fields/member/resolvers';
import { memberType, memberCreateInput } from '@/fields/member/types';

export const memberMutation = {
  createMember: {
    type: new GraphQLList(memberType),
    args: {
      member: {
        type: new GraphQLNonNull(memberCreateInput)
      }
    },
    resolve: (_: any, args: any) => {
      return createMember(args.member);
    }
  }
};
```

### src/fieles/member/index.ts

実装した member の `Mutation` をエクスポートします。

```js:src/fields/member/index.ts
import { memberQuery as query } from '@/fields/member/query';
import { memberMutation as mutation } from '@/fields/member/mutation';

export const memberField = {
  query,
  mutation
};
```

### src/fields/index

実装したすべてのモジュールを Root Mutation としてまとめてエクスポートします。

```js:src/fields/index.ts
import { GraphQLObjectType } from 'graphql';
import { memberField } from '@/fields/member/';

export const queryType = new GraphQLObjectType({
  name: 'Query',
  description: 'The root query type.',
  fields: {
    ...memberField.query
  }
});

export const mutationType = new GraphQLObjectType({
  name: 'Mutation',
  description: 'The root Mutation type.',
  fields: {
    ...memberField.mutation
  }
});
```

### Express

最後に `Schema` に `Mutation` を追加します。

```js:src/index.ts
import { queryType, mutationType } from '@/fields/';

const schema = new GraphQLSchema({
  query: queryType,
  mutation: mutationType
});
```

## 実行

### サーバ起動

下記コマンドでAPIサーバを起動します。

```bash
yarn ts-node -r tsconfig-paths/register src/index.ts
```

## 動作チェック

下記クエリを入力して実行。`memberList` に入力したメンバーが追加できれば成功🎉

```graphql
mutation createMember {
  createMember(member: {
    name: "Monica"
    age: 29
  }) {
    id
    name
    age
  }
}
```

<img width="1035" alt="スクリーンショット 2019-12-10 17.28.15.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/207520/e112a2f0-0290-460f-b2d9-5542336fa17f.png">

# さいごに
 
`GraphQL` のメリットとして、書いたコードがそのままドキュメントになることが挙げられます。
予め `description` を書くルールなどを定めておけばAPI ドキュメントを用意する必要がなくなります。

実際に手を動かして、`GraphQL` を体験してみてください!!!

以上