Docker 環境で Node.js Express Webサービスを起動する

# Docker 環境で Node.js Express Webサービスを起動する

こんにちは、@studio_meowtoon です。今回は、WSL Ubuntu の Docker 環境で、Node.js Express Web アプリケーションをコンテナとして起動する方法を紹介します。
![express_on_docker.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3206449/c7babfed-3506-33be-4a6c-1d3279672537.png)

## 目的

Windows 11 の Linux でクラウド開発します。

こちらから記事の一覧がご覧いただけます。

https://qiita.com/studio_meowtoon/items/6119ab05d8b13527efd8

## 実現すること

ローカル環境の WSL Ubuntu の Docker 環境で、Dockerfile からビルドした Node.js Express Web サービスのカスタムコンテナイメージを起動します。

**JS ファイルのアプリをコンテナとして起動**
```mermaid
flowchart LR
terminal --> express
subgraph Ubuntu
  subgraph Docker
    subgraph container[app-hello-express]
      subgraph node.js
        express <--> app[server.js]
      end
    end
  end
end
style Ubuntu fill:#FFBCA4, stroke:#E0460F
style Docker fill:#EEFDFF, stroke:#74B9DC
style container fill:#FFD7E4, stroke:#E13C73
style node.js fill:#DCFFDC, stroke:#2F872F
style app fill:#ECECFF, stroke:#9674DC
style express fill:#FFFFDE, stroke:#B4AE43
```

**実行環境**

|要素|概要|
|:--|:--|
|terminal|ターミナル|
|Ubuntu|OS|
|Docker|コンテナ実行環境|

**Web サービス コンテナ**

|要素|概要|
|:--|:--|
|app-hello-express|カスタムコンテナ|
|node.js|Node.js 実行環境|
|server.js|JS スクリプト|
|express|Web サーバー|

## 技術トピック

### Express とは？  

<details>
<summary>こちらを展開してご覧いただけます。</summary>

### [Express](http://expressjs.com/ja/)

:::note info
Express は、軽量で柔軟な Web アプリケーションフレームワークです。Node.js 上で動作し、簡単なルーティングやミドルウェアを提供します。

|キーワード|内容|
|:--|:--|
|ミニマルなフレームワーク|Express は Node.js 用のミニマルなウェブフレームワークであり、不要な複雑さを排除しつつ、ウェブアプリケーションの構築に必要な基本的な機能を提供します。|
|簡単なルーティング|シンプルで柔軟なルーティングシステムを提供し、HTTP リクエストのハンドリングと異なるエンドポイントのルートを定義するのが簡単です。|
|ミドルウェアのサポート|Express はミドルウェアの利用を可能にし、リクエストとレスポンスオブジェクトを変更する関数を追加できます。これにより、認証、ログ出力などの機能を簡単に追加できます。|
|テンプレートエンジンの統合|Express は EJS、Pug、Handlebars などのさまざまなテンプレートエンジンとの統合をサポートし、動的なビューを簡単にレンダリングできます。|
|強力な HTTP ユーティリティ|Express は GET、POST、PUT、DELETE などの HTTP 動詞を扱うための HTTP ユーティリティメソッドを提供します。|
|モジュール性|Express はモジュール性を奨励し、再利用可能なミドルウェアとルートハンドラーを作成することができます。|
|大規模なコミュニティ|大きく活発なコミュニティが存在し、多数のライブラリやモジュールが提供されており、他のツールやサービスとの簡単な統合が可能です。|
|スケーラビリティ|ベースとなる Node.js の特性により、Express は多数の同時接続を処理することができるため、スケーラビリティに優れています。|
|ミドルウェアエコシステム|Express は豊富なサードパーティのミドルウェアエコシステムを利用できるため、アプリケーションの機能を簡単に拡張できます。|
|オープンソース|Express はオープンソースであり、積極的にメンテナンスが行われているため、安定性とセキュリティを確保しています。|
:::

</details>

### Dockerfile とは？  

<details>
<summary>こちらを展開してご覧いただけます。</summary>

### [Dockerfile](https://docs.docker.jp/engine/reference/builder.html#dockerfile)

:::note info
Dockerfile は、Docker コンテナを構築するためのテキストファイルです。Docker コンテナはアプリケーションやサービスを実行するための環境を含む軽量でポータブルな仮想化ユニットです。

|キーワード|内容|
|:--|:--|
|スクリプト形式|Dockerfile はシンプルなスクリプト形式で記述されるため、コンテナのビルドプロセスを自動化することが容易です。|
|レイヤー構造|Docker イメージは Dockerfile の各命令が実行される際にレイヤーとして生成され、再利用やキャッシュが可能な構造となっています。|
|バージョン管理|Dockerfile はテキストベースであるため、コードと同様にバージョン管理システムで管理しやすいです。|
|ポータビリティ|Dockerfile により、アプリケーションとその依存関係が1つのコンテナイメージにパッケージ化されるため、異なる環境間での移植性が高まります。|
|自動化と効率化|Dockerfile を使用することで、アプリケーションのビルドや環境構築を自動化できます。これにより、手動での作業時間やヒューマンエラーが減り、開発・デプロイプロセスが効率的になります。|
|再現性|Dockerfile はビルド手順を完全に定義するため、異なる環境で同じアプリケーションを再現できます。これにより、開発、テスト、本番環境間での一貫性が確保されます。|
|環境の分離|Docker コンテナはホストシステムから分離されるため、アプリケーションの依存関係やライブラリの衝突を回避し、より安全な環境で実行できます。|
|拡張性|Dockerfile を使用することで、カスタムイメージを作成することができます。このため、特定のニーズに合わせてカスタマイズされたコンテナイメージを容易に作成できます。|
:::

</details>

## 開発環境

+ Windows 11 Home 22H2 を使用しています。  

:::note info
[WSL](https://learn.microsoft.com/ja-jp/windows/wsl/about) の Ubuntu を操作していきますので macOS の方も参考にして頂けます。  
:::

<details>

WSL (Microsoft Store アプリ版) [※ こちらの関連記事からインストール方法をご確認いただけます](https://qiita.com/studio_meowtoon/items/2aaa6579cb8e4fa1d874)

```sh
> wsl --version
WSL バージョン: 1.0.3.0
カーネル バージョン: 5.15.79.1
WSLg バージョン: 1.0.47
```

Ubuntu [※ こちらの関連記事からインストール方法をご確認いただけます](https://qiita.com/studio_meowtoon/items/2aaa6579cb8e4fa1d874)

```sh
$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 22.04.1 LTS
Release:        22.04
```

npm [※ こちらの関連記事からインストール方法をご確認いただけます](https://qiita.com/studio_meowtoon/items/111f556d8fb11a76430a)

```sh
$ node -v
v19.8.1
```

```sh
$ npm -v
9.5.1
```

Docker [※ こちらの関連記事からインストール方法をご確認いただけます](https://qiita.com/studio_meowtoon/items/3bceee25ac67cc73ebbe)

```sh
$ docker --version
Docker version 23.0.1, build a5ee5b1
```

</details>

:::note warn
この記事では基本的に **Ubuntu のターミナルで操作**を行います。**Vim を使用してコピペする方法**を初めて学ぶ人のために、以下の記事で手順を紹介しています。ぜひ挑戦してみてください。
:::

https://qiita.com/studio_meowtoon/items/7771ad48676926e41463

## 作成する Web アプリケーションの仕様

|No|エンドポイント|HTTPメソッド|MIME タイプ|
|:--|:--|:--|:--|
|1|/api/data|GET|application/json|

:::note info
**/api/data** というエンドポイントに対して HTTP GET リクエストを送信すると、JSON データがレスポンスされるシンプルな Web サービスを実装します。
**{"message":"Hello World!"}**
:::

## Hello World を表示する手順

### プロジェクトの作成

プロジェクトフォルダを作成します。  
※ ~/tmp/hello-express をプロジェクトフォルダとします。

```sh
$ mkdir -p ~/tmp/hello-express
$ cd ~/tmp/hello-express
```

### JS ファイルの作成

**server.js** JS ファイルを作成します。

```sh
$ mkdir -p backend/src
$ vim backend/src/server.js
```

ファイルの内容

```js:backend/server.js
const express = require('express');
const app = express();
const port = process.env.PORT || 5000;

app.get('/api/data', (req, res) => {
    const response = {
        message: "Hello World!"
    };
    res.json(response);
});

app.listen(port, () => {
    console.log(`Server is running on http://localhost:${port}`);
});
```

### webpack 設定の作成

**webpack.config.js** ファイルを作成します。

```sh
$ vim backend/webpack.config.js
```

ファイルの内容

```js:backend/webpack.config.js
const path = require('path');

module.exports = {
  mode: 'development',
  target: 'node',
  entry: './src/server.js',
  output: {
    path: path.resolve(__dirname, 'build'),
    filename: 'server.bundle.js',
  },
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env'],
          },
        },
      },
    ],
  },
};
```

### プロジェクトの初期化

プロジェクトの初期化を行います。

```sh
$ cd backend
$ npm init -y
```

package.json を修正します。

```sh
$ vim package.json
```

ファイルの内容

```sh:package.json
{
  "name": "hello-express",
  "version": "1.0.0",
  "description": "",
  "main": "webpack.config.js",
  "scripts": {
    "start": "node build/server.bundle.js",
    "build": "webpack"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}
```

ライブラリをインストールします。

```sh
$ npm install express --save
$ npm install babel-loader @babel/core @babel/preset-env \
    webpack webpack-cli --save-dev
```

ローカルでビルド・実行します。
停止するときは ctrl + C を押します。

```sh
$ npm run build
$ npm start
```

別ターミナルから curl コマンドで確認します。

```sh
$ curl -v http://localhost:5000/api/data -w '\n'
```

出力

```sh
*   Trying 127.0.0.1:5000...
* Connected to localhost (127.0.0.1) port 5000 (#0)
> GET /api/data HTTP/1.1
> Host: localhost:5000
> User-Agent: curl/7.81.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< X-Powered-By: Express
< Content-Type: application/json; charset=utf-8
< Content-Length: 26
< ETag: W/"1a-iEQ9RXvkycqsT4vWvcdHrxZT8OE"
< Date: Fri, 04 Aug 2023 05:10:15 GMT
< Connection: keep-alive
< Keep-Alive: timeout=5
<
* Connection #0 to host localhost left intact
{"message":"Hello World!"}
```

:::note info
ここまでの手順で、ターミナルに **{"message":"Hello World!"}** と表示され、**JSON データ**を取得することが出来ました。
:::

### コンテナイメージをビルド

Dockerfile を作成します。

```sh
$ vim Dockerfile
```

ファイルの内容

```Dockerfile:backend/Dockerfile
# build the app.
FROM node:lts-bookworm-slim as build-env

# set the working dir.
WORKDIR /app

# copy json files to the working dir.
COPY package.json package-lock.json /app/
COPY webpack.config.js /app/

# copy the source files to the working dir.
COPY src /app/src

# install dependencies listed in package.json.
RUN npm install

# run the build command to build the app.
RUN npm run build

# set up the production container.
FROM node:lts-bookworm-slim

# set the working dir.
WORKDIR /app

# install dumb-init for process management.
RUN apt-get update && \
    apt-get install -y dumb-init

# copy the build output from the build-env.
COPY --from=build-env /app/build /app/build

# install express.
RUN npm install express

# expose port.
EXPOSE 5000

# command to run the app.
CMD ["dumb-init","node","build/server.bundle.js"]
```

Docker デーモンを起動します。

```sh
$ sudo service docker start
 * Starting Docker: docker    [ OK ]
```

:::note warn
Docker 環境をお持ちでない場合は、以下の関連記事から Docker Engine のインストール手順をご確認いただけます。
:::

https://qiita.com/studio_meowtoon/items/3bceee25ac67cc73ebbe

コンテナイメージをビルドします。

```sh
$ docker build \
    --no-cache \
    --tag app-hello-express:latest .
```

コンテナイメージを確認します。

```sh
$ docker images | grep app-hello-express
app-hello-express   latest    6c3f5b84bbf4   31 seconds ago   269MB
```

:::note info
ここまでの手順で、ローカル環境の **Docker** にアプリの**カスタムコンテナイメージ**をビルドすることができました。
:::

### コンテナを起動

ローカルでコンテナを起動します。
※ コンテナを停止するときは ctrl + C を押します。

```sh
$ docker run --rm \
    --publish 5000:5000 \
    --name app-local \
    app-hello-express:latest
```

:::note info
ここまでの手順で、ローカル環境の **Docker** でアプリの**カスタムコンテナ**を起動することができました。
:::

### コンテナの動作確認

別ターミナルから curl コマンドで確認します。

```sh
$ curl -v http://localhost:5000/api/data -w '\n'
```

出力

```sh
*   Trying 127.0.0.1:5000...
* Connected to localhost (127.0.0.1) port 5000 (#0)
> GET /api/data HTTP/1.1
> Host: localhost:5000
> User-Agent: curl/7.81.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< X-Powered-By: Express
< Content-Type: application/json; charset=utf-8
< Content-Length: 26
< ETag: W/"1a-iEQ9RXvkycqsT4vWvcdHrxZT8OE"
< Date: Thu, 03 Aug 2023 07:12:50 GMT
< Connection: keep-alive
< Keep-Alive: timeout=5
<
* Connection #0 to host localhost left intact
{"message":"Hello World!"}
```

:::note info
ここまでの手順で、ターミナルに **{"message":"Hello World!"}** と表示され、**JSON データ**を取得することが出来ました。 
::: 

コンテナの状態を確認してみます。

```sh
$ docker ps
CONTAINER ID   IMAGE                      COMMAND                   CREATED         STATUS         PORTS                                       NAMES
f505344b0132   app-hello-express:latest   "docker-entrypoint.s…"   5 minutes ago   Up 5 minutes   0.0.0.0:5000->5000/tcp, :::5000->5000/tcp   app-local
```

### コンテナに接続

別ターミナルからコンテナに接続します。  

```sh
$ docker exec -it app-local /bin/sh
```

コンテナに接続後にディレクトリを確認します。
※ コンテナから出るときは ctrl + D を押します。

```
# pwd
/app
# ls -lah
total 44K
drwxr-xr-x  1 root root 4.0K Aug 10 01:53 .
drwxr-xr-x  1 root root 4.0K Aug 10 02:20 ..
drwxr-xr-x  2 root root 4.0K Aug 10 01:53 build
drwxr-xr-x 60 root root 4.0K Aug 10 01:53 node_modules
-rw-r--r--  1 root root  22K Aug 10 01:53 package-lock.json
-rw-r--r--  1 root root   53 Aug 10 01:53 package.json
```

top コマンドで状況を確認します。

```sh
# apt update
# apt install procps
# top
top - 02:28:08 up  2:37,  0 user,  load average: 0.13, 0.03, 0.01
Tasks:   4 total,   1 running,   3 sleeping,   0 stopped,   0 zombie
%Cpu(s):  0.0 us,  0.1 sy,  0.0 ni, 99.9 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
MiB Mem :   7897.1 total,   2492.5 free,   1786.6 used,   3913.8 buff/cache
MiB Swap:   2048.0 total,   2048.0 free,      0.0 used.   6110.5 avail Mem

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
    1 root      20   0    2328    888    796 S   0.0   0.0   0:00.01 dumb-init
    8 root      20   0  604752  51572  35612 S   0.0   0.6   0:00.11 node
   15 root      20   0    2576    868    776 S   0.0   0.0   0:00.01 sh
  203 root      20   0    8560   4464   2596 R   0.0   0.1   0:00.00 top
```

コンテナの情報を表示してみます。

```sh
$ cat /etc/*-release
PRETTY_NAME="Debian GNU/Linux 12 (bookworm)"
NAME="Debian GNU/Linux"
VERSION_ID="12"
VERSION="12 (bookworm)"
VERSION_CODENAME=bookworm
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"
```

:::note warn
このコンテナは Debian GNU/Linux をベースに作成されています。つまり、Debian GNU/Linux と同じように扱うことができます。
:::

### ディレクトリ・ファイル構成

プロジェクトのファイル構成を表示してみます。

```sh
$ tree -I 'node_modules'
.
└── backend
    ├── Dockerfile
    ├── build
    │   └── server.bundle.js
    ├── package-lock.json
    ├── package.json
    ├── src
    │   └── server.js
    └── webpack.config.js
```

## まとめ

WSL Ubuntu の Docker 環境で、Dockerfile からビルドした Node.js Express Web サービスのカスタムコンテナを起動することができました。

:::note warn
クラウド開発においては、**Dockerfile の理解**は重要です。自動ビルドツールもありますが、手動で書く必要があるケースもあります。**Ubuntu** を使うと **Linux の知識**も身に付きます。最初は難しく感じるかもしれませんが、徐々に進めていけば自信を持って書けるようになります。
:::

どうでしたか？ WSL Ubuntu で、Node.js Express Web アプリケーションを手軽に実行することができます。ぜひお試しください。今後も Node.js の開発環境などを紹介していきますので、ぜひお楽しみにしてください。

## 推奨コンテンツ

https://qiita.com/studio_meowtoon/items/948356c4b7e13198708a

## 関連記事

#### Java Spring Boot

https://qiita.com/studio_meowtoon/items/9c07e20b4124d8c5f972