【個人開発】Laravelでサーバーレスなアプリをサクッと作ろう

# はじめに
:::note info
アプリの作成手順だけ見たい方は[こちら](#構築手順)から
:::
正月の暇な時間を使い、Laravelで小さなアプリを作成しました。ランニングコストを抑えるべくサーバーレスな構成にしたので、その過程をまとめておきます。

# 作ったアプリ
## 読める誤字メーカー

「誤字のある文章だけど、気にせず読める」という体験は日本人なら誰もがしたことがあると思います。このアプリは、入力されたテキストを、自然に読めるレベルまで誤字だらけにしてくれます。

https://goji.anonymous-friend-diary.com

### 使用例
入力：
> むかしむかし、あるところに、小さな村がありました。その村には、のんびりした人たちがすんでいて、毎日畑で野菜を育てたり、小川で魚をとったりしてくらしていました。

↓
出力：
> むかしむかし、あるところに、小さな村がありました。その村には、のびんりした人たちがすんでいて、毎日畑で野菜を育てたり、小川で魚をとったりしてくしらていしまた。

※この現象は[タイポグリセミア](https://makitani.net/shimauma/typoglycemia)と言われるそうです。興味ある方は調べてみて下さい。

# 構築手順
## brefの利用
本アプリの構築にあたっては、LaravelアプリケーションをLambdaで実行するため、PHPのランタイムを提供してくれる[bref](https://bref.sh/docs/setup)を利用します。
brefは公式ドキュメントが（英語ですが）とても充実しており、今回の構築でもほぼ公式ドキュメントの通りに進めて作成ができました。
そのため、この記事ではドキュメントに載っていないor少し引っ掛かるかもしれない個所をメインに書いていきます。

## インフラ構成図
Brefの公式ドキュメントにある構成と同じです。セットアップ手順もこちらのページに載っています。
・メインの処理を行うリクエストはLambdaへ
・JSやCSSなどの静的ファイルはS3に配置し、CloudFrontでキャッシュさせる
という形をとっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/676792/d3c9fd3c-c413-52d2-aead-b4e9f18c99c0.png)
引用：https://bref.sh/docs/use-cases/websites

気をつけるべき点として、S3に配置する静的ファイルは、プロジェクトごとに配置が異なるため、適宜適切なディレクトリを指定して下さい。（私の場合は`public/build/assets/`にjsファイルやCSSファイルを配置していたので、`/build/assets/*`にアクセスした際にS3の静的ファイルのキャッシュを取得するようにしています。）


## Serverless.ymlの設定
静的ファイルをキャッシュするCloudFrontを簡単に作成するため、[serverless-lift](https://github.com/getlift/lift)プラグインを使用しています。`constructs`要素に下記のように書くことで、S3へのアセットの配置・CloudFrontのビヘイビアの設定・カスタムドメインの設定までを一通り行ってくれます。
```YAML
service: laravel

provider:
    name: aws
    region: ap-northeast-1 # 東京リージョンを指定
    environment:
        APP_ENV: production 
        SESSION_DRIVER: cookie

package:
    patterns:
        - '!node_modules/**'
        - '!public/storage'
        - '!resources/assets/**'
        - '!storage/**'
        - '!tests/**'
        - '!database/*.sqlite'

functions:

    web:
        handler: public/index.php
        runtime: php-82-fpm
        timeout: 28 
        events:
            - httpApi:
                path: /{proxy+} 
                method: ANY
    artisan:
        handler: artisan
        runtime: php-82-console
        timeout: 720

plugins:
  - ./vendor/bref/bref
  - serverless-lift

constructs:
    website:
        type: server-side-website
        assets:
            '/build/assets/*': public/build/assets/
            '/favicon.ico': public/favicon.ico
            '/robots.txt': public/robots.txt
        domain: <使用したいカスタムドメイン名> # あとで設定
        certificate: <SSL証明書のARN> # あとで設定
```
※カスタムドメインとACMで発行するSSL証明書のARNは、使用するCloudFrontのドメインが発行された後に記述しています。

カスタムドメインを設定するためにCloudFrontのドメインが必要になるため、一度`serverless deploy`を実行しておきましょう。初回は5 ~ 10分程度かかります。
表示されたURLをすると、作成したアプリにアクセスできるはずです。

## カスタムドメインの設定
ここまでくればもうほぼ完成なのですが、`.cloudfront.net`でアクセスしてもらうのも少し味気ないので、カスタムドメインを設定するところまで行います。

:::note info
Route53でドメインの発行が済んでいる前提で進めています。
:::
Route53で、使用するカスタムドメインのCNAMEレコードを作成します。向き先は`serverless deploy`実行時に表示されるCloudFrontのドメインを指定します。

![スクリーンショット 2025-01-02 19.47.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/676792/9b82a59b-db53-8bef-ece4-f21f2c12257c.png)

Route53の設定例：
![スクリーンショット 2025-01-03 17.49.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/676792/bef7b0b4-4e7c-eb84-9aa1-53b9b47674b6.png)

また、us-east-1リージョンでカスタムドメインのSSL証明書を作成しておきます。（CloudFrontはus-east-1リージョンに作成された証明書しか利用できないため）
![スクリーンショット 2025-01-03 18.22.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/676792/186ba677-d4d3-01f6-e161-f54efc4d15a3.png)

# 完成
カスタムドメイン名と発行したSSL証明書のARNを`serverless.yml`に記述します。（記述例は上述の通り）
最後に、もう一度`serverless deploy`を実行すると、カスタムドメインのURLが表示されます。
![スクリーンショット 2025-01-02 19.48.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/676792/e343584c-5eff-cf52-027e-3711739dc784.png)

無事、作成したアプリにアクセスできました！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/676792/73a4e351-7012-b7e1-ae3a-2c9fa86047a2.png)


# 参考
bref公式ドキュメント：
https://bref.sh/docs/setup

brefを使ったサーバーレスWebサイトの構築手順：
https://bref.sh/docs/use-cases/websites

serverless-liftプラグインのリポジトリ：
https://github.com/getlift/lift