【Serverless Framework】serverless-offlineでcannot get nullが返ってきて表示されないときの対処法

## はじめに

私はExpressアプリケーションをコストが低いサーバレス（API Gateway/Lambda/DynamoDB）にデプロイしようと考えました。

パブリッククラウドに慣れていない＋従量課金制に恐怖があったので、できる限りデプロイ前に**ローカルでテスト**できないか調査し、Serverless Framework＋プラグインを使用してローカルでテストできる環境を構築することにしました。

Serverless Framework+Serverless-offline+Expressでローカルにサーバレス環境を構築したところ、***ルートディレクトリ***にアクセスしたときに、cannot get null　というエラーが発生しました。このエラー解決に時間がかかったので、記事にしておこうと思いました。

### この記事の対象者

- 同じエラーが出て困っている人
- Serverless Frameworkを使っている人
- サーバレスをローカル環境に構築したい人

### **実行環境**

- OS：Windows 11 Home 21H2
- 言語
    - Node.js
        - version: 16.11.1
    - npm
        - version: 8.0.0
- 使用しているライブラリ・フレームワーク
    - Express
        - version: 4.18.1
    - serverless
        - version: ^3.24.1
    - @vendia/serverless-express
        - version: ^4.10.1
    - serverless-offline
        - version: ^11.3.0

### 前提知識

<details>
<summary>Serverless Frameworkについて</summary>
<ul>
    <li>Serverless Applicationを構成管理デプロイするためのツール</li>
    <li> 
    IaC（インフラのコード化）、デプロイが楽になる、Serverlessアプリケーションの管理がしやすい、プラグインが豊富で<strong>ローカル環境</strong>の構築が楽にできる。
    <ul>
        <li>類似サービス：AWS SAM</li>
    </ul>
</ul>
</details>
<details>
<summary>Serverless-offlineについて</summary>
<ul>
    <li>API Gatewayをローカルで動かすServerless Frameworkの拡張機能</li>
    <ul>
        <li>完全にAPI Gatewayを再現しているわけではない</li>
    </ul>
</details>
<details>
<summary>Expressについて</summary>
<ul>
    <li>Node.jsにおけるWebフレームワーク</li>
    <li>基本的なミドルウェアを備え、拡張モジュールが豊富</li>
    <ul>
        <li>完全にAPI Gatewayを再現しているわけではない</li>
    </ul>
</details>
<details>
<summary>Serverless-expressについて</summary>
<ul>
    <li>API Gateway + lambda上でExpressアプリケーションを動かすことができるライブラリ</li>
    <li>API Gateway→lambdaからくるイベントオブジェクトの変換の他、API Gateway + lambdaとの連携をよしなにやってくれるライブラリ</li>
    <ul>
        <li>完全にAPI Gatewayを再現しているわけではない</li>
    </ul>
</details>

## エラーの詳細

### やりたかったこと

- AWS Serverless環境（lambda/API Gateway/DynamoDB）をローカル環境に疑似的に構築し、デプロイ前にテストを行う。
- serverless-expressを使ってExpressとLambdaを接続する。
- Serverless-offline→Serverless-offline→Expressの順にリクエストを送り、ルーティング処理が行われる。

### 発生したエラー（上手くいかなかったこと）

Serverless-offlineとServerless-expressとの接続をする際にエラーが発生。
`http://localhost:3000/dev/`にアクセスしても、**cannot get null**が返ってきてしまう。

↓　`http://localhost:3000/dev/`にアクセスした結果
![serverless-offline2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2983519/4e6e0549-3618-9a05-dffa-642c9f09d527.png)

### 調査したこと、思考プロセス

<details>
<summary>1 Serverless-offlineを使ったときに、サーバが立ち上がっているか確認</summary>
<ul>
    <li>Serverless-offlineのコマンドを実行</li>
    <li><code>serverless offline</code>をターミナルで実行</li>
    <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2983519/40c295c2-2ba0-1564-eca6-8c9e7aa01532.png">
</details>
<details>
<summary>2 Serverless-offlineを使わないで、Expressの開発用サーバーでルーティングできるか試す</summary>
<ul>
    <li> 問題なくルーティングできた。→<strong>Express側のルーティングの問題ではない</strong>。</li>
</ul>
</details>
<details>
<summary>3 Serverless-offlineで開発サーバーを立ち上げ、他のルーティングが動くか確認する</summary>
<ul>
    <li>今までルートディレクトリ（<code>/</code>）にアクセスして表示されなかった</li>
    <li><code>/index</code>のルートを追加してテスト→表示された</li>
    <li>→<strong>ルートディレクトリ</strong>のときだけ起こる<strong>エラー</strong></li>
</ul>
</details>
<details>
<summary>4「Serverless-offline root directory cannot get null」で検索</summary>
<ul>
    <li>Google検索、GitHub Issueで検索</li>
    <li>参考記事を発見</li>
    <ul>
    <li>
        <a href="https://github.com/dherault/serverless-offline/issues/88">
            参考にしたGitHub Issue①
        </a>
    </li>
    <li>
        <a href="https://github.com/dougmoscrop/serverless-http/issues/86#issuecomment-842231137">
            参考にしたGitHub Issue②
        </a>
    </li>
    </ul>
</details>
    

### 調査結果、エラーの発生原因

**※私がまだ未熟なエンジニアですので以下の推測が間違っている場合があります。
　もし間違っている箇所があればコメントで指摘をしていただけると幸いです。**

- Serverless-offlineがServerless-expressにプロキシする際に、root directoryだけスラッシュを入れた状態でプロキシしてしまうと思われる（ちゃんとソースコード見てないので推測です）。
    - 理由
    - `http://localhost:3000/dev//`にはアクセスできるため、ルートディレクトリ（//）が**重複**している。
    - `http://localhost:3000/dev//`へのアクセス
    ![serverless-offline3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2983519/9bda5d29-ff7f-cfce-ce54-40678a03d435.png)

    
- `http://localhost:3000/dev/`というURLでプロキシしてしまい、`http://localhost:3000/dev//`とスラッシュが<strong>重複</strong>してしまう。
    - serverless-offlineとserverless-express間のソースコードを見れば、原因がわかりそうではありますが。今回は割愛します。

## エラーの解決法

### **解決方法①**

- ルートディレクトリを使用しない
    - 今回のエラーはルートディレクトリときのみ起きるエラーなのでそもそもExpress側のルーティングでルートディレクトリを使用しないようにする。
    - 例　/indexにアクセスするようにする
        
        ```jsx
        // ルートディレクトリを使用しない
        // app.get("/", (req, res) => ())
        
        //代わりに/indexのようなルーティングにする
        app.get("/index", (req, res) => ())
        ```
        

### 解決方法②

- ルートディレクトリを使用したいケースも存在するため、その場合は**Serverless.yml**にサンプルのコードを追記する。
<details>
<summary>Serverless.yml</summary>
    <ul>
      <li>Serverless Frameworkでサーバレスのインフラをコードで書くファイル。</li>
      <li>ここにAWSの各リソースの設定やローカル環境時の設定等を書く。</li>
      <li>インフラをコードで表した<strong>設計図</strong></li>
    </ul>
</details>
以下コードのサンプル。

```yaml
provider:
  name: aws
  region: ap-northeast-1
  stage: dev
  runtime: nodejs14.x
	~~~~~~~~~~~~~~~~~~~~~~~ 以下省略

// 以下を追記する。
custom:
  serverless-offline:
    noPrependStageInUrl: true
```

- 上記を書くとServerless-offlineで実行するときは**stage名**(/dev)をURLに含まめない
    - Serverless-offline側でstage名を追加するときに`/`を追加し、`dev/`としていることが**スラッシュ**が重複する原因だと思われます。
- こうした場合、下記URLでアクセス可能
    - `http://localhost:3000/`
    - Expressの開発サーバーを使うのと同じになります。
        - 今回の設定は**ローカル**で実行するときのみ有効なので、デプロイするときは**stage名**(/dev)は追加されます。
- 参考にしたGithub Issues
[serverless-http 2.0.0 with express · Issue #86 · dougmoscrop/serverless-http](https://github.com/dougmoscrop/serverless-http/issues/86#issuecomment-842231137)
[Serverless-offline: route not found · Issue #88 · dherault/serverless-offline](https://github.com/dherault/serverless-offline/issues/88)

## おわりに
今回はServerlessをローカル環境で再現してるときに遭遇したエラーの解決法について書かせていただきました。実際の原因と詳しくは特定できていないので、もしご存じの方がいましたらご連絡ください。

## 参考にしたサイト・動画等

- 参考にしたGithub Issues
[serverless-http 2.0.0 with express · Issue #86 · dougmoscrop/serverless-http](https://github.com/dougmoscrop/serverless-http/issues/86#issuecomment-842231137)
[Serverless-offline: route not found · Issue #88 · dherault/serverless-offline](https://github.com/dherault/serverless-offline/issues/88)

- 各ライブラリ、拡張機能の公式URL
    - Serverless Framework
[Serverless: Develop & Monitor Apps On AWS Lambda](https://www.serverless.com/)
    - Serverless-offline
[GitHub - dherault/serverless-offline: Emulate AWS λ and API Gateway locally when developing your Serverless project](https://github.com/dherault/serverless-offline)
    - Serverless-express
[GitHub - vendia/serverless-express: Run Node.js web applications and APIs using existing application frameworks on AWS #serverless technologies such as Lambda, API Gateway, Lambda@Edge, and ALB.](https://github.com/vendia/serverless-express)