はじめに
仮想環境のお勉強がてら、WSL2で仮想環境を作ってその中で簡単なWebアプリを動かしてみたので、どんなことをしたのか書いていこうと思います。
※筆者のOSはWindowsです。また、WSL2がインストールされているのが前提です。
今回作ったもの
言語を選んでボタンを押すと、その言語の挨拶が表示されるだけのアプリです。
- WSL2を利用したUbuntuの仮想環境
- Apache2を利用したWebサーバー
- Node.js + Expressを利用したAPIサーバー
- MySQLを利用したデータベース
構成としては、すべてUbuntu環境の中で完結しています。
[Windowsのブラウザ]
│ http://localhost
▼
┌─────────────────────────────────┐
│ Ubuntu (WSL2) │
│ │
│ Apache2 (:80) │
│ └ /var/www/html/index.html │
│ │ fetch │
│ ▼ │
│ Express (:3000) │
│ │ SQL │
│ ▼ │
│ MySQL (:3306) │
└─────────────────────────────────┘
WindowsのブラウザからWSL2内のApacheに http://localhost でアクセスできるのは、WSL2のlocalhostフォワーディング機能のおかげです。特別なポート設定は不要でした。
作業の流れ
- WSL2でUbuntuの仮想環境を作成
- Apache2をインストール
-
/var/www/html/index.htmlを書き換える -
/var/www/html/js/greet.jsを作成 - MySQLをインストール
- テーブルを定義する
- Node.jsとExpressをインストール
-
~/express-app/app.jsを作成 - localhostからWebアプリを触る
それぞれの作業を詳しく
WSL2でUbuntuの仮想環境を作成
ターミナルを開き、以下のwslコマンドでUbuntu環境を作成します。
インストールの際にデフォルトのユーザーアカウントを作成するので、任意の名前とパスワードを設定します。
wsl --install -d Ubuntu
Apache2をインストール
Ubuntu環境に入れたら、Apache2をインストールします。
sudo apt update
sudo apt install -y apache2
apt update でインストールできるパッケージの最新リストを取得し、apt install -y apache2 でApache2をインストールします。
インストール後、サービスを起動します。
sudo service apache2 start
WSL2ではsystemdが有効になっていない場合があり、その場合 systemctl ではなく service コマンドを使います。また、WSLを再起動するとサービスは停止するので、都度起動が必要です。
この時点で http://localhost にアクセスすると、Apacheのデフォルトページが表示されます。
/var/www/html/index.html を書き換える
デフォルトルートのindex.htmlを今回用に書き換えます。以下のコマンドでvimで開きます。
sudo vi /var/www/html/index.html
内容を以下のように書き換えます。greet.js については次に記載します。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>挨拶アプリ</title>
</head>
<body>
<h3>挨拶アプリ</h3>
<select name="greetings" id="greet-select">
<option value="ja">日本語</option>
<option value="en">英語</option>
<option value="ch">中国語</option>
</select>
<button id="greet-button" onclick="pushButton(document.getElementById('greet-select').value)">挨拶する</button>
<br>
<div id="result"></div>
<script src="/js/greet.js"></script>
</body>
</html>
日本語や中国語を表示するので、<meta charset="UTF-8"> の指定を忘れないようにします。これがないと文字化けします。
/var/www/html/js/greet.js を作成
以下のコマンドで js ディレクトリを作成し、greet.js を作成します。
cd /var/www/html && sudo mkdir js
cd js && sudo touch greet.js
vimで開きます。
sudo vi greet.js
以下の内容を記述します。
※後で作成するAPIをfetchしているところがありますが気になさらず……
const pushButton = async (lang) => {
const response = await fetch(`http://localhost:3000/?lang=${lang}`);
const results = await response.json();
document.getElementById('result').innerHTML = `<h3>${results[0].word}</h3>`;
};
APIは配列でレコードを返すので、results[0] で先頭のレコードを取り出し、word カラムの値を表示しています。
MySQLをインストール
以下のコマンドでMySQLをインストールし、サービスを起動します。
sudo apt install -y mysql-server
sudo service mysql start
インストールが完了したらrootユーザーでMySQLに入ります。
sudo mysql -u root
テーブルを定義する
以下のコマンドでデータベースとテーブルを作成し、挨拶データを登録します。
CREATE DATABASE greeting_database;
USE greeting_database;
CREATE TABLE greetings (
id INT NOT NULL AUTO_INCREMENT,
lang VARCHAR(10) NOT NULL,
word VARCHAR(100) NOT NULL,
PRIMARY KEY (id)
);
INSERT INTO greetings (lang, word) VALUES
('ja', 'こんにちは'),
('en', 'Hello'),
('ch', '你好');
id はAUTO_INCREMENTなので、INSERTの際は指定しなくても自動で採番されます。
続いて、アプリから接続するための専用ユーザーを作成します。
CREATE USER 'appuser'@'localhost' IDENTIFIED BY 'password';
GRANT SELECT ON greeting_database.* TO 'appuser'@'localhost';
FLUSH PRIVILEGES;
UbuntuのMySQLはrootユーザーが auth_socket 認証になっているため、Node.jsからrootで接続しようとすると失敗することがあります。また、アプリからrootで接続するのはセキュリティ的にも避けたいので、専用ユーザーを作るのが確実です。
exit でMySQLを抜けます。
Node.jsとExpressをインストール
まずNode.jsとnpmをインストールします。
sudo apt install -y nodejs npm
node -v
npm -v
次にExpress用のディレクトリを作成し、プロジェクトを初期化して package.json を生成します。
mkdir ~/express-app
cd ~/express-app
npm init -y
sudo を付けずに作成するのがポイントです。sudoで作るとディレクトリの所有者がrootになり、後の npm install が権限エラーになります。
今回使用する express、cors、mysql2 をインストールします。
npm install express cors mysql2
~/express-app/app.js を作成
express-app 配下に app.js を作成し、中身を記述します。
touch app.js
vi app.js
内容は以下です。
const express = require('express');
const cors = require('cors');
const mysql = require('mysql2');
const app = express();
app.use(cors());
// コネクションプールを作成
const pool = mysql.createPool({
host: 'localhost',
user: 'appuser',
password: 'password',
database: 'greeting_database'
});
// ルートエンドポイントの設定
app.get('/', (req, res) => {
pool.query(
'SELECT * FROM greetings WHERE lang = ?',
[req.query.lang],
(error, results) => {
if (error) {
res.status(500).json({ error: error.message });
return;
}
res.status(200).json(results);
}
);
});
// サーバーの起動
const PORT = 3000;
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
ポイントは3つあります。
app.use(cors()) はルート定義より前に書く
Webページはポート80(Apache)、APIはポート3000で動いているため、ブラウザからは別オリジン扱いになりCORSエラーになります。corsミドルウェアで許可しますが、ミドルウェアは上から順に適用されるので、app.get() より後ろに書くと効きません。
SQLは値を直接埋め込まない
WHERE lang = ? のようにプレースホルダを使い、値は配列で渡します。文字列連結で組み立てるとSQLインジェクションの原因になります。
コネクションはプールで管理する
リクエストのたびに createConnection すると、接続が閉じられずに溜まっていきます。createPool を使うと接続を使い回してくれるので、この心配がありません。
localhostからWebアプリを触る
あとは動かすだけです。以下のコマンドでNodeサーバーを起動します。
cd ~/express-app
node app.js
Server is running on port 3000 と表示されたら起動成功です。
localhostにアクセスしてみます。
続いて、言語を選択して挨拶するボタンを押してみます。
英語であいさつしてくれました!ちゃっちいアプリですが、動いてくれると嬉しいですね。
うまく動かない場合は、ブラウザの開発者ツール(F12)のコンソールとネットワークタブを見ると原因がわかりやすいです。「blocked by CORS policy」ならCORS設定、http://localhost:3000/?lang=ja へのリクエストが失敗しているならNodeサーバーかMySQLの起動状況を確認してみてください。
最後に
Webサーバー、APIサーバー、DBサーバーを仮想環境で動かす基礎的なところが学べた気がします。
次はDockerを使って3コンテナ作って実装してみようと思います。
参考

