Help us understand the problem. What is going on with this article?

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

はじめに

この記事は express-graphqlNode.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 の登録をします。

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 で用意します。

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 として作成します。

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 を返します。

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

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

Query

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

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 をまとめてエクスポートします。

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

export const memberField = {
  query
};

src/fields/index

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

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 でサーバを実装します。

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サーバを起動します。

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

動作チェック

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

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

query getMemberList {
  memberList {
    id
    name
    age
  }
}

スクリーンショット 2019-12-09 02.43.18.png

GraphQL Mutation

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

実装

Types

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

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

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

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 を作成します。

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 をエクスポートします。

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 としてまとめてエクスポートします。

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

最後に SchemaMutation を追加します。

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

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

実行

サーバ起動

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

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

動作チェック

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

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

スクリーンショット 2019-12-10 17.28.15.png

さいごに

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

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

以上

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away