AWS Amplify mock apiでsubscriptionを快適に使用するためにプロキシ環境を作成したよ(NextJS)


経緯

  • Amplify mock api は、ローカル開発を加速させることができるので非常に便利
  • しかし、現状(2022年3月)時点では、チュートリアルに書かれた方法では動作しない
  • AppSyncを使いたい大きな動機が、subscriptionであり、どうしてもローカルでシミュレートしたい

対応方法

  • 結局のところ、mock apiのサーバ自体はsubscriptionに対応している
  • しかし、aws-sdkが未対応のため、誤ったエンドポイントに通信しており使用できない状況
  • ローカルにプロキシを立てることで、sdkからmock apiへの通信を修正すれば使用できる

動作検証バージョン

"aws-amplify": "^4.3.14"
"next": "^11.1.3"

詳細

mock api はsubscriptionに対応していない?

元々subscriptionが動作しないというバグがあったようだが、修正されているとのことでした。
https://github.com/aws-amplify/amplify-cli/issues/6026

しかしmock apiを動作させると分かりますが、どうもクライアントからの通信先がおかしいです。
aws-sdkの修正状況にずれが生じていることが原因のようです。

issueでは、subscription の mock api 動作時に/graphqlではなく、/graphql/realtimeへアクセスしてしまっていることが問題のため、rewritesなどして向き先を変える必要があるとのことでした。
https://github.com/aws-amplify/amplify-cli/issues/9621

URLの書き換えをするだけなのだが。。

/graphql/realtimeへのアクセスを/graphqlに書き換える(/graphqlへのアクセスはそのままにする)だけで、問題は解決しそうです。

NextJSを使用しているなら、rewritesの設定するだけということでパッとやってみましたが、うまく動作しませんでした。また、rewriteは気軽に使用しない方がいいという記事も発見しました。
https://zenn.dev/catnose99/scraps/91c31ee6609929

そもそも、 テスト環境作るためだけに、rewrite設定なんて入れたくない ため、別の手段を考えることにしました。

nodeモジュールでプロキシを立てる

nginxなど、プロキシアプリを別に設定することも考えましたが、NextJSプロジェクトの中で(node_modulesで)、管理できると便利だと考え、以下のようにカスタマイズしてプロキシを立てることとしました。

プロキシの方針

  • node-http-proxyをベースにする
  • URL書き換えのオプションがなかったのでカスタマイズする
  • package.jsonにコマンド記載してyarn等で起動管理する

node-http-proxyをカスタマイズ

カスタマイズ結果はblueoath-video-ec/node-http-proxyを参照。

pathRewriteオプションを新設して、正規表現でURL書き換えを行います。

lib/http-proxy/common.js
  if (options.pathRewrites && (options.pathRewrites instanceof Array)) {
    for (var i = 0; i < options.pathRewrites.length; i++) {
      console.log(outgoing.path);
      var from = options.pathRewrites[i].from
      var to = options.pathRewrites[i].to
      outgoing.path = outgoing.path.replace(new RegExp(from, 'g'), to);
    }
  }

使い方は以下のような感じです。

proxy.js
var proxy = new httpProxy.createProxyServer({
  ...
  pathRewrite: [
    { from: "^/graphql/realtime", to: "/graphql" }
  ],
  ...
});

package.jsonへの組み込み

まずは、カスタマイズしたライブラリを追加します。
package.jsonに以下のように追加してyarn installを実行します。

package.json
  "devDependencies": {
    ...
    "node-http-proxy": "git+https://github.com/blueoath-video-ec/node-http-proxy.git",
    "kill-port": "^1.6.1",
    ...
  }

yarnを使用していますが、適宜npmなどに読み替えてください。
カスタマイズしたライブラリは適宜自分の環境にforkするなどしてください。

プロジェクトのルートにproxy.jsonを配置して、プロキシサーバの起動コードを記載します。

サンプルコード
proxy.js
// NOTE: カスタマイズしたProxyを使用していることに注意(pathRewrite追加している)
// https://github.com/http-party/node-http-proxy
// https://github.com/blueoath-video-ec/node-http-proxy
var http = require('http');
var httpProxy = require('node-http-proxy');

var mockAPIServerPort = 20002
var proxyPort = 8015

//
// Setup our server to proxy standard HTTP requests
//
var proxy = new httpProxy.createProxyServer({
  target: {
    protocol: 'http',
    host: 'localhost',
    port: mockAPIServerPort,
    path: '/',
  },
  pathRewrite: [
    { from: "^/graphql/realtime", to: "/graphql" }
  ],
  changeOrigin: true
});
var proxyServer = http.createServer(function (req, res) {
  proxy.web(req, res, function (err) {
    // Now you can get the err
    // and handle it by your self
    // if (err) throw err;
    console.log("http error === ")
    console.log(err);
  });
});
//
// Listen to the `upgrade` event and proxy the
// WebSocket requests as well.
//
proxyServer.on('upgrade', function (req, socket, head) {
  proxy.ws(req, socket, head, function (err) {
    // Now you can get the err
    // and handle it by your self
    // if (err) throw err;
    console.log("ws error === ")
    console.log(err);
    socket.close();
  })
});

console.log("mockAPIServerPort: " + mockAPIServerPort)
console.log("proxyPort: " + proxyPort)

proxyServer.listen(proxyPort);

mockAPIServerPortは、mock apiサーバのポートです。
proxyPortは、今回作成したプロキシサーバのポートです。

プロキシサーバ起動オプション追加

package.jsonに以下を追記します。

package.json
  "scripts": {
    ...
    "mock:api": "yarn kill-amplify-mock-api && amplify mock api",
    "mock:proxy": "node ./proxy.js",
    "kill-amplify-mock-api": "kill-port 20002 && kill-port 20003"
  },

これでyarn mock:apiと打つと、mockサーバを停止して、プロキシサーバが起動されます。

アプリから使うぞ!

プロキシサーバを立てても、それだけではmock apiを使用できません。
Amplify.configureにおける、aws-exportsの情報を書き換えて、エンドポイントがプロキシサーバに向くように修正が必要です。コンフィグ設定についてはここを参照。

somewhere-in-the-app.tsx
import awsExports from '../aws-exports'

//Amplify.configure({ ...awsExports, ssr: true })
let usingProxy = awsExports
usingProxy.aws_appsync_graphqlEndpoint = 'http://localhost:8015/graphql'
Amplify.configure({ ...usingProxy, ssr: true })

上記の設定は、 ローカルデバッグの時のみ実施 する必要があります。
環境変数等で、コードの有効無効を切り替えてください。

localhost:8015のポートは、proxy.jsで設定したproxyPortの値を設定します。
なお、amplify mock api 実行により、aws-exports.jsの値が自動で書き変わります(mock apiの便利なところ)。

aws-exports.js
const awsmobile = {
    ...
    "aws_appsync_graphqlEndpoint": "http://192.168.12.8:20002/graphql",

上記の:20002はmock api サーバのエンドポイントです。
これを

somewhere-in-the-app.tsx
usingProxy.aws_appsync_graphqlEndpoint = 'http://localhost:8015/graphql'

でプロキシサーバのエンドポイントに上書き変更しています。

まとめ

AWS Amplify mock apiでsubscriptionは動作しますが、一工夫必要です。
Amplify(AppSync)は、subscriptionがあるから使ってるようなものなので、ローカル環境でmock apiが動くととても快適に開発が進められます。多少のモック環境用コードを付与する必要がありますが、やる価値はあると思いますので、ぜひお試しください。