サーバーレスでWebアプリを作ろうとするときによく私が使うZeit NowというWebサービスを使ったサーバーサイド開発(簡単なAPIの作り方)のはじめ方について全3記事で説明します。前回の記事では、Expressを用いたAPIの開発について説明しました。本記事では、ZEIT Nowの機能を使ってサーバーレス関数を作っていきます。
(例によってALIS Boot Campで度々説明が必要になるので参考資料です)
記事の目次はこちらです。
① ZEIT Nowへの登録
② Expressを用いたAPIの立ち上げ
③ サーバーレス関数の作成(本記事)
サーバーレス開発では、クラウドを使ってサービスを立ち上げますので、使ったリソース分だけお金がかかります。なので、CPUやメモリなどについて最低限の計算リソースで処理を実行することが非常に重要になります。AWSのAWS LambdaやGCPのCloud Functionsは、関数(Lambda関数と呼ばれます)の実行のために最低限必要なリソースを自動で割り当ててくれるFaaS(Function as a Service)と呼ばれるサービスで、サーバーレス開発ではよく使われます。前回の記事でAPIについて説明しましたが、1回のAPIへのリクエストが1回のLambda関数の実行に対応しており、各リクエストに対して計算リソースが割り当てられ、関数が処理されて結果が返されます。リクエストがない(アクセスがない)時には一切課金されません。このリソースの柔軟性が、FaaSが好まれる理由です。ZEIT Nowにもこのような仕組みがあり、AWS Lambdaのようにサーバーレスな関数を作ることができます。
参考文献:サーバーレスのメリット&本質を、AWS Lambdaを使って理解しよう
それでは早速作っていきましょう。前回と同様、適当なディレクトリを用意します。
mkdir api-sample-now
cd api-sample-now
次に、前回の記事と同じようにpackage.jsonを作り、expressとnode-fetchをインストールします。
npm init
npm install express node-fetch
package.jsonの例としては以下のような感じです。
{
"name": "お好きなプロジェクト名",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"express": "^4.17.1",
"node-fetch": "^2.6.0"
}
}
次に、now.jsonというZEIT Nowの設定ファイルと作ります。以下のように書きます。
{
"version": 2,
"name": "お好きなプロジェクト名",
"builds": [
{ "src": "api/**/*.js", "use": "@now/node" }
]
}
"version"は、Nowのバージョンを指定します。最新は2です。v1.0は、もうすぐデプロイができなくなり、2020年8/7には今デプロイされているプロジェクトも全て削除されることが2020年4月にアナウンスされました。
"name"はプロジェクト名を指定します。
"builds"ではそのファイルをどの言語でビルドするかを指定します。"src"はビルドするソースファイルで、"api/**/*.js"はファイルがある場所をワイルドカードを使って指定してます。"use" は使用するビルダを指定することができ、今回は@now/nodeを使用します。
前回の記事ではapp.jsに全てのAPIをapp.jsに書き込んでいました。Lambda関数では、各APIを別々のファイルに保存します。ファイルの場所が、先ほどnow.jsonで指定した"src"の"api/**/*.js"に対応しています。
まず準備として、app.jsという名前のファイルを用意し、以下のように書きます。
const exp = require("express");
const app = exp();
module.exports = app;
最初の2行は前回の記事でapp.jsに書き込んだものと同じ、expressのモジュールのインポートとインスタンス化に関する部分です。最後の1行は、インスタンスを他のファイルから読み込みできるようにしています。app.listen()に相当する部分はNowが自動でやってくれるので必要ありません。これを適当な場所(今回はutil/app.js)に置きましょう。
次に、apiというディレクトリを作り、さらにその中にhello-worldというディレクトリを作りましょう。最後にhello-worldのディレクトリ内にindex.jsという名前でファイルを作ります。中身は以下のようにします。
const app = require("../../util/app");
app.get("*", (req, res) => {
res.send("hello world!");
});
module.exports = app;
一行目は、先ほど作成したutil/app.jsからエクスポートしたappインスタンスを読み込んでます。
app.get()の部分は前回の記事で説明したものと同じですが、前回の記事ではapp.get()の第一引数が"/hello"という文字列でAPIのアクセス先(エンドポイント)を指定していたのに対して、今回はエンドポイントを”ファイルの場所”で設定しており(api/hello-world/index.js)、app.get()の第一引数が"*"であることが違いです。また、最後の行でappインスタンスをmodule.exportsで書き出すのも必須なのでご注意下さい。
現在のファイル構成は以下のような感じです。
api-sample-now
├── api
│ └── hello-world
│ └── index.js
├── now.json
├── package.json
├── package-lock.json
├── node_modules
└── util
└── app.js
それではひとまずこの最小構成でデプロイしてみましょう。まずは、api-sample-nowのディレクトリ内で、以下のコマンドを実行します。
now
nowのコマンドを使うには、こちらの記事でZEIT Nowへのユーザー登録とCLIのインストールを行う必要があります。
以下のようなメッセージが出たらOKです。これで、nowのアカウントと現在のディレクトリがリンクされます。
Now CLI 17.1.1
🔍 Inspect: https://zeit.co/ユーザー名/プロジェクト名/96zge3abt [2s]
✅ Preview: https://プロジェクト名.ユーザー名.now.sh [copied to clipboard] [7s]
📝 To deploy to production (プロジェクト名.now.sh), run `now --prod`
次に、ローカルで動かしてみましょう。
now dev
以下のようなメッセージが出ればOKです。(Ctrl+Cでアプリが停止されます)
> Ready! Available at http://localhost:3000
http://localhost:3000に続けて、Lambda関数のファイル場所である/api/hello-worldを繋げると、そのLambda関数を実行できます。
http://localhost:3000/api/hello-world
ブラウザからアクセスしてhello worldという文字列が出たら成功です。
無料版では、1日に本番環境へデプロイできる回数には100という制限があるため、ZEIT Nowで開発するときは、now devを使ってローカルでテストすることをお勧めします。
次に、前回の記事でも追加した/api/opensea-assetsのAPIを追加してみましょう。apiディレクトリ内にopensea-assetsという名前のディレクトリを作成し、その中にindex.jsというファイルを作ります。
現在のファイル構成は以下のような感じです。
api-sample-now
├── api
│ └── hello-world
│ └── index.js
│ └── opensea-assets
│ └── index.js
├── now.json
├── package.json
├── package-lock.json
├── node_modules
└── util
└── app.js
api/opensea-assets/index.jsのファイルの中身は以下のようにします。
const app = require("../../util/app");
const { parse } = require("url");
const fetch = require("node-fetch");
app.get("*", (req, res) => {
const { query } = parse(req.url, true);
const { owner } = query;
const url = `https://rinkeby-api.opensea.io/api/v1/assets/?owner=${owner}&format=json`
fetch(url)
.then((result) => result.json())
.then((data) => {
res.send(data);
})
.catch(() => {
res.status(401).end();
});
});
module.exports = app;
app.get()の第一引数が"*"であること、最後の行のmodule.exports = app;にご注意ください。保存できたら、now devで動作確認します。
http://localhost:3000/api/opensea-assets?owner=0x9ba9105d4a1cde23fa2ae25c67dea928b975d729
動作確認ができたら、本番環境にデプロイしてみましょう。nowコマンドを--prodオプションをつけて実行します。
now --prod
成功すると、
✅ Production: https://プロジェクト名.now.sh [copied to clipboard] [23s]
のようなProduction: に続くアドレスがメッセージに出てきますので、
https://プロジェクト名.now.sh/api/hello-world
にアクセスすると、本番環境で動いているのがわかるかと思います。また、ZEIT Nowのダッシュボードでもステータスの確認ができます。
以上です。お疲れさまでした。
簡単ですが、サーバレス関数をAPIとしてZEIT Nowにデプロイする手順について説明しました。手順通りに進めれば非常に簡単にできますが、サーバレス関数を全く知らないとnow.jsonの設定の仕方やフォルダ構成など、結構詰まる箇所が多いと思います。今回3回に分けて、なるべく初心者にも「まずはとりあえず動かしてみる」ことができるように意識して記事を執筆しました。この記事がどなたかのお役に立ちましたら幸いです。
クレジット(画像素材):