【javascript】小規模Webサービスを無料でリリースしよう

こんにちは、G-Blossomの松下です。この記事はG-Blossomアドベントカレンダー2024の12/6の記事となります。
ここ最近、小規模なWebサービスを開発する機会がありました。（業務ではC#しか触っていませんので、完全プライベートです）
本記事では、その際にChatGPTくんに教えてもらった「サーバーレス関数」について軽く紹介していきます。

### 当記事のゴール
めちゃくちゃ小規模でいいから、無料でWebサービスを作って公開する

### 手段
サーバーレス関数によってフロントとバックエンドを一括で開発し、Vercelでリリースする

## 選定経緯
大まかな条件は以下の通りです。
* フロントはReactを利用する
* バックエンドは簡単にデータベース操作するAPIで十分
* 利用頻度は低く、同時に利用するユーザーは非常に少ない（最大でも20人以内）

フロントはReactを用いる前提で、バックエンドに関していくつか構成を考えたのですが、
* node.js
利用頻度の低いnode.jsのサーバープログラムを常時起動するのはコストがかかりすぎる（pythonも同様）
* php
apache設定を変更できないレンタルサーバではphpとReactの併用が不可能

などの問題があり、これはお金で解決するしかないのか、と思っていたところで辿り着いたのが、Vercelでサーバーレス関数を用いる手法でした。
なお、データベースはAmazon RDSの無料使用枠を利用します。
 
## サーバーレス関数の利点
* 安い、または無料のサービスで賄える
サーバープログラムを常時起動しなくてよいので、サーバー負荷が抑えられる
 
* 学習コストが低い
フロントとバックエンドを一貫したjavascriptのプロジェクトで開発できる
## 悪いところ
* しばらく呼び出しがなかった場合、実行に時間がかかる（コールドスタート）
* 1つの処理に時間がかかりすぎる(10秒)と落ちる（無料だから）
* 1プロジェクトあたり12種類までの関数（エンドポイント）しか使えない（無料だから）

とはいえ、これらの問題は小規模プロジェクトでは許容できる範囲でした。
なお、有料プランにすることで簡単にスケーリングできるのも利点の一つです。

### Vercelの特徴
* https://［プロジェクト名］.vercel.app というスッキリしたURLが使える
* githubに連携し、pushすると自動でdeployしてくれる

## 実際の設計例
ポイントは、フロントとバックエンドを同一プロジェクトで開発できる点です。
```project-root/
├── public/              # Reactの公開ファイル（faviconや画像など）
├── src/                 # Reactアプリのメインディレクトリ
│   ├── components/      # コンポーネント
│   ├── pages/           # ページコンポーネント
│   └── App.js           # Reactアプリのエントリーポイント
├── api/                 # サーバーレス関数（API）
│   ├── info.js          # 例: /api/info.js のエンドポイント
│   └── login.js         # 例: /api/login のエンドポイント
├── package.json         # プロジェクトの依存関係やスクリプト
├── knexfile.json        # knexでの接続情報を記述
└── vercel.json          # Vercel用の設定ファイル
```

そして、api/info.jsなどのエンドポイントで必要なAPIの処理を記述します。
```// api/info.js
const db = require('../knexfile.js');

export default async function handler(req, res) {
  if (req.method === 'GET') {
    const { USER_ID } = req.query;
    try {
      const user = await db('USER_TBL').select('*').where({ USER_ID });
      res.status(200).json(user[0]);
    } catch (error) {
      res.status(500).json({ error: error.message });
    }
  } else {
    res.status(405).json({ error: 'Method Not Allowed' });
  }
}
```
### 環境変数

接続情報やビルドモード依存の値はknexfileに以下のように記述します。
```//knexfile.js
const db = require('knex')({
  client: 'mysql2',
  connection: {
    host: process.env.REACT_APP_DB_HOST,
    user: process.env.REACT_APP_DB_USER,
    password: process.env.REACT_APP_DB_PASSWORD,
    database: process.env.REACT_APP_DB_NAME,
  },
});

module.exports = db;
```

値はリポジトリに直接記述せず、Vercel側の環境変数で設定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131072/d390e4d8-cba5-0d81-4956-9d5fffe8d828.png)
この例ではDevelopment、Preview、Productionで全部同じ値を使用していますが、もちろん個別に設定可能です。

### ルーティング
API用のルーティングはvercel.jsonによって設定しています。
```//vercel.json
{
  "rewrites": [
    { "source": "/api/(.*)", "destination": "/api/$1" }
  ]
}
```

### API呼び出し
フロント側からのAPI呼び出しもシンプルです。
```//api.js
//API呼び出し用クラス
class ApiClass{
const baseURL = "/api";
  constructor(){
    axios.defaults.headers.post['Content-Type'] = 'application/json;charset=utf-8';
    axios.defaults.headers.post['Access-Control-Allow-Origin'] = '*';

    axios.interceptors.request.use(request => {
      return request
    })

    axios.interceptors.response.use(response => {
      return response
    })

  }
  GetUserInfo(userId){
    let param = {
      USER_ID: userId,
    }
    return axios.get(baseURL + '/info', {params: param});
  }
  Login(){
    return axios.post(baseURL + '/login');
  }
}

let api = new ApiClass();
export default api;
```

```//play.js
//フロント
api.GetUserInfo(userId).then(res => {
  setUser(res.data)
})
```
あとは特別な実装はありません。

## 終わりに

フロント、バックエンドで分業する業務では縁のなかったサーバーレス関数ですが、プライベートで開発する際にここまでかゆいところに手が届く仕組みがあることに感動し、今回記事にさせていただきました。
そして、つい最近(2024/10)、Web環境の最強IDEであるWebStormの非商用利用が無料になったので、Webエンジニアの開発環境が無料で全部揃います！

https://www.jetbrains.com/ja-jp/webstorm/

VercelとWebStorm、ウェブエンジニアスターターキットとしてかなり「正解」なのではないかと思っています。

以上です。ご意見お待ちしております。