Nuxt.js + Netlify Functions を TypeScript で構築する

# この記事の目標
- Nuxt.js + TypeScriptで実装
- Netlifyにホスティング
- Netlify FunctionsをTypeScriptで実装
- Nuxt.jsからNetlify Functionsを呼び出す

# 動作確認環境
| 項目 | バージョン |
|:-:|:-:|
| macOS | 10.13.6 (High Sierra) |
| node | 13.8.0 |
| npm | 6.13.7 |

# ソースコード
https://github.com/nishitaku/nuxt-netlify-ts

# 1. Nuxt.js
まず、フロントエンド環境から構築していきます。

## ボイラーテンプレート生成
`create-nuxt-app`を使います。

2020年3月18日にリリースされたv2.15.0から、TypeScriptをサポートしているので、質問に答えるだけで、簡単にTypeScript環境を構築できます。

```
$ npx create-nuxt-app nuxt-netlify-ts
```

- 言語は`TypeScript`を選択
- TypeScriptのruntimeは`@nuxt/typescript-runtime`を選択
- `Axios`のモジュールを追加
- レンダリングモードはSPA
- あとはお好みで

```
? Project name nuxt-netlify-ts
? Project description My super Nuxt.js project
? Author name nishitaku
? Choose programming language TypeScript
? Choose the package manager Yarn
? Choose UI framework Vuetify.js
? Choose custom server framework None (Recommended)
? Choose the runtime for TypeScript @nuxt/typescript-runtime
? Choose Nuxt.js modules Axios, DotEnv
? Choose linting tools ESLint, Prettier
? Choose test framework None
? Choose rendering mode Single Page App
? Choose development tools jsconfig.json
```

以下Lintエラーが出力されますが、無視してOKです。

```
$ eslint --ext .js,.vue --ignore-path .gitignore . --fix

/Users/takuro/workspace/ojthv2/nuxt-netlify-ts/nuxt.config.js
  80:12  error  'config' is defined but never used. Allowed unused a
rgs must match /^_/u  @typescript-eslint/no-unused-vars
  80:20  error  'ctx' is defined but never used. Allowed unused args
 must match /^_/u     @typescript-eslint/no-unused-vars

✖ 2 problems (2 errors, 0 warnings)

error Command failed with exit code 1.
```

## 動作確認
開発サーバ起動

```
$ cd nuxt-netlify-ts
$ yarn dev
```

`http://localhost:3000`にアクセスして正常に表示されることを確認。
![nuxt.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187787/0a5b14e2-659b-a77e-6adf-ae25489b8497.png)

# 2. Netlifyにデプロイ
次に、生成したボイラーテンプレートを、Netlifyにデプロイします。

NetlifyはGitHubリポジトリと紐付けておくことで、PushやMergeをトリガーに、自動的にビルド＆デプロイしてくれる機能があるため、それを使っていきます。

## GitHubリポジトリ作成
Gitに関してはざっくりとコマンドのみ。

```
$ git remote add origin (リポジトリURL)
$ git add .
$ git commit -m "generated by create-nuxt-app/2.15.0 darwin-x64 node-v13.8.0"
$ git push origin master
```

## NetlifyとGitHubリポジトリを紐付け
- Create a new site
    - Continuous Deployment
        - GitHubを選択
    - Continuous Deployment: GitHub App
        - 先程のGitHubリポジトリを選択
    - Basic build settings
        - Build command：`yarn build`
        - Publish directory：`dist`

## 動作確認
デプロイ完了後、独自に割り当てられた`https://(Netlifyドメイン)`にブラウザからアクセスし、開発サーバのときと同じ画面が表示されることを確認。

# 3. Netlify Functions
Netlifyが提供する**FaaS (Function as a Service)**。

Freeプランで、**125,000リクエスト/月**、**100時間/月**使えます。

ベースは**AWS Lambda**ですが、シンプルな設定ですぐに使い始められます。

## 環境構築
`netlify-lambda`を使います。

```
$ yarn add -D netlify-lambda
```

次に、TypeScriptで記述するためのライブラリを追加。

```
$ yarn add -D @babel/preset-typescript @types/aws-lambda
```

`netlify-lambda`のbabelの設定を上書きするために、新たに`.babelrc`をルートに追加。

```json:.babelrc
{
  "presets": [
    "@babel/preset-typescript",
    "@babel/preset-env"
  ],
  "plugins": [
    "@babel/plugin-proposal-class-properties",
    "@babel/plugin-transform-object-assign",
    "@babel/plugin-proposal-object-rest-spread"
  ]
}
```

開発サーバを起動するためには、netlifyの設定ファイルが必要になります。

また、NetlifyはGitHubリポジトリに`netlify.toml`が存在する場合、GUIでの設定内容よりも優先します。そのため、Functionsの設定だけでなく、ホスティングの設定も合わせて記載しておく必要があります。

```toml:netlify.toml
[build]
  Command = "yarn build"
  functions = "functions/dist"
  publish = "dist"
```

`package.json`に、開発サーバ起動スクリプトを追加。

```json:package.json
{
  "scripts": {
    "dev:functions": "netlify-lambda serve functions/api",
  },
}
```

## 実装
ディレクトリ`functions/api`を追加し、サンプル用の関数を実装。

```typescript:hello.ts
import { Handler, Context, Callback, APIGatewayEvent } from 'aws-lambda'

interface HelloResponse {
  statusCode: number
  body: string
}

export const handler: Handler = (
  event: APIGatewayEvent,
  context: Context,
  callback: Callback
) => {
  console.log(`hello invoked`)
  const params = event.queryStringParameters
  const response: HelloResponse = {
    statusCode: 200,
    body: JSON.stringify({
      msg: `Hello world ${Math.floor(Math.random() * 10)}`,
      requestId: context.awsRequestId || 'dummy',
      params
    })
  }

  callback(undefined, response)
}
```

## 動作確認
開発サーバを起動。9000番ポートで起動します。

```
$ yarn dev:functions
```

curlでレスポンスを確認。

```
$ curl "http://localhost:9000/.netlify/functions/hello"
{"msg":"Hello world 0","requestId":"dummy","params":{}}
```

因みに、`http://localhost:9000/hello`でも同様の結果が返ってきます。

# 4. Netlify Functionsのデプロイ
ホスティングと同様、GitHubリポジトリへPushするだけで、自動的にNetlify環境へ反映されます。

## GitHubリポジトリへのPush
まず、Functionsのビルド設定を追加。

```json:package.json
{
  "scripts": {
    "build": "netlify-lambda build functions/api && nuxt-ts build",
  },
}
```

GitHubにPushし、

## 動作確認
デプロイ完了後、curlでレスポンスを確認。

```
$ curl "http://(Netlifyドメイン)/.netlify/functions/hello"
{"msg":"Hello world 0","requestId":"dummy","params":{}}
```

# 5. Nuxt.jsからNetlify Functionsを呼び出す
NetlifyにデプロイしたNuxt.jsの画面から、Netlify Functionsの関数を呼び出します。

## CORSエラー対応
開発環境では、Nuxt.jsのポート番号(3000)とnetlify-lambdaのポート番号(9000)が異なるため、そのままだとCORSエラーが発生します。

回避策として、`@nuxtjs/proxy`のリバースプロキシ機能を使うことで、`http://localhost:3000/.netlify/functions/hello`へのアクセスを、`http://localhost:9000/.netlify/functions/hello`へ転送します。

`@nuxtjs/proxy`をインストール。

```
$ yarn add @nuxtjs/proxy
```

`nuxt.config.js`の`modules`に`@nuxtjs/proxy`を追加

```javascript:nuxt.config.js
export default {
  modules: [
    '@nuxtjs/proxy'
  ],
}
```

`nuxt.config.js`の`proxy`を追加

```javascript:nuxt.config.js
export default {
  proxy: {
    '/.netlify/functions': {
      target: 'http://localhost:9000'
    }
  }
}
```

## Axiosの設定
`@nuxtjs/axios`は`baseURL`に何も設定しなかった場合、デフォルトで`http://localhost:3000`となってしまいます。

そのため、`https://(Netlifyドメイン)`から`https://(Netlifyドメイン)/.netlify/functions`を呼び出してほしいのに、`http://localhost:3000/.netlify/functions`を呼び出してしまいます。

これを回避するため、`nuxt.config.js`の`axios`の`baseURL`を設定します。

```javascript:nuxt.config.js
export default {
  axios: [
    baseURL: '/'
  ],
}
```

## ソースコード修正
動作確認用のコードを`index.vue`に埋め込む。

```vue:index.vue
<script>
export default {
  // 追加
  async mounted() {
    try {
      const response = await this.$axios.$get('.netlify/functions/hello')
      console.log(`respones=${JSON.stringify(response)}`)
    } catch (err) {
      console.error(err)
    }
  }
}
</script>
```

## 動作確認

```
$ yarn dev // Nuxt.js開発サーバ起動
$ yarn dev:functions // netlify-lambda開発サーバ起動
```

`http://localhost:3000`にブラウザからアクセスし、コンソールログにFunctionsを呼び出した結果が表示されていることを確認する。

さらに、GitHubにPushし、デプロイ完了後、ブラウザからNetlifyドメインへアクセスし、同様にコンソールログを確認する。

# さいごに

**FaaS (Functions as as Service)**を使ったサーバレスアーキテクチャは、ここ数年でかなり使いやすくなってきている印象です。

今回のNetlify Functions以外にも
- Google Cloud Functions
- AWS Lambda
- IBM Cloud Functions
- Azure Functions
- Twilio Functions
など、各クラウドベンダが提供している**FaaS**もあるので、それらと比較してみると面白そうです。

また、Netlifyには他にも便利な機能があるみたいなので(Formsとか認証とか)、**Netlify CLI**と一緒に今後使ってみたいと思います。

# Reference
- [【入門】Netlify Functionsコトハジメ](https://qiita.com/Sr_Bangs/items/7867853f5e71bd4ada56)
- [Netlify Functions を使って CORS エラーを回避する](https://qiita.com/kurosame/items/7b5fd3cbf1f688b47e88)
- [Nuxt.js & Netlify-Lambda で Serverless SPA 開発]
(https://qiita.com/mikakane/items/b6d5bc7a472c61952302)
- [Nuxtのaxios-moduleがbaseURLを決定する仕組みの解説と、Netlify FormsへPOSTするときの対策](https://qiita.com/shozzy/items/69706cd550b407526884)
- [Nuxt.js (TypeScript) on Netlify、Functions / Split Testingで簡単にA/Bテストまでやってみる](https://tamanyan.me/netlify-functions-split-testing/)