Function Compute + API GatewayでGraphQLのAPIを作成する

アリババクラウドのFunction Compute + API Gatewayの構成のAPIでGraphQLを使います。

GraphQLとは

エンドポイントごとに機能を持たせるのではなく、一つのエンドポイントに命令をPOSTしてその結果を得るようにしたものです。

具体例だとこんな感じになります。

$ curl -H "Content-Type: application/json" -X POST -d '
{
  "query": "{ messages { name body } }"
}
' http://xxxxxxxxxxxxxxxxxx-cn-shanghai.alicloudapi.com/

{"data":{"messages":[{"body":"Hello","name":"asmsuechan"},{"body":"World","name":"suechan"}]}}

私はここを参考にしました。

www.m3tech.blog

適当におググりくだされば分かりやすい記事がたくさん出てくると思うのでGraphQLそのものの説明はそこに任せます。

最小のコード

まず、一番小さなgraphqlのコードを書いてfunction compute上で動かします。

helloというクエリを投げると{ data: { hello: "world" } }という結果が得られます。

投げるクエリ: "{ hello }"
返ってくる結果: { data: { hello: "world" } }

このサンプルは公式GitHubのものです。

// index.js
const { hook } = require('fc-helper');
const {
  graphql,
  GraphQLSchema,
  GraphQLObjectType,
  GraphQLString
} = require('graphql');

const schema = new GraphQLSchema({
  query: new GraphQLObjectType({
    name: 'RootQueryType',
    fields: {
      hello: {
        type: GraphQLString,
        resolve() {
          return 'world';
        }
      }
    }
  })
});

const query = '{ hello }';

module.exports.handler = (event, context, callback) => {
  graphql(schema, query).then((result) => {
    callback(null, { statusCode: 200, body: result });
  });
});

graphql()の実行をhandler関数の中で行うだけです。簡単です。

なお、Function Compute + API GatewayでPOSTを実装するのは少しクセがあるのでこちらを参考にしてください。

ちょっと高度なAPIにする

では少し複雑にしてみます。以下のようなクエリと結果の組み合わせを想定してコードを書きます。

投げるクエリ: "{ messages { name body } }"
返ってくる結果: {"data":{"messages":[{"body":"Hello","name":"asmsuechan"},{"body":"World","name":"suechan"}]}}

messageという型を定義して、複数のmessageが返ってくるようにします。

// index.js
const { hook } = require('fc-helper');
const {
  graphql,
  GraphQLSchema,
  GraphQLObjectType,
  GraphQLList,
  GraphQLString
} = require('graphql');
const atob = require('atob');

const messages = [
  {
    name: 'asmsuechan',
    body: 'Hello'
  },
  {
    name: 'suechan',
    body: 'World'
  }
];

const schema = new GraphQLSchema({
  query: new GraphQLObjectType({
    name: 'RootQueryType',
    fields: {
      messages: {
        type: GraphQLList(
          new GraphQLObjectType({
            name: 'message',
            fields: {
              name: { type: GraphQLString },
              body: { type: GraphQLString },
            },
          }),
        ),
        resolve() {
          return messages;
        }
      }
    }
  })
});

module.exports.handler = (event, context, callback) => {
  const request = JSON.parse(event.toString('utf8'))
  const query = JSON.parse(atob(request["body"]))["query"]
  graphql(schema, query).then((result) => {
    callback(null, { statusCode: 200, body: result });
  });
};

実際に使う場合はresolve()でデータベースから全てのメッセージを拾ってくるgetMessages()のような関数を実行します。

curlで確認すると期待する結果が得られていることが分かりますね。

$ curl -H "Content-Type: application/json" -X POST -d '
{ "query": "{ messages { name body } }"}
'http://xxxxxxxxxxxxxxxxxx-cn-shanghai.alicloudapi.com/

{"data":{"messages":[{"body":"Hello","name":"asmsuechan"},{"body":"World","name":"suechan"}]}}

まとめ

Function Compute(Faas全般で言えることです)でGraphQLを使うと、RESTと違って複数のAPI Gatewayを立てることが必要ないので簡単に複雑なAPIを作ることができて幸せになれます。

しかし正直まだ実戦投入には尚早かなと思います。

API GatewayとFunction Compute、めちゃくちゃデバッグしにくい・・・

今回のソースコードはこちらに公開しています。

github.com