Expressサーバーにデータベースを接続する
これまで2つのことを別々に学びました。ExpressでAPIサーバーを作る方法、そしてSQLでデータベースを扱う方法。今回はこの2つを接続します。
前回のExpressの例では、試料データはJavaScriptの配列にありました:
const samples = [
{ id: "S001", name: "Blood Sample A", od: 1.85, status: "pass" },
// ...
];サーバーを再起動するとこの配列は初期状態に戻ります。POSTで新しい試料を登録しても、サーバーを再起動すれば消えてしまいます。実験ノートに鉛筆で書いては毎回消しゴムで消すようなものです。
データベースを接続すればこの問題が解決します。Expressが配列の代わりにデータベースにデータを保存・取得するようにすれば — サーバーが停止しても、コンピュータを再起動しても、データは安全です。
接続準備:mysql2パッケージのインストール
Node.jsからMySQLに接続するにはmysql2パッケージが必要です:
npm install mysql2そしてデータベース接続設定をコードに記述します:
const mysql = require("mysql2");
const db = mysql.createConnection({
host: "localhost",
user: "root",
password: "パスワード",
database: "lab_db"
});
db.connect(function(err) {
if (err) {
console.log("DB接続失敗:", err.message);
return;
}
console.log("DB接続成功");
});実験装置にログインするのと同じです — アドレス(host)、アカウント(user/password)、どのデータベースを使うか(database)を伝えます。
配列をDBに置き換え:SELECT
既存のExpressコードで配列の代わりにSQLクエリでデータを取得します。
Before(配列):
app.get("/samples", function(req, res) {
res.json(samples);
});After(DB):
app.get("/samples", function(req, res) {
db.query("SELECT * FROM samples", function(err, rows) {
if (err) {
res.status(500).json({ error: "DB照会失敗" });
return;
}
res.json(rows);
});
});db.query()はSQLをデータベースに送り、結果をコールバックで受け取ります。rowsは配列形式で返ってきます — 以前自分で作った配列と同じ構造です。フロントエンドのコードは変更不要です。
特定の試料を照会:WHEREとプレースホルダ
URLパラメータで特定の試料を照会する際、ユーザー入力をSQLに直接入れるとSQLインジェクションというセキュリティ攻撃にさらされます。プレースホルダ(?)を使います:
app.get("/sample/:id", function(req, res) {
db.query(
"SELECT * FROM samples WHERE id = ?",
[req.params.id],
function(err, rows) {
if (err) {
res.status(500).json({ error: "照会失敗" });
return;
}
if (rows.length === 0) {
res.status(404).json({ error: "試料が見つかりません" });
return;
}
res.json(rows[0]);
}
);
});?の位置に[req.params.id]の値が安全に挿入されます。こうすれば、悪意あるユーザーがURLにSQLコードを入れても、データベースはそれをデータとしてのみ扱います。
プロトコルに試料番号だけを入れ替える欄を作っておくのと同じです — その欄に何を入れても試料番号としてのみ解釈され、プロトコル自体を変えることはできません。
試料登録:INSERT
POSTリクエストで新しい試料を登録し、データベースに永久保存します:
app.use(express.json());
app.post("/samples", function(req, res) {
const { name, od, status } = req.body;
db.query(
"INSERT INTO samples (name, od, status, created_at) VALUES (?, ?, ?, NOW())",
[name, od, status],
function(err, result) {
if (err) {
res.status(500).json({ error: "登録失敗" });
return;
}
res.json({
message: "試料登録完了",
id: result.insertId
});
}
);
});result.insertIdは今追加された行の自動生成IDです。NOW()は現在時刻を自動で入れるMySQL関数です。
総合例:試料管理CRUD API
これまで学んだことを合わせた全体のサーバーコードです:
const express = require("express");
const mysql = require("mysql2");
const app = express();
app.use(express.json());
const db = mysql.createConnection({
host: "localhost",
user: "root",
password: "パスワード",
database: "lab_db"
});
// 全試料リスト
app.get("/samples", function(req, res) {
db.query("SELECT * FROM samples ORDER BY created_at DESC", function(err, rows) {
if (err) return res.status(500).json({ error: err.message });
res.json(rows);
});
});
// 特定の試料を照会
app.get("/sample/:id", function(req, res) {
db.query("SELECT * FROM samples WHERE id = ?", [req.params.id], function(err, rows) {
if (err) return res.status(500).json({ error: err.message });
if (rows.length === 0) return res.status(404).json({ error: "試料なし" });
res.json(rows[0]);
});
});
// 試料登録
app.post("/samples", function(req, res) {
const { name, od, status } = req.body;
db.query(
"INSERT INTO samples (name, od, status, created_at) VALUES (?, ?, ?, NOW())",
[name, od, status],
function(err, result) {
if (err) return res.status(500).json({ error: err.message });
res.json({ message: "登録完了", id: result.insertId });
}
);
});
// QC合格試料のみフィルタリング
app.get("/samples/passed", function(req, res) {
db.query("SELECT * FROM samples WHERE status = 'pass'", function(err, rows) {
if (err) return res.status(500).json({ error: err.message });
res.json({ count: rows.length, samples: rows });
});
});
app.listen(3000, function() {
console.log("試料管理APIサーバー: http://localhost:3000");
});このサーバーの特別な点 — 配列ベースのExpressサーバーとAPIインターフェースが同一です。/samples、/sample/:id、/samples/passed — 同じアドレス、同じレスポンス形式。変わったのは内部ストレージだけです。フロントエンドのコードは一文字も修正する必要がありません。
これがバックエンドとフロントエンドを分離する最大のメリットです。ストレージをファイルからMySQLへ、MySQLからPostgreSQLへ変えても、APIが同じならフロントエンドは影響を受けません。
やってみよう(Faded Example)
空欄を埋めて特定の研究者の試料を照会するExpressルートを完成させてください。
app.get("/researcher/:name/samples", function(req, res) {db.("SELECT * FROM samples WHERE researcher = ",[req..name],function(err, rows) {if (err) return res.status(500).json({ error: err.message });res.json(rows);});});
よくあるエラーと解決法
Q: ER_ACCESS_DENIED_ERROR: Access denied for userエラーが出ます
createConnectionのuser、password、databaseが正しいか確認してください。MySQLに該当アカウントが存在し、そのデータベースへのアクセス権限がある必要があります。
Q: ECONNREFUSEDエラーが出ます
MySQLサーバーが起動していません。Macではbrew services start mysql、Linuxではsudo systemctl start mysqlでMySQLを先に起動してください。
Q: クエリ結果が空の配列[]で返ってきます
テーブルにデータがないか、WHERE条件に合う行がありません。まずMySQLクライアントでSELECT * FROM samples;を直接実行してデータがあるか確認してください。
Q: 日本語データが文字化けして保存されます
データベースとテーブルの文字セット(charset)がutf8mb4であるか確認してください。ALTER DATABASE lab_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;で変更できます。