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

2025-08-26

このガイドでは、Node.jsアプリケーションからmysql2パッケージを使ってMySQLデータベースに接続する方法を、基礎から実践まで詳しく解説します。

目次

  1. mysql2パッケージの特徴と利点
  2. プロジェクトのセットアップ
  3. 基本的な接続方法
  4. 接続プールの活用
  5. プリペアドステートメントの使用
  6. トランザクション処理
  7. エラーハンドリング
  8. パフォーマンスチューニング
  9. よくある問題と解決策
  10. 実践的なサンプルアプリケーション

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

mysql2はNode.js用の高性能MySQLクライアントで、以下の特徴があります:

  • オリジナルのmysqlパッケージより高速
  • プリペアドステートメントのサポート
  • PromiseベースのAPI(async/await対応)
  • 接続プーリングの内蔵サポート
  • SSL接続のサポート
  • MySQLサーバーとの互換性が高い

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

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

npm install mysql2
# または
yarn add mysql2

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

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

3. 基本的な接続方法

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

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

基本的な接続例

// 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 };

接続テストスクリプト

// 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('接続を閉じました');
  }
})();

4. 接続プールの活用

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

接続プールの設定

// 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;

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

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;
}

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

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

基本的な使用方法

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句)

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]);

6. トランザクション処理

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();
  }
}

7. エラーハンドリング

MySQLエラーを適切に処理する方法:

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' };
  }
}

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

接続プールの最適化

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

クエリ最適化のヒント

  1. インデックスの活用
  2. 必要な列のみ選択
  3. 適切なデータ型の使用
  4. 大量データの場合はページネーション
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;
}

9. よくある問題と解決策

問題1: 接続タイムアウト

解決策:

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

問題2: 接続が切れる

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

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

問題3: ER_NOT_SUPPORTED_AUTH_MODE

MySQL 8.0の認証方式変更によるエラー:

解決策:

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

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

ユーザー管理APIの例

// 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アプリケーションでの使用例

// 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}`);
});

さらに学ぶために

  1. 公式ドキュメント: mysql2 GitHubリポジトリ
  2. ORMの使用: Sequelize, TypeORM, PrismaなどのORMを検討
  3. マイグレーション: knex.jsやumzugなどのマイグレーションツールの導入
  4. 監視: クエリのパフォーマンス監視ツールの導入

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