node.jsやexpressを使った開発そのものの情報はたくさんありますが、node.jsとexpressでWebアプリケーションを開発したあと、本番環境にどうデプロイするのか、という情報は以外となかったりします。

今回は本番環境にデプロイするまでの方法になります。今回のWebアプリケーションはmongoDB Cloud を使っているため、用意するサーバはWebサーバのみです。また一気に設定するのではなく、少しずつテストするように流れを記載しています。

全体像

node.js と expressで開発したWebアプリケーションの場合、リバースプロキシの使用とWebアプリケーションのデーモン化が一緒に行われることが多いです。今回はリバースプロキシにnginx、デーモン化にPM2を使っています。

nginx : ユーザからのアクセスを受け取り、node.jsアプリに受け渡す

PM2 : node.js アプリをプロセスとして管理

そしてなぜこれらを使うのか、最初に説明しておきます。これらは必ず使わなければ行けないものではありません。使用する理由やメリットを抑えておくことで適切な選択ができるようになります。

なんとなく行うのではなく、まずは目的やメリットなどを理解しておきましょう。もし、早く手を動かしたいという場合は 本番サーバを構築する のセクションから読んでください。

Webアプリケーションのデーモン化をする理由

通常、expressを使って開発するとき、npm run dev といったコマンドで内蔵のWebサーバを起動してブラウザから確認すると思います。これはプロセスがフォアグラウンドで動いており、ログアウトするとこのプロセスは停止してしまいます。

この問題だけなら、バックグラウンドでプロセスを動かせば解決します。しかし、Webアプリケーションを運用するという観点で全体を考えたときに、Webアプリケーションをデーモン化することで、自動起動や停止、ログや監視といった運用がとても行いやすくなります。

リバースプロキシを使用する理由

これには大きく2つの理由があります。

1つ目の理由は 静的ファイルの配信に適している ことです。

node.jsとexpressでは、Webアプリケーションの動的な処理に適していますが、静的ファイルの配信には向いていません。一方、Nginxは高速で静的ファイルの配信に適しており、Webアプリケーションにおいて静的ファイルの配信にNginxを使用することで、Webアプリケーションのパフォーマンスを向上させることができます。

2つ目の理由は 拡張性が高い ことです。 Nginxは、多くのモジュールを持っており、Webアプリケーションのニーズに合わせてカスタマイズすることができます。また、Nginxは高度な設定が可能であり、Webアプリケーションのニーズに合わせて柔軟に構成できます。

他にもロードバランシングのため、などの理由もありますが、その場合はロードランサーを使うなど代替となる方法があり大きな理由ではないため、ここでは省略します。

サーバを構築する

ここで行うことはサーバを起動させること、OSにログインできること、node.jsをインストールすることです。

詳細な手順は省略します。

手動での起動テスト

構築した本番サーバにソースコードを配置します。その後、ライブラリをインストールします。

.env ファイルを使用していたら、そのファイルもサーバ内に作成しましょう。

$ git clone XXXXXXXXXXXXXXXXX
$ cd apps/
$ npm install

ここまでで起動する準備が整いました。ここでいきなりnginxやPM2を設定していくのではなく、

まずは今まで通りの(開発のときに起動した)方法でWebアプリケーションが起動するか確認します。それは少しずつ変更し、テストしていくことでエラーが発生したときに解決しやすくするためです。

それではSSH接続してサーバとのセッションを2つ用意してください。

セッション1

セッション1でWebアプリケーションを起動します。このとき使うコマンドは開発環境で使っていたコマンドを使ってください。下記のコマンドはあくまで一例です。

$ npm run dev

セッション2

セッション2ではcurl を使って、接続確認をします。

$ curl http://localhost:8100/

テストをして問題なければ次に進みましょう。

過去に私が遭遇したトラブルとして .env ファイルが読み込めず、DBと接続できないというエラーが出たことがあります。そのときは下記の方法で .env ファイルが読み込めるようになりました。

開発環境

本番環境では下記の記載方法では .env ファイルが読み込めなかった

require("dotenv").config();

本番環境

明示的に .env ファイルのパスを指定して読み込めるようになった

require("dotenv").config({path:__dirname+'/.env'});

アプリケーションのデーモン化

デーモン化する目的は冒頭に記載したので、ここでは How についてだけ記載していきます。

デーモン化するために PM2 というソフトウェアを使っていきます。

PM2 経由での起動確認

PM2 のインストール

$ npm install pm2 -g
$ which pm2

PM2 経由でのWebアプリケーション起動

まずはPM2 経由で正常にWebアプリケーションが起動するか確認します。

## pm2 start <ファイル名> --name <アプリ名>
$ pm2 start app.js --name todo-app

起動確認

online になっていれば大丈夫です

$ pm2 list
┌────┬─────────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐
│ id │ name        │ namespace   │ version │ mode    │ pid      │ uptime │ ↺    │ status    │ cpu      │ mem      │ user     │ watching │
├────┼─────────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤
│ 0  │ todo-app    │ default     │ 1.0.0   │ fork    │ 2038     │ 7m     │ 7    │ online    │ 0%       │ 76.2mb   │ ubuntu   │ disabled │
└────┴─────────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘

ログ確認

ログも確認しておきます。

## pm2 logs <アプリ名>
$ pm2 logs todo-app

接続確認

$ curl http://localhost:8100/

PM2の設定ファイル作成

$ vi pm2config.json
{
    "name": "todo-app",
    "script": "app.js",
    "exec_mode": "fork",
    "log-date-format": "YYYY-MM-DD HH:mm Z"
}

起動テスト

設定ファイルを使用して起動するか確認します。

すでに起動している場合は pm2 stop コマンドで停止させたあとに起動テストします。

$ pm2 start pm2config.json

PM2自動起動設定

startup コマンドでは ubuntu を指定していますが、実際には皆さんの環境に合わせて変更してください。詳細はこちら

$ sudo pm2 startup ubuntu

出力ここから

中略

Target path
/etc/systemd/system/pm2-root.service
Command list
[ 'systemctl enable pm2-root' ]
[PM2] Writing init configuration in /etc/systemd/system/pm2-root.service
[PM2] Making script booting at startup...
[PM2] [-] Executing: systemctl enable pm2-root...
[PM2] [v] Command successfully executed.
+---------------------------------------+
[PM2] Freeze a process list on reboot via:
$ pm2 save

[PM2] Remove init script via:
$ pm2 unstartup ubuntu

出力ここまで

$ sudo systemctl enable pm2-root
$ systemctl list-units | grep pm2
  pm2-root.service                                  loaded active     running   PM2 process manager
ubuntu@ip-10-0-0-116:~/blog.12sec.work$ pm2 save
[PM2] Saving current process list...
[PM2] Successfully saved in /home/ubuntu/.pm2/dump.pm2

リバースプロキシの導入

リバールプロキシには nginx を使います。apacheを使っても大丈夫です。

nginx をインストールします。

$ sudo apt install nginx

デフォルト設定を削除しておきます

これは行わなくても問題ありません。

$ cd /etc/nginx/sites-enabled/
$ rm default

リバースプロキシの設定

下記の設定はあくまで一例です。

$ vi /etc/nginx/sites-enabled/todo-app
server {
  listen 80;
  server_name グローバルIP or ドメイン名;

  location / {
    proxy_pass http://127.0.0.1:8100; # expressのポートを確認
    proxy_redirect                          off;
    proxy_set_header Host                   $host;
    proxy_set_header X-Real-IP              $remote_addr;
    proxy_set_header X-Forwarded-Host       $host;
    proxy_set_header X-Forwarded-Server     $host;
    proxy_set_header X-Forwarded-Proto      $scheme;
    proxy_set_header X-Forwarded-For        $proxy_add_x_forwarded_for;
  }
}

シンボリックリンク作成

$ sudo ln -s /etc/nginx/sites-available/todo-app /etc/nginx/sites-enabled/

nginx 起動

$ sudo nginx -t
$ sudo systemctl start nginx
$ sudo systemctl status nginx
● nginx.service - A high performance web server and a reverse proxy server
     Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled)
     Active: active (running) since Wed 2023-05-10 09:25:17 UTC; 15min ago
       Docs: man:nginx(8)
   Main PID: 517 (nginx)
      Tasks: 2 (limit: 1141)
     Memory: 11.7M
        CPU: 50ms
     CGroup: /system.slice/nginx.service
             ├─517 "nginx: master process /usr/sbin/nginx -g daemon on; master_process on;"
             └─518 "nginx: worker process" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" ""

テスト

ブラウザから URL にアクセスしてWebアプリケーションのトップページが表示されればテスト完了です。

もし上手くいかなければ、nginx のエラーログなどを見てみましょう

まとめ

node.js と express で開発したWebアプリケーションはソースコードをドキュメントルートに配置して終わり、というわけではありません。意外と設定が必要だったかなという印象ではないでしょうか。

重要な仕組みですが、その割にはあまり情報がなかったため、網羅的な記事を書いてみました。また手順だけではなく、目的やメリットを理解して、この仕組みを使うようにしましょう。