Nowを使って静的サイトとAPIを単一レポジトリで運用する
Nowを使ってGatsby.jsによるサイトとnode.jsによるAPIのデプロイを一元管理する
November 10, 2018
以前、Gatsby.jsを使って柏市議会の議事録を解析した結果を公開するサイトを作成しました。 その時の記事です。
そのサイトに、頻出共起単語(要するに文中で一緒に使われることが多い単語)を見れるようにする機能を追加するにあたって、ある単語の頻出共起単語リストを取得するAPIが必要になりました。
現状サイトをデプロイしているNetlifyにはLambda関数を実行できる環境があるので、それを利用して実現は可能です。
ただ、最近知った Now
というサービスを使ってみたかったので、静的サイトとAPIまとめてNowに移行してみました。
Now
Next.jsを開発しているZEITが提供しているサーバーレスのwebサイト|アプリ実行環境です。
デプロイが非常にシンプルで、xxx.jsやxxx.goなどにHTTPリクエストを扱うLambdaを実装して now
コマンドを叩くだけでデプロイが完了します。(厳密に言うとLambda実装がphpなのかnodeなのかgoなのか、といったビルドに関する設定は必要ですが)
ソースファイルをNow上でどのように扱うかはファイルごと(あるいは正規表現を使って表現されるファイル群)に定義します。 あるファイル(群)は静的サイトジェネレータを使ってビルド、あるファイルはLambdaに変換してWeb APIとして公開、あるファイル(例えば画像とか)はそのまま公開、といった設定が、一つのプロジェクト内で柔軟に設定でき、モノレポで管理している静的サイトやAPIをNow上にコマンド一発で展開できます。
Now CLIのinstall
公式サイト にinstall方法の記載があります。Desktopアプリもあるようですが、私はCloud9で開発しているので
npm install -g now
でCLIだけinstallしました。
Gatsbyによる静的サイトをNowに展開する
既存のGatsbyによる静的サイトをNowに展開します。
まず、プロジェクトルートに以下の内容をnow.jsonとして記述してください。
now.jsonは設定ファイルです。
builds
は、指定のファイル( src
)に対してどういったビルド処理を実行するか( use
)を指定するための設定です。
static-build
を利用すると、 src
に指定されたpackage.jsonに定義された now-build
を実行した後に、distディレクトリの内容を公開します。
{
"builds": [
{ "src": "package.json", "use": "@now/static-build" }
]
}
package.jsonの scripts
に "now-build": "gatsby build && mv public dist"
を追加します。
Gatsbyはビルド結果をpublicに吐き出すので、処理完了後renameしています。
あとは now
コマンドを実行するだけです。
議事録解析結果を取得するエンドポイントの指定が必要だったので、実行時に引数でビルド時に必要な環境変数を指定します。
now -b API_ENDPOINT="http://gateway.dataforkashiwa.net.s3-website-ap-southeast-1.amazonaws.com"
ビルド時に必要な環境変数は -b
で指定します。
初めてNowを利用するときは認証リンクをクリックさせるフローが走るので、指示通りにやってください。
これで、Gatsbyによって生成された静的サイトがNowにデプロイされます。
※特に設定をしない場合、デプロイされたソースや、ビルドや実行時に生成されるログは誰でも閲覧可能な状態になっています。ドキュメント を参考にして、必要に応じて公開されないようにしてください。
Lambda
共起単語を取得するAPIを定義するために、apiディレクトリを作成してcollocations.jsを定義します。
const CollocationData = require('./collocations_data');
const { parse } = require('url');
module.exports = (req, res) => {
const word = parse(req.url, true).query.word;
const result = CollocationData.fetchCollocations(word);
res.writeHead(200, {
'Content-Type': 'application/json'
});
res.write(JSON.stringify(result));
res.end();
};
collocations_data.jsに、共起単語を取得するための関数を定義します。
const { collocations } = require('./collocations.json');
module.exports = {
fetchCollocations: (word) => {
if(!word) { return []; }
const result = collocations[word];
if(!result) { return []; }
return result;
}
};
api/collocations.jsonに予め用意した解析結果のjsonファイルを置きます。
now.jsonの builds
に以下を追加。
{ "src": "apis/collocations.json", "use": "@now/node" }
now
を実行してデプロイします。 @now/node
を使うと、src
に定義された関数がNode.js実装のAPIとして公開されます。
これで、 /api/collcations.js?word=xxx
にアクセスすると共起単語のリストを返すAPIが公開されます。
routes
この状態だと、 apiディレクトリ以下のファイルを変更しただけなのに、デプロイのたびにGatsbyのビルドも再度走ってしまい無駄です。
apiディレクトリがGatsbyの src
に指定したpackage.jsonより下層のディレクトリにあるからと思われます。
これを避けるためには、siteディレクトリを作って、 Gatsby.jsで利用するソースをサブディレクトリに移動させます。
ただ、今度は /site
以下にデプロイされるようになります。静的サイト部分に /
からアクセスしたければ、now.jsonに以下を追加してください。
"routes": [
{ "src": "/api/(.*)", "dest": "/api/$1" },
{ "src": "/*", "dest": "/site" }
]
/api
以下へのアクセスはそのまま /api
以下にデプロイされたLambdaへアクセスさせ、それ以外のアクセスは全てGatsby.jsによるビルド結果がデプロイされた /site
以下にアクセスさせる設定です。
これで、 /api
でLambdaを実行させつつ、静的サイトを /
以下にデプロイすることができます。
テスト
apiにテストを書きたければ、 AVAなどのテストランナーを入れて実行すれば良いです。Lambdaはリクエストとレスポンスの2つを引数に取るので、それぞれ必要なインタフェースをモックするオブジェクトを用意するか、 mock-resを使ってください。 以下一例です。
api/package.json を用意します。
{
"scripts": {
"test": "ava"
},
"devDependencies": {
"ava": "1.0.0-rc.2",
"mock-res": "^0.5.0"
}
}
テストを api/test.js
に実装します。
import test from 'ava';
import MockRes from 'mock-res';
import CollocationData from './collocations_data';
import handler from './collocations';
test('共起単語リストを返す', t => {
const mockRequest= {
url: '/collocations.js?word=議会'
};
const mockResponse = new MockRes();
CollocationData.fetchCollocations = () => { return ['税金', '市長']; };
handler(mockRequest, mockResponse);
const result = mockResponse._getJSON();
t.is(result.length, 2);
t.true(result[0] === '税金' && result[1] === '市長');
});
apiディレクトリに移動して npm test
でテストを実行することができます。
プロジェクトの構成
以下のような構成になります。(ファイルについては抜粋)
- プロジェクトルート
- api ... APIとして公開するLambda実装
- collocations.js
- package.json
- test.js
- now.json
- .nowignore ... デプロイ時にNowにアップロードしないファイル、ディレクトリを指定するファイル(.gitignoreみたいなもの)
- site ... Gatsbyによる静的サイトのためのソース
- gatsby-config.js
- package.json
- src
- ...