【入門】Netlify Functionsコトハジメ

# 前書き
Netlify Functionsの入門記事になります。
導入するにあたり色々調べても日本語の記事が少なかったり、Nuxt.jsと組み合わせてどうたらみたいなNetlify Functionsの事に絞られてない記事が多かったので自分用の備忘録も兼ねて。
前提としてJavaScriptチョット書ける、npm使える位の人からを対象とした記事です。

# この記事を読むと
・Netlify Functionsの使い方が何となく分かるようになる。
・ローカルでNetlify Functionsのエミュレートが出来るようになる。
・Netlify Functionsを介して外部のAPIとやり取り出来るようになる。
・Netlifyでの環境変数の設定の仕方が分かるようになる。

# Netlify Functions is 何
Netlifyが提供しているアドオンの1つ。
**FaaS（Function as a service）**と呼ばれるもので、機能（Functions）を実行できる環境を提供してくれるサービスです。
AWS Lambdaが基盤になっており、実行環境はNode.js 8.10がデフォルトで設定されています（執筆時点）。
10.x系を使いたい場合は、`AWS_LAMBDA_JS_RUNTIME`の環境変数に`nodejs10.x`を指定すればオーバーライドできます。
（`AWS_LAMBDA_JS_RUNTIME`の環境変数で指定できる値は、[AWS Lambda runtime](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/lambda-runtimes.html)に記載されているものになります。）

# Netlifyとリポジトリの連携
何はともあれNetlifyとリポジトリを連携させないと話が始まらないので連携させましょう。
連携させるだけなら[NetlifyのTOPページ](https://www.netlify.com/)から流れに沿って進めるだけで特に難しいところもないので、細かい連携の方法については割愛します。
既にNetlifyと連携しているリポジトリを使うのであれば、Netlify Functionsのために新規でリポジトリを作る必要はありません。

# 環境構築

## Netlifyの設定ファイルを作成する
Netlifyの設定ファイルである**netlify.toml**ファイルをルートディレクトリに作成します。
ここに書く設定はNetlifyの管理ページからでも設定できますが、後述するローカルでのエミュレートにこのファイルが必須になります。

```toml:netlify.toml
[build]
  command = "npm run build"
  functions = "dist/api"
```

`command` = Netlifyデプロイ時に実行されるコマンド
`functions` = Netlify Functionsのエンドポイント対象ファイルを格納するディレクトリ

## ローカルでエミュレートできるようにする
[netlify-lambda](https://www.npmjs.com/package/netlify-lambda)を使えばローカルでNetlify Functionsのエミュレートをすることが出来ます。
これが無いと変更→コミット→デプロイ→確認というフローを毎回踏むことになって苦しいです。
後から導入することも出来ますが、Netlifyの設定の仕方によっては色々面倒なことになりかねないので最初に入れておくのがオススメです。

まずはルートディレクトリで

```sh
npm init
```

を実行して**package.json**を作成して

```sh
npm i netlify-lambda
```

でインストールしたら、**package.json**に実行用のスクリプトを追記します。

```json:package.json
"scripts": {
  "dev": "netlify-lambda serve resources/api（ソースが格納されているフォルダ名）",
  "build": "netlify-lambda build resources/api（ソースが格納されているフォルダ名）"
}
```

`netlify-lambda serve <folder-name>`で、ローカルで確認できるサーバを立ち上げてくれると同時に、対象としているディレクトリの中身がコンパイルされて、**netlify.toml**の`functions`で指定しているディレクトリに吐き出されます。

デフォルトで使用するポートは9000番ですが、`--port`オプションで変更することもできます。

```json:port指定の例
"dev": "netlify-lambda serve resources/api --port 9001"
```

ここまでで最低限の環境構築は完了です。

# Hello Worldが返ってくるAPIを作る
まずはサンプルとして**Hello World**の文字列が返ってくるAPIを作ってみます。
先ほど`netlify-lambda`の対象に設定したディレクトリ（今回の場合**resources/api**）の中にjsファイルを作成しましょう。
今回はとりあえず**hello.js**という名前で作成しました。
このファイル名がエンドポイント名になるので実制作の際は命名に気をつけた方がいいです。
**例：hello.js = /.netlify/functions/hello**

## hello.jsの中身
基本的には[公式ドキュメント](https://www.netlify.com/docs/functions/#javascript-lambda-functions)と全く同じような内容になります。

まずお作法として以下のように`handler`メソッドをエクスポートする必要があります。

```js:hello.js
exports.handler = function(event, context, callback) {
    // ここに中身の処理を記述
}
```

とりあえず**Hello World**を返すだけなので、中身の処理はこれだけです。
`callback`関数の第2引数に、ステータスコードとbodyの情報を渡すだけ。
第1引数はエラーの時用なので今回はnullにしてます。

```js:hello.js
callback(null, {
  statusCode: 200,
  body: 'Hello World'
});
```

ここまで終わったら

```sh
npm run dev
```

でローカルサーバーを立ち上げて、**localhost:9000/.netlify/functions/hello**にアクセスすると**Hello World**が表示されているはずです。

### eventパラメータに入っているもの

```json:event
{
    "path": "リクエストのパス", 
    "httpMethod": "リクエストのメソッド（GETとかPOSTとか）"
    "headers": {ヘッダ情報}
    "queryStringParameters": {クエリパラメータの情報}
    "body": "リクエストの文字列"
    "isBase64Encoded": "リクエストがBase64でエンコードされているかどうか"
}
```

### contextパラメータに入っているもの
[Netlify Identity](https://www.netlify.com/docs/identity/)を使用している場合にユーザーのコンテキスト情報が返ってきます。（使用していない場合は空の配列）
私自身がNetlify Identityを使用したことがないのであまり詳しいことは分からないです、スミマセン。

### asyncを使えば簡潔に書ける

`async`を使うことでより簡潔に書くことができます。
（実行環境をNodejs6.10に指定していると使えないので注意）

```js:hello.js
exports.handler = async () => {
  return {
    statusCode: 200,
    body: 'Hello World',
  };
};
```

## コミットして動いているか確認

ここまでの設定が終わったらコミットして本番でも動いているか確認して見ましょう。
デプロイが終わった後、管理ページの**Functions**の中に**hello.js**の項目が出来ていたら無事に認識されています。
<img width="1225" alt="Functions___functionstest.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266579/fe8b4e21-7a5c-ab6c-c395-97abab7eda70.png">

**hello.js**の項目をクリックするとログを見られる画面に遷移します。
<img width="1274" alt="Function_hello___functionstest.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266579/1897a218-b91c-a383-9e94-1c7446e8f5c8.png">

**Function log**のところに実行された履歴や、JS内に`console.log`を書いていればその内容が表示されます。
**Endpoint**のURLにアクセスして、画面に**Hello World**が表示されていれば本番でも問題なく動いています。

# Netlify Functionsの中で外部APIを扱う

外部APIを使用する時にAPIのキーをフロント側に持たせたくないというのはあるあるだと思うのですが、Netlifyの環境変数と組み合わせれば幸せになれます。

## 環境変数の設定（Netlify側）

環境変数の設定は**Settings > Build&deploy > Environment**から行えます。
<img width="1295" alt="Build___deploy___Settings.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266579/fb460dc1-a4d1-93cb-3197-b8678e303bf6.png">

ここで`key`と`value`を設定しておけば、`process.env.KEY`のような感じで環境変数を取得することが出来ます。

## 環境変数の設定（ローカル側）
セキュアなものでなければ**netlify.toml**に以下のように追記することで、ローカルでも環境変数を使用できます。

```toml:netlify.toml
[build.environment]
  KEY="hogefuga"
```

:warning: tomlファイルには数値を書くことができないので、値を””で囲むことを忘れずに。

セキュアな情報を扱いたい場合は**.env**ファイルを使用します。
まずは`dotenv-webpack`をインストールして

```sh
npm i dotenv-webpack
```

`netlify-lambda`が使用しているwebpackの設定を上書きするために**webpack.functions.js**ファイルを作成します。
中身は以下の通り。

```js:webpack.functions.js
const Dotenv = require('dotenv-webpack')

module.exports = {
  plugins: [new Dotenv()],
}
```

あとは**.env**ファイルを作成して中にキーと値の情報を書いて

```:.env
KEY=value
```

`dev`コマンドに`--config`オプションを追加してwebpackに追加のモジュールを読み込ませるようにします。

```json:package.json
"dev": "netlify-lambda serve resources/api --config ./webpack.functions.js",
```

これでローカルでも環境変数が使えるようになったはずです。
:warning: **.env**ファイルをignore設定するのを忘れないように。

## axiosのインストール

```sh
npm i axios
```

で`axios`を追加。

あとはJSファイルの中で`require`してあげれば使えます。

```js
const axios = require('axios');
```

:warning: babelを通してないので`import`は使えません。

環境変数とaxiosを組み合わせたサンプルとしては以下のような感じになります。

```js
const axios = require('axios');

exports.handler = async () => {
  const URL = `https://api.com?key=${process.env.API_KEY}`;
  return axios.get(URL)
    .then(({ data: data }) => ({
      statusCode: 200,
      body: JSON.stringify(data)
    }))
    .catch(e => ({
      statusCode: 400,
      body: e
    }));
};
```

### node-fetchは駄目っぽい
`axios`の代わりに`node-fetch`を使ってみようとしたところ、ローカルでは動くもののNetlifyでデプロイ時にコケてしまい駄目でした。（`netlify-lambda`を使っていない状態なら大丈夫だったので、コンパイル時に何か問題が起こってる？）

色々調べ回っていた時にどこかのIssueで、`node-fetchじゃなくてaxiosの使用を推奨しています。`みたいな事を書いているのを見かけたのでそういう事なのかなと。

#おしまい
ざっくりとした説明でしたが以上になります。
Netlifyをお使いの方はぜひお試しください。