まず、今回紹介するのは Node.js + Express で作った API を CORS 対応にする、という、これ自体はシンプルな内容なのですが、この話を考えるに至った経緯を最初にまとめておきます。


【背景】
普段からウェブアプリケーション・サービスを開発しています。詳しいアーキテクチャはともかく、ユーザーのアクセス先となるフロントエンドのアプリケーション・サーバーとバックエンド(データベースなど)があって、クラウドっぽくバックエンドには API サーバー経由でアクセスする、という形態を多く採用しています。

この形態を採用している時に限らない話ですが、「サービスの安定運用」を考えると「いかにフロントエンドを安定させるか」を考慮する必要があります。ネットワークやバックエンド含めたサービス全体のどこかに不具合が生じた場合であっても、ユーザーが最初にアクセスするフロントエンドが動いていれば「画面に障害発生メッセージを出す」ことができるようになります。逆にフロントエンドにアクセス過多を含めた不具合が発生してしまうと、「メッセージで利用者に不具合が発生していることを知らせる」ことすらもできなくなってしまいます。

このフロントエンドサーバーを安定稼働させるための技術として、最近は(docker などの)コンテナ技術であったり、(k8s や OpenShift などの)コンテナのオーケストレーション技術が流行っています。ここまでは「いわゆる一般論」的な話です。

一般論を理解した所で、技術者としての「一般論ではない話」も考えます。自分は業務でもプログラミングや作ったりサービスの運用を行ったりしていますが、業務外でも(つまり個人でも)プログラミングしたり、作ったサービスを公開して運用したりします。2つの異なる立場を持っているわけです(決して珍しくないと思ってます)。前者ではある程度の予算の中でクラウドのサービスを契約したりして、必要であればベンダーが提供するコンテナやコンテナ・オーケストレーションも使って構築することになります。 一方後者では、これらのインフラ構築部分も自腹になるわけです。まあ「これも授業代」と太っ腹に考える人は立派だと思いますが、コンテナ・オーケストレーションまで使おうとするとそれなりに懐も痛む価格だったりします。


要するに「個人開発者としての自分はケチ」なわけで(繰り返しますが、決して珍しくないと思ってますw)、「ケチはケチなりに知恵と作業でなんとかしたい」と考えるわけです。コンテナ技術やコンテナ・オーケストレーション技術を否定するつもりは全くありませんが、「より安価」に「フロントエンドを安定稼働」させる方法はないものか、と:
2021032001


#「コンテナ・オーケストレーションまで自分で構築すればいい」と考える人もいると思うので一応コメントを。技術的にはそのとおりなんですが、目的はあくまで「安価なフロントエンドの安定稼働」です。そう考えると例えば1ノードで構築した場合に目的を達成しているといえるか・・・ では複数ノードを構築する場合、今度は安価といえるのか・・・ となってしまうと考えました。


そんな自分にひらめいた1つの案が「フロントエンドを GitHub Pages にする」方法です。GitHub Pages は GitHub の無料アカウントがあれば使うことのできる「静的ウェブコンテンツの公開サービス」です。GitHub の一部として考えると、容量は(ほぼ)無制限で、アップロード直後にバックアップされ、世界中のウェブサービスの中でも指折りの安定稼働を誇っています。「フロントエンドを安定させたい」という目的だけを考えると、「かなり使える案」だと思っています:
2021032002
(↑フロントエンドを GitHub Pages にして、バックエンドを IBM Cloud にする場合)


もちろんこの方法は万能ではありません。まず GitHub Pages で公開できるコンテンツはウェブアプリケーションではなく「静的ページ」、つまり HTML ページに限られます(この時点で i18n などを考慮するアプリケーションページの公開は難しくなります)。表示データは REST API で取得すれば良いのですが、フロントエンドが静的ページである以上、通信は AJAX に限られてしまいます。その結果、API 側は(クロスオリジン通信をすることになるため)CORS を考慮した設計が必須となります:
2021032003


フロントエンドはウェブアプリケーションではないので、ウェブページをテンプレートから作る、といった便利な手法が使えず、AJAX を駆使したレンダリングを実装しないといけないことも不利な点となりますが、バックエンド側も考慮点の影響が大きな方法ではあると思っています。ただ「安価にフロントエンドを安定稼働」させる面ではイケそうな方法にも感じています。


・・・といった背景がありました。この前提だと REST API 部分も便利なサービスを有償契約して使うのではなく、安価なアプリケーション・サーバーを使って、自前で API サーバーを構築する必要があります(最悪、ここにトラブルがあってもフロントエンド側ではその旨を伝えることができる構成)。というわけで、無料のライトプランが使える IBM Cloud で「Node.js + Express で作った API を CORS 対応にする」ための方法を理解しておく必要がありました。


【Node.js + Express の REST API を CORS 対応する】
こちらはサンプルを用意しておきました:
https://github.com/dotnsf/express-cors


まず settings.js の exports.cors 配列変数内にクロスオリジン通信を許可するオリジン(ドメイン)を登録しておきます。デフォルトでは以下のようになっていて、'http://localhost:8080' と 'https://dotnsf.github.io' からの AJAX 通信を許可するよう設定しています。前者はローカルでの動作確認用ですが、実際に Github Pages で運用を始めた後は削除しておくべきです。後者は僕の Github Pages のドメインです。実際にみなさんが Github Pages でこの API を使う場合は自分自身の Github Pages ドメインに変更してください:
//. settings for CORS
exports.cors = [ 'http://localhost:8080', 'https://dotnsf.github.io' ];

本体とも言える app.js の内部は以下です:
//. app.js
var express = require( 'express' ),
    app = express();

var settings = require( './settings' );

app.use( express.static( __dirname + '/public' ) );

//. CORS
if( settings && settings.cors && settings.cors.length && settings.cors[0] ){
  var cors = require( 'cors' );
  var option = {
    origin: settings.cors,
    optionSuccessStatus: 200
  };
  app.use( cors( option ) );
}

app.get( '/ping', function( req, res ){
  res.contentType( 'application/json; charset=utf-8' );
  res.write( JSON.stringify( { status: 'OK' } ) );
  res.end();
});


var port = process.env.PORT || 8080;
app.listen( port );
console.log( "server starting on " + port + " ..." );

赤字以外の部分はごく普通の Node.js + Express を使った REST API アプリケーションです。この例では GET /ping という動作確認用のシンプルな API を1つだけ定義しています(サーバー側に障害等が起きていなければ、返り値は常に { status: 'OK' } という JSON です)。

肝心の CORS の対応をしているのが赤字部分です。settings.js の内容を確認し、exports.cors 配列に有効な値が1つでも含まれていると判断した場合は、cors ライブラリを使って設定されているオリジンを対象に CORS を有効にします。ちなみに settings.js の exports.cors が例えば null とか [](空配列)とかに設定されている場合は CORS の処理が行われないので、CORS の処理としてはデフォルトの「全てのクロスオリジンからの AJAX アクセスを許可しない(同一オリジンからの AJAX アクセスのみ許可する)」ことになります。

あとは必要に応じてベーシック認証を加えるとか、トークン認証を加えるとか(フロントエンドが静的コンテンツだとトークンの管理が面倒そうだけど)するなどして CORS 対応の REST API を構築することができます。ここは手順がわかってしまえば簡単そうですね。


【運用時】
こんな感じでデータベースへの読み書き検索などが REST API 化され、CORS 対応までできていれば Github Pages の HTML からも利用できるので、かなり安定したフロントエンドコンテンツを実現できそうです。この形で REST API が実現できていると、例えばウェブアプリを作るハンズオンでもあらかじめ用意した REST API を Github Pages から呼び出す形で実現できるので、フロントエンドの運用サーバーは無料で(しかもかなり安定したものを)用意できます。更にフロントエンド部分の開発時にはこんなフォルダ構成の Node.js + Express のアプリケーションにしておくと docs/ 以下の静的コンテンツを対象に作って、ローカルで動作確認もして、コードごと Github にあげて docs/ 以下を Github Pages で公開する、といった便利な開発・運用も可能になります:
//. app.js
var express = require( 'express' ),
    app = express();

app.use( express.static( __dirname + '/docs' ) );

var port = process.env.PORT || 8080;
app.listen( port );
console.log( "server starting on " + port + " ..." );
(↑メインファイル。静的コンテンツのフォルダを /docs に指定している以外はごく普通のシンプルな Express アプリ)


2021032004
(↑このプロジェクトを Github に上げる)


2021032005
(↑Github Pages の設定で /docs フォルダ以下を公開するよう指定する)


フロントエンドコンテンツは全て AJAX 対応させる必要があり、昨今の流行りとは違う形での実装は必要になります。それはそれで大変だし、普通のウェブアプリケーションで使える便利な技術も使えなくて不便な面も出てくるのですが、"Poorman's stable web contents" みたいに割り切って使うネタを準備しています。まだアイデア先行な面もあって実証実験が必要な段階だと思っていますが、運用していて気づくことがあったらまたこのページに追記します。