DockerでNuxt.jsとLaravelの環境を作る！ついでにbrefでLaravelをAWSにデプロイするよ

## はじめに
以前こちらの記事を書きました。

https://qiita.com/Ryo9597/items/ba0838e68da2a73bc55b

上記の記事内でDockerでNuxt.jsとLaravelの環境を作ることができました。

ただデプロイをNuxt.jsは`Netlify`, Laravelは`Heroku`を使っていました。
Netlifyは個人開発レベルでは必要十分なのでいいとして、Herokuは様々な制限があるのでどうにかして違うものを使いたいと思っていました。

そこで目をつけたのが、**サーバーレスアーキテクチャ**です。
以下の記事でも、サーバーレスアーキテクチャを採用しています。

https://qiita.com/Ryo9597/items/660d988858c573c3d58e

https://qiita.com/Ryo9597/items/39c876fbd508f81d5d34

私が考える、個人開発におけるサーバーアーキテクチャのメリットは3つあります。
**①コスト最適化**
極端なスケールを前提としない場合は、デプロイ先にEC2やECSなどのサーバーの使用は、「コスト最適化」という側面からすると論外です。
サーバーを常時稼働させなければならないのでコストがあまりにかかりすぎます。

しかし、サーバーレスの場合は名前の通りサーバーが必要ないので、使用した分だけ払う従量課金なのでコストが安くなります。
また無料利用枠には、1か月ごとに100万件のリクエストが含まれます。
もし無料枠を超過しても、100万件リクエストごとに20円ぽっちです。

https://aws.amazon.com/jp/lambda/pricing/

**②開発スピードの効率化**
デプロイには、`ServerlessFramework`を使用します。
こちらを使用することでコマンド1つでデプロイまで完了します。

**③構成のミニマム化**
DockerでLaravelを使用する場合などは、MySQLなども同時にコンテナ化しますが、
`DynamoDB`を採用するため構成がミニマムになります。

また「コストの最適化」という側面からも、DynamoDBは従量課金ができるのに対して、RDS(MySQL)はインスタンスの起動時間で課金され、DynamoDBの方がコストが安くなります。

**以上のことから、バックエンド（API）のデプロイはサーバーレスアーキテクチャを使用します。**

しかし、問題はPHPがLambdaのランタイムの対象外であることです。
![スクリーンショット 2021-08-10 10.32.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838372/7efb3f49-9c98-afaa-8b0b-545b9acc2b84.png)

対象外なので`カスタムランタイム`という方法で環境を作る必要があります。

**そこで使用するのが、`Bref`です。**

https://bref.sh/

Brefは、PHPとそのフレームワークの完全なサポートをAWSLambdaにもたらすオープンソースプロジェクトです。

Brefは、`serverless framework`を使用して、サーバーレスアプリケーションを構成およびデプロイします。

https://www.serverless.com/

## Github

https://github.com/ssk9597/Docker-Nuxt-Laravel-Bref

## 事前準備
ここでやることは3つです。

### ①`serverless`コマンドをインストールする
```terminal:ターミナル
$ npm install -g serverless
```

### ②AWSアクセスキーを作成する
#### 1. AWSダッシュボードで、IAMに移動し、新しいユーザーを作成する

#### 2. ユーザー名を設定し、プログラムによるアクセスを有効にする
![スクリーンショット 2021-08-03 11.05.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838372/5a292f3a-07e5-3f9c-baa3-91807d72be3e.png)

#### 3. [既存のポリシーを直接アタッチ]をクリックし、AdministratorAccessを検索して選択する
![スクリーンショット 2021-08-03 11.06.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838372/be374731-a89d-9c1a-9827-25dd9d72e173.png)

#### 4. ユーザーの作成を完了し、生成されたAWSアクセスキーとシークレットキーをメモする

### ③次のコマンドを実行して、AWSのキーを設定する
```terminal:ターミナル
$ serverless config credentials --provider aws --key <key> --secret <secret>
```

これで`serverless framework`が使えるようになりました！

## それでは環境を作っていきましょう！
### フロントエンドの作成

```terminal:ターミナル
$ git clone git@github.com:ssk9597/Docker-Nuxt-Laravel-Bref.git
$ cd Docker-Nuxt-Laravel-Bref
$ make nuxt
```

### バックエンドの作成とデプロイ

```terminal:ターミナル
$ cd api
$ composer install
$ cp .env.example .env
$ php artisan key:generate
$ php artisan config:clear
$ serverless deploy
```

これでデプロイが完了します。

```
Service Information
service: laravel
stage: dev
region: ap-northeast-1
stack: laravel-dev
resources: 14
api keys:
  None
endpoints:
  ANY - https://XXXXXXXXX.execute-api.ap-northeast-1.amazonaws.com
functions:
  web: laravel-dev-web
  artisan: laravel-dev-artisan
layers:
  None
```

このエンドポイントがURLです。

[https://XXXXXXXXX.execute-api.ap-northeast-1.amazonaws.com/api](https://XXXXXXXXX.execute-api.ap-northeast-1.amazonaws.com/api)

にアクセスすると以下の画面になればデプロイ成功です。

![スクリーンショット 2021-08-10 10.45.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838372/30bd55c6-6ae9-eb91-043a-3d6c49cb5cc8.png)

では、フロントエンド側の修正をしましょう。
API_URLの登録をしましょう。

```env:frontend/.env
# API_URL
API_URL = "https://XXXXXXXXX.execute-api.ap-northeast-1.amazonaws.com/api"
```

そして、nuxt.config.jsの修正もしましょう。

```javascript:frontend/nuxt.config.js
require('dotenv').config();
const { API_URL } = process.env;

export default {
  head: {
    target: 'static',
    title: 'frontend',
    htmlAttrs: {
      lang: 'ja',
    },
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
      { hid: 'description', name: 'description', content: '' },
    ],
    link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }],
  },

  css: [],

  plugins: [],

  components: true,

  buildModules: [],

  watchers: {
    webpack: {
      poll: true,
    },
  },

  modules: ['@nuxtjs/axios', '@nuxtjs/proxy', '@nuxtjs/dotenv'],

  env: {
    API_URL,
  },

  proxy: {
    '/api': process.env.API_URL,
  },

  axios: {
    baseURL: process.env.API_URL,
    browserBaseURL: process.env.API_URL,
  },

  build: {},
};
```

ではNetlifyにフロントエンドもデプロイしましょう。

```vue:frontend/pages/index.vue
<template>
  <div>
    <h1 class="title">
      {{ text }}
    </h1>
  </div>
</template>

<script>
export default {
  async asyncData({ $axios }) {
    const text = await $axios.$get('/');
    return {
      text,
    };
  },
  data() {
    return {
      text: '',
    };
  },
};
</script>
```

これで先ほどと同様に`Hello World`が表示されればOKです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838372/4d072bcb-795d-59d8-b9be-579669e26293.png)

## ここからは任意で追記していきましょう！
### TypeScript
TypeScriptの導入はファイルの追記も必要になります。

```terminal:ターミナル
$ make typescript
```

#### ファイルの修正
##### shims-vue.d.tsに追記
```typescript:frontend/shims-vue.d.ts
declare module '*.vue' {
  import Vue from 'vue';
  export default Vue;
}
```

##### tsconfig.jsonに追記
```json:frontend/tsconfig.json
{
  "compilerOptions": {
    "target": "ES2018",
    "module": "ESNext",
    "moduleResolution": "Node",
    "lib": ["ESNext", "ESNext.AsyncIterable", "DOM"],
    "esModuleInterop": true,
    "allowJs": true,
    "sourceMap": true,
    "strict": true,
    "noEmit": true,
    "experimentalDecorators": true,
    "baseUrl": ".",
    "paths": {
      "~/*": ["./*"],
      "@/*": ["./*"]
    },
    "types": ["@nuxt/types", "@types/node"]
  },
  "files": ["shims-vue.d.ts"],
  "include": [
    "components/**/*.ts",
    "components/**/*.vue",
    "layouts/**/*.ts",
    "layouts/**/*.vue",
    "pages/**/*.ts",
    "pages/**/*.vue"
  ],
  "exclude": ["node_modules", ".nuxt", "dist"]
}
```

### Composition-API
Composition-APIの導入も、ファイルの追記が必要になります。

```terminal:ターミナル
$ make composition-api
```

#### ファイルの修正
##### nuxt.config.jsに追加
```javascript:frontend/nuxt.config.js
{
  buildModules: [
    '@nuxtjs/composition-api/module'
  ],
  generate: {
    // choose to suit your project
    interval: 2000,
  }
}
```

### Storybook
Storybookの導入はコマンドだけでOKです。

```terminal:ターミナル
$ make storybook

// 再起動したいとき
$ make re-storybook
```