Skip to content
Code Blue プログラミング学習ノート
Javascript/Next.js

Node.jsからMySQLに接続(PKG使用)

はじめに

このガイドでは、Node.jsからmysql2パッケージを利用してMySQLデータベースへ接続する方法を学びます。基本的な接続方法から、接続プールやプリペアドステートメント、トランザクション処理、エラーハンドリング、パフォーマンスチューニングまで、実践的な内容を順番に解説します。また、よくあるトラブルの対処法やサンプルアプリケーションも紹介し、実際の開発で活用できる知識を身に付けます。

mysql2パッケージの特徴と利点

mysql2はNode.js用の高性能MySQLクライアントで、以下の特徴があります。mysql2は、従来のmysqlパッケージよりも高速に動作するNode.js向けのMySQLドライバーです。

プリペアドステートメントやPromiseベースのAPIに対応しているため、async/awaitを利用した分かりやすいコードを記述できます。また、接続プールやSSL接続を標準でサポートしており、MySQLサーバーとの高い互換性を備えているため、実用的なWebアプリケーション開発に適しています。

プロジェクトのセットアップ

mysqlを使用するために必要な環境を構築します。

必要なパッケージのインストール

まずは、mysql2パッケージをインストールして、Node.jsからMySQLへ接続できる環境を準備します。

npm install mysql2
# または
yarn add mysql2

TypeScriptを使用する場合(オプション)

TypeScriptを使用する場合は、typescriptやNode.jsの型定義もあわせてインストールし、tsconfig.jsonを作成してTypeScriptの設定を行います。これにより、型安全な開発環境を構築できます。

npm install --save-dev @types/node typescript
# tsconfig.jsonを作成
npx tsc --init

基本的な接続方法

MySQLへ接続する手順と方法について。

環境変数の設定(.envファイル)

.envファイルにデータベースのホスト名やポート番号、ユーザー名、パスワード、データベース名を設定します。接続情報をソースコードと分離することで、安全かつ管理しやすくなります。

DB_HOST=localhost
DB_PORT=3306
DB_USER=your_username
DB_PASSWORD=your_password
DB_NAME=your_database

基本的な接続例

mysql2/promiseを利用してMySQLへの接続処理を作成しています。環境変数から接続情報を読み込み、createConnection()でデータベース接続を確立できるようにしています。

// db.js
const mysql = require('mysql2/promise');
require('dotenv').config();

async function createConnection() {
  return await mysql.createConnection({
    host: process.env.DB_HOST,
    port: process.env.DB_PORT,
    user: process.env.DB_USER,
    password: process.env.DB_PASSWORD,
    database: process.env.DB_NAME
  });
}

module.exports = { createConnection };

接続テストスクリプト

作成した接続処理を利用してMySQLへ接続し、SELECT 1 + 1を実行して接続を確認しています。最後に接続を閉じることで、リソースを適切に解放しています。

// testConnection.js
const { createConnection } = require('./db');

(async () => {
  let connection;
  try {
    connection = await createConnection();
    console.log('MySQLに接続しました');

    // 簡単なクエリの実行
    const [rows] = await connection.query('SELECT 1 + 1 AS result');
    console.log('1 + 1 =', rows[0].result);
  } catch (error) {
    console.error('接続エラー:', error);
  } finally {
    if (connection) await connection.end();
    console.log('接続を閉じました');
  }
})();

接続プールの活用

実運用では接続プールを使用するのがベストプラクティスです。

接続プールの設定

createPool()を使用すると、複数のデータベース接続を管理できます。接続を再利用することで毎回接続を作成する必要がなくなり、アプリケーションのパフォーマンスを向上させることができます。

// pool.js
const mysql = require('mysql2/promise');
require('dotenv').config();

const pool = mysql.createPool({
  host: process.env.DB_HOST,
  port: process.env.DB_PORT,
  user: process.env.DB_USER,
  password: process.env.DB_PASSWORD,
  database: process.env.DB_NAME,
  waitForConnections: true,
  connectionLimit: 10,
  queueLimit: 0
});

module.exports = pool;

プールを使用したクエリ実行

接続プールを利用してSQLを実行する例です。query()でデータを取得し、プレースホルダー(?)を使用して値を渡すことで、安全に条件付き検索を行うことができます。

const pool = require('./pool');

async function getUsers() {
  const [rows] = await pool.query('SELECT * FROM users LIMIT 10');
  return rows;
}

async function getUserById(id) {
  const [rows] = await pool.query('SELECT * FROM users WHERE id = ?', [id]);
  return rows[0] || null;
}

プリペアドステートメントの使用

SQLインジェクション対策に必須です。

基本的な使用方法

execute()を使用すると、プレースホルダー(?)に値を渡しながら安全にSQLを実行できます。この例では、新しいユーザーを登録し、追加されたレコードのIDを取得しています。

async function createUser(userData) {
  const sql = 'INSERT INTO users (name, email, age) VALUES (?, ?, ?)';
  const [result] = await pool.execute(sql, [
    userData.name,
    userData.email,
    userData.age
  ]);
  return result.insertId;
}

複雑な例(WHERE IN句)

WHERE IN句を利用すると、複数のIDを指定してまとめてデータを取得できます。配列を渡すだけで複数条件の検索ができるため、一括取得を効率的に実装できます。

async function getUsersByIds(ids) {
  const sql = 'SELECT * FROM users WHERE id IN (?)';
  const [rows] = await pool.query(sql, [ids]);
  return rows;
}

// 実際には以下のように使用
// await getUsersByIds([1, 2, 3]);

トランザクション処理

このコードは、トランザクションを利用して口座間の送金処理を実装した例です。まず送金元の残高を減らし、続いて送金先の残高を増やします。

両方の処理が成功した場合はcommit()で変更を確定し、途中でエラーが発生した場合はrollback()によってすべての変更を取り消します。最後に接続を解放することで、データの整合性を保ちながら安全に送金処理を実行しています。

async function transferMoney(fromId, toId, amount) {
  const conn = await pool.getConnection();
  try {
    await conn.beginTransaction();
    
    // 送金元の残高を減らす
    await conn.query(
      'UPDATE accounts SET balance = balance - ? WHERE id = ? AND balance >= ?',
      [amount, fromId, amount]
    );
    
    // 送金先の残高を増やす
    await conn.query(
      'UPDATE accounts SET balance = balance + ? WHERE id = ?',
      [amount, toId]
    );
    
    // トランザクションをコミット
    await conn.commit();
    return true;
  } catch (error) {
    await conn.rollback();
    console.error('Transfer failed:', error);
    return false;
  } finally {
    conn.release();
  }
}

エラーハンドリング

このコードは、MySQLエラーを適切に処理する方法で、安全にデータベースへクエリを実行するための関数です。SQLの実行に成功した場合は取得したデータを返し、エラーが発生した場合はエラー情報をログへ出力します。また、重複データや外部キー制約違反などの代表的なエラーを判定し、アプリケーションで扱いやすい独自のエラーメッセージへ変換しています。これにより、エラーの原因を把握しやすく、安全なデータベース処理を実現できます。

async function safeQuery(sql, params) {
  try {
    const [rows] = await pool.query(sql, params);
    return { success: true, data: rows };
  } catch (error) {
    console.error('Database error:', {
      sql,
      params,
      errorCode: error.code,
      errno: error.errno,
      sqlState: error.sqlState,
      sqlMessage: error.sqlMessage
    });

    // アプリケーション固有のエラーに変換
    if (error.code === 'ER_DUP_ENTRY') {
      return { success: false, error: 'Duplicate entry' };
    }
    if (error.code === 'ER_NO_REFERENCED_ROW_2') {
      return { success: false, error: 'Foreign key constraint fails' };
    }

    return { success: false, error: 'Database operation failed' };
  }
}

パフォーマンスチューニング

接続プールの最適化

接続プールの各種設定を調整することで、接続数やタイムアウトを適切に管理できます。これにより、データベースへの負荷を抑えながら、安定したパフォーマンスを維持できます。

const pool = mysql.createPool({
  // ...他の設定
  connectionLimit: 20,          // 最大接続数
  idleTimeout: 60000,           // アイドル接続のタイムアウト(ms)
  enableKeepAlive: true,         // 接続を維持
  keepAliveInitialDelay: 0,      // キープアライブの初期遅延
  connectTimeout: 10000,         // 接続タイムアウト(ms)
  queueLimit: 0                  // 接続待ちキューの制限(0 = 無制限)
});

クエリ最適化のヒント

検索速度を向上させるには、インデックスの活用や必要な列だけを取得することが重要です。また、適切なデータ型を選択し、大量のデータを扱う場合はLIMITとOFFSETを利用したページネーションを実装することで、効率的にデータを取得できます。

async function getPaginatedUsers(page = 1, pageSize = 10) {
  const offset = (page - 1) * pageSize;
  const [rows] = await pool.query(
    'SELECT id, name, email FROM users ORDER BY id LIMIT ? OFFSET ?',
    [pageSize, offset]
  );
  return rows;
}

よくある問題と解決策

接続タイムアウト

connectTimeoutを設定すると、データベースへの接続が一定時間内に完了しない場合にタイムアウトとなります。接続待ちが長引くことを防ぎ、アプリケーションの応答性を維持できます。

解決策

const pool = mysql.createPool({
  // ...他の設定
  connectTimeout: 10000, // 10秒
});

接続が切れる

enableKeepAliveを有効にすると、接続を維持するための通信が定期的に行われます。長時間アイドル状態でも接続が切れにくくなり、安定した運用が可能です。

解決策 (キープアライブを有効化)

const pool = mysql.createPool({
  // ...他の設定
  enableKeepAlive: true,
  keepAliveInitialDelay: 0
});

ER_NOT_SUPPORTED_AUTH_MODE

このエラーは、MySQL 8.0の認証方式がクライアントと一致しない場合に発生します。ユーザーの認証方式をmysql_native_passwordへ変更することで、接続できるようになります。MySQL 8.0の認証方式変更によるエラーの対処です。

解決策

-- MySQLで実行
ALTER USER 'your_username'@'localhost' IDENTIFIED WITH mysql_native_password BY 'your_password';
FLUSH PRIVILEGES;

10. 実践的なサンプルアプリケーション

以下のコード例は実践的なアプリのサンプルになります。

ユーザー管理APIの例

このコードは、MySQLを利用したユーザー管理用のリポジトリを実装した例です。findAll()やfindById()でユーザーを取得し、create()・update()・delete()で登録・更新・削除を行います。データベース操作をクラスにまとめることで、保守性や再利用性を高めています。

// userRepository.js
const pool = require('./pool');

class UserRepository {
  async findAll() {
    const [rows] = await pool.query('SELECT * FROM users');
    return rows;
  }

  async findById(id) {
    const [rows] = await pool.query('SELECT * FROM users WHERE id = ?', [id]);
    return rows[0];
  }

  async create(user) {
    const [result] = await pool.query(
      'INSERT INTO users (name, email, password_hash) VALUES (?, ?, ?)',
      [user.name, user.email, user.passwordHash]
    );
    return result.insertId;
  }

  async update(id, userData) {
    const [result] = await pool.query(
      'UPDATE users SET name = ?, email = ? WHERE id = ?',
      [userData.name, userData.email, id]
    );
    return result.affectedRows > 0;
  }

  async delete(id) {
    const [result] = await pool.query('DELETE FROM users WHERE id = ?', [id]);
    return result.affectedRows > 0;
  }
}

module.exports = new UserRepository();

Expressアプリケーションでの使用例

このコードは、ExpressでREST APIを実装した例です。GET /usersでは全ユーザーを取得し、GET /users/:idでは指定したIDのユーザーを取得します。また、エラーハンドリングを行い、問題が発生した場合は適切なHTTPステータスコードとエラーメッセージを返しています。

// app.js
const express = require('express');
const userRepository = require('./userRepository');

const app = express();
app.use(express.json());

app.get('/users', async (req, res) => {
  try {
    const users = await userRepository.findAll();
    res.json(users);
  } catch (error) {
    res.status(500).json({ error: 'Internal server error' });
  }
});

app.get('/users/:id', async (req, res) => {
  try {
    const user = await userRepository.findById(req.params.id);
    if (!user) {
      return res.status(404).json({ error: 'User not found' });
    }
    res.json(user);
  } catch (error) {
    res.status(500).json({ error: 'Internal server error' });
  }
});

// 他のルートも同様に実装...

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

まとめ

このガイドで、Node.jsからmysql2パッケージを使ってMySQLデータベースに接続する方法を包括的に学びました。実際のプロジェクトでは、要件に応じてさらに最適化を加えてください。