8. データベース

Node.jsからデータベースに接続する方法を学びます。SQLiteを使用して、データの作成・読み取り・更新・削除を行います。

この章で学べること

  • Node.jsからデータベースに接続する方法
  • データの作成(Create): INSERT文の実行
  • データの読み取り(Read): SELECT文の実行
  • データの更新(Update): UPDATE文の実行
  • データの削除(Delete): DELETE文の実行
  • データベース操作でのエラー処理方法

データベースとは?

データベースは、アプリケーションの心臓部とも言える重要な技術です。ユーザー情報、商品データ、学習進捗など、アプリケーションで扱う様々な情報を安全かつ効率的に保存・管理するために使用されます。

この章では、Node.jsからデータベースに接続し、データの登録・取得・更新・削除(CRUD操作)を行う方法を学習します。SQLiteという軽量なデータベースを使用して、実際にコードを書きながらデータベースの基本操作を身につけていきましょう。

データベース接続の流れ

Node.js からデータベースに接続する流れはどの種類のデータベースでもほぼ共通です。

  1. DBクライアントライブラリをインストール
  2. 接続情報(ホスト名・ユーザー名・パスワード・DB名など)を設定
  3. 接続を確立
  4. クエリを実行
  5. 結果を取得して処理
  6. 接続を閉じる

プロジェクトを作成しよう

まずは、プロジェクトのディレクトリを作成します。 node-database という名前で作成します。

mkdir node-database
cd node-database

次にnpm initコマンドを実行します。プロジェクト管理ファイルの package.json が作成されます。

npm init -y

次のコマンドで package.jsonにmoduleモードの設定を追加します。

npm pkg set type=module

SQLite をインストール

次のコマンドでSQLiteをインストールします。SQLite は「ファイル1つで動く軽量データベース」で、開発・学習・小規模アプリに最適です。

npm install sqlite sqlite3

SQLiteに接続

SQLiteに接続する機能を作成します。db.jsというファイルを作成して、次のコードを記入してください。

import sqlite3 from "sqlite3";
import { open } from "sqlite";

// SQLiteに接続する関数
export async function initDB() {
  const db = await open({
    // openで接続を確立
    filename: "./database.db", // DBファイル名
    driver: sqlite3.Database,
  });

  // usersテーブルを作成
  await db.exec(`
    CREATE TABLE IF NOT EXISTS users (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      name TEXT NOT NULL,
      email TEXT NOT NULL
    );
  `);

  return db;
}

データの登録

データベースにデータを登録しましょう。insert.jsを作成して、次のコードを入力してください。

import { initDB } from "./db.js"; // db.jsからinitDB関数をインポート

async function insertUser(name, email) {
  const db = await initDB(); // DB接続を確立

  try {
    // データを登録
    const result = await db.run(
      "INSERT INTO users (name, email) VALUES (?, ?)",
      [name, email]
    );

    console.log(`登録完了: ID = ${result.lastID}`);
  } finally {
    await db.close(); // 接続を閉じる
  }
}

// 登録するデータ
await insertUser("Taro", "taro@example.com");
await insertUser("Hanako", "hanako@example.com");

実行してみましょう。

node insert.js

結果

登録完了: ID = 1
登録完了: ID = 2

このとき database.db が自動生成され、ユーザーデータが追加されています。

データベースの初期化について

もしデータベースを初期化したい場合は、database.dbを削除してください。

データの取得

データベースからデータを取得しましょう。read.js を作成して、次のコードを入力してください。

import { initDB } from "./db.js"; // db.jsからinitDB関数をインポート

const db = await initDB();

try {
  const users = await db.all("SELECT * FROM users"); // クエリを実行
  console.log(users); // 結果を表示
} finally {
  await db.close(); // 接続を閉じる
}

実行

node read.js

結果

[
  { id: 1, name: 'Taro', email: 'taro@example.com' },
  { id: 2, name: 'Hanako', email: 'hanako@example.com' }
]

データの更新

データの更新をしてみましょう。update.jsを作成し、次のコードを記入してください。

import { initDB } from "./db.js"; // db.jsからinitDB関数をインポート

const db = await initDB();

try {
  // 例:id=1 のユーザーの名前と年齢を更新
  const userId = 1;
  const newName = "Jiro";
  const newEmail = "jiro@example.com";

  const result = await db.run(
    `UPDATE users SET name = ?, email = ? WHERE id = ?`,
    [newName, newEmail, userId]
  );

  if (result.changes > 0) {
    console.log(`ユーザーID ${userId} の情報を更新しました`);
  } else {
    console.log(`ユーザーID ${userId} は見つかりませんでした`);
  }
} finally {
  await db.close(); // 接続を閉じる
}

実行

node update.js

結果

ユーザーID 1 の情報を更新しました

データが更新されているかを確認してみましょう。

node read.js

結果

[
  { id: 1, name: 'Jiro', email: 'jiro@example.com' }, // このデータが更新されている
  { id: 2, name: 'Hanako', email: 'hanako@example.com' }
]

データの削除

データの削除をしてみましょう。delete.jsを作成し、次のコードを記入してください。

import { initDB } from "./db.js"; // db.jsからinitDB関数をインポート

const db = await initDB();

try {
  // ユーザーを全て削除
  const result = await db.run(`DELETE FROM users`);

  if (result.changes > 0) {
    console.log(`ユーザーを削除しました`);
  } else {
    console.log(`ユーザー削除に失敗しました`);
  }
} finally {
  // DB接続を閉じる
  await db.close();
}

実行

node delete.js

結果

ユーザーを削除しました

データが削除されているかを確認しましょう。

node read.js

結果

[]

エラーハンドリング

データベース操作では、様々なエラーが発生する可能性があります。適切なエラーハンドリングを実装することで、アプリケーションの安定性とユーザー体験を向上させることができます。

基本的なエラーハンドリング

データベース操作では、以下のようなエラーが発生する可能性があります。

  • 接続エラー: データベースサーバーに接続できない
  • SQLエラー: 無効なSQL文や構文エラー
  • 制約エラー: 主キー重複、外部キー制約違反など
  • 権限エラー: データベースへのアクセス権限がない

エラーハンドリングの実装例

error-handling.jsを作成して、適切なエラーハンドリングを実装してみましょう。

import { initDB } from "./db.js"; // db.jsからinitDB関数をインポート

const db = await initDB();

try {
  const users = await db.all("SELECT * FROM user"); // userテーブルは存在しない(正しくはusers)
  console.log(users); // 結果を表示
} finally {
  await db.close(); // 接続を閉じる
}

console.log("処理完了");

error-handling.jsを実行してみましょう。

node error-handling.js

結果

node:internal/modules/run_main:122
    triggerUncaughtException(
    ^

[Error: SQLITE_ERROR: no such table: user] {
  errno: 1,
  code: 'SQLITE_ERROR'
}

Node.js v22.15.0

何やらエラーが発生しているようですが、システム的でわかりにくいメッセージになっています。また、処理の最後に処理完了というメッセージが表示されるはずですが、表示されていません。これは、エラーが発生した時点で処理が止まってしまうためです。

上記のコードはエラーハンドリングが適切に実施されておらず、次の問題があります。

  • エラーメッセージがわかりにくい
  • 処理が途中で止まってしまう

上記の問題を解決するために、エラーハンドリングを改善しましょう。catch構文を追加して、エラーハンドリングを行います。

error-handling.js を次のような修正します。

import { initDB } from "./db.js";

const db = await initDB();

try {
  const users = await db.all("SELECT * FROM user");
  console.log(users);
} catch (error) { // エラーハンドリングを追加
  console.error("エラーが発生しました:", error.message); // エラー時に適切なメッセージを表示する
} finally {
  await db.close();
}

console.log("処理完了");

実行

node error-handling.js

結果

エラーが発生しました: SQLITE_ERROR: no such table: user
処理完了

メッセージが改善され、エラーが発生したということがわかりやすくなりました。また、catchによってエラーが適切に処理され、処理完了というメッセージが表示されるようになりました。

エラーハンドリングのベストプラクティス

  1. try-catch-finally文の使用 - catchで適切なエラー処理を実施する
  2. 具体的なエラーメッセージ - ユーザーにとって理解しやすいメッセージを提供

演習問題

以下の問題を解いて、データベース操作の理解を深めましょう。

演習1:エラーを発生させてみよう

先ほど書いたコードにエラーを発生させてみましょう。間違えたテーブル名を入力することでエラーを故意に発生させてみましょう。

insert.js

const result = await db.run(
    "INSERT INTO user (name, email) VALUES (?, ?)", // users → userに変更
    [name, email]
  );

read.js

const users = await db.all("SELECT * FROM user"); // users → userに変更

update.js

const result = await db.run(
    `UPDATE user SET name = ?, email = ? WHERE id = ?`, // users → userに変更
    [newName, newEmail, userId]
  );

delete.js

const result = await db.run(`DELETE FROM user`); // users → userに変更

それぞれのファイルを実行して、エラーが発生することを確認してください。

演習2:エラーハンドリングの追加

次のファイルに適切なエラーハンドリングを追加してください。

実装すべきファイル

  • create.js
  • read.js
  • update.js
  • delete.js

演習3:ユーザー管理システムの作成

ユーザー情報を管理するシステムを作成してください。以下の要件を満たす関数を実装しましょう。

要件

  • user-manager.jsを作成して次の機能を実装してください。
  • ユーザーの登録(名前、メールアドレス)
  • ユーザー情報の取得(全ユーザー、特定のユーザー)
  • ユーザー情報の更新
  • ユーザー情報の削除
  • 適切なエラーハンドリング

実装すべき関数

  1. createUser(name, email) - ユーザー作成
  2. getAllUsers() - 全ユーザー取得
  3. getUserById(id) - ID指定でユーザー取得
  4. updateUser(id, name, email) - ユーザー更新
  5. deleteUser(id) - ユーザー削除

まとめ

この章では、Node.jsからデータベースに接続し、基本的なCRUD操作を行う方法を学習しました。

この章で学んだこと

  • データベース接続: SQLiteを使用したNode.jsからのデータベース接続方法
  • CRUD操作: データの作成(Create)、読み取り(Read)、更新(Update)、削除(Delete)
  • エラーハンドリング: try-catch-finally文を使った適切なエラー処理

次回予告

次回はHTTPサーバーの基本的な仕組みを学びます。