Old Sunset Days

Node.jsでSequelizeを使ってRDBを扱う

日付 タグ node.js カテゴリ node.js

目次

Node.jsからSequelizeでSQLite3を扱う

Node.jsからRDB(MySQL, PostgreSQL, MariaDBやSQLiteなど)を扱う場合、各々に対応したパッケージを利用してもいいのだが、何かしらのORM(Object Relational Mapping)を利用しておけば、どのDBシステムを利用するにしても同じようにプログラム実装できて、DBシステムのスイッチもしやすい。

そこで、Node.jsで良く使われているORMであるSequelizeを利用して、今回はSQLite3のDBにデータを書き込む手順を見ていく。

https://sequelize.org/ - Sequelize ORM

なお、今回作ったサンプルのソースコードは参照用としてGithubの以下に置いてある。 https://github.com/hugodeblog/node-sequelize

sequelizeのインストールとサンプルコード

今回もまずはサンプルディレクトリの作成とパッケージインストールから

$ mkdir node-sequelize
$ cd node-sequelize
$ npm init --yes
$ npm install --save sequelize
$ npm install --save sqlite3
$ npm install --save js-yaml

sequelizeだけでなく、DBとしてsqlite3を使うので、sqlite3のパッケージもインストール。
また、DB接続の設定をyamlに書いて、それをプログラムから読み込むので、js-yamlもインストールしている。

簡単なサンプルとして、sequelizeを利用するsequelize.mjs を作成した。


sequelize.mjs

import Sequelize from 'sequelize';
import { default as jsyaml } from 'js-yaml';
import * as util from 'util';
import { promises as fs } from 'fs';

class User extends Sequelize.Model {}

let sequlz;

async function connectDB() {

  if(sequlz) return sequlz;

  const yamltext = await fs.readFile('sequelize-sqlite.yaml', 'utf8');
  const params = await jsyaml.safeLoad(yamltext, 'utf8');

  sequlz = new Sequelize(
    params.dbname,
    params.uesrname,
    params.password,
    params.params
  );

  User.init({
    username: {type: Sequelize.STRING, unique: true},
    password: Sequelize.STRING,
    address: Sequelize.STRING
  }, {
    sequelize: sequlz,
    modelName: 'User',
    timestamps: true
  });

  await User.sync({ force: true });
}


async function closeDB() {

  if (sequlz) sequlz.close();
  sequlz = undefined;

}

async function create(user, pass, address) {
   await connectDB();
   const result = await User.create( {
     username:user, password:pass, address:address
   });
}

async function update(user, pass, address) {
  await connectDB();
  var result = await User.findOne( {where:{username:user} });
  if(!result) {
    throw new Error(`Not found for ${user}`);
  } else {
    await User.update(
      {password:pass, address:address},
      {where:{username:user}}
    );
    result = await User.findOne( {where:{username:user} });
  }
}

async function destroy(user) {
  await connectDB();
  var result = await User.findOne( {where:{username:user} });
  if(!result) {
    throw new Error(`Not found for ${user}`);
  } else {
    await User.destroy( {where: {username: user} });
  }
}

async function readall() {
  await connectDB();
  const results = await User.findAll({});
  console.log('currentDB status:');
  console.log(results.map(result => (
    {'username': result.username, 'password':result.password, 'address':result.address}
  )));
}

(async () => {

  console.log('******** Sequelizeテストスタート ********');

  try {
    await connectDB();

    console.log('******** データ作成 ********');
    await create('taro', 'taro123', 'Tokyo');
    await create('jiro', 'jiro456', 'Osaka');
    await create('sabro', 'jiro789', 'Hokkaido');
    await readall();

    console.log('******** jiroのデータアップート ********');
    await update('jiro', '456jiro', 'Kanagawa');
    await readall();

    console.log('******** taroのデータ削除 ********');
    await destroy('taro');
    await readall();



  } catch (err) {
    console.error(err);
    console.log('エラーが出たのでテストを終了します');
  } finally {
    console.log('******** Sequelizeテスト終了 ********');
    closeDB();
  }
})();

DBへの接続とテーブル作成

少しプログラムの作りを見ておこう。

const yamltext = await fs.readFile('sequelize-sqlite.yaml', 'utf8');
const params = await jsyaml.safeLoad(yamltext, 'utf8');

まずはsequelize-sqlite.yaml にてDBへの接続情報を指定。そのパラメーターを読み込んでいる。

SQLite3の場合、ユーザー名、パスワードなしでローカルPCにおいてあるDBファイルへのアクセスだが、MySQL等を利用する場合は、DBアクセス時にDBのユーザー名、パスワードが必要になる。

dbname: users
username:
password:
params:
    dialect: sqlite
    storage: users-sequelize.sqlite3
    logging: false

なお、sequelizeではデフォルトだと、SQL命令発行のログがかなり多くなるので、ログ出力を抑えたければ、logging:false を指定しておく方がいいだろう。

ここでDB接続のパラメーターをyamlファイルから読み込まれば、Sequelizeのインスタンスを作成してDB接続ができる。

sequlz = new Sequelize(
  params.dbname,
  params.uesrname,
  params.password,
  params.params
);

次にDB上で今回テストに使うテーブルを作成する。

User.init({
  username: {type: Sequelize.STRING, unique: true},
  password: Sequelize.STRING,
  address: Sequelize.STRING
}, {
  sequelize: sequlz,
  modelName: 'User',
  timestamps: true
});

await User.sync({ force: true });
 

今回テーブル名はUsers(Userの複数形)になり、Sequelizeが自動的にデータに対して付加してくれるカラムcreatedAt, updatedAtも利用する。 timestamps: true はデフォルトでtrueなのであえて書かなくても、デフォルトで勝手に付与してくれるidの他にcreatedAt, updatedAtを利用することができる。createdAt, updatedAtが不必要ならtimestamps: false とすれば良い。 なお、SQLite3ではタイムゾーンの概念がなくUTC時刻で記録されることには注意が必要。

User.sync({ force: true }); によって毎回テストする時にテーブルをDropして作り直している。

  • User.sync()
    • テーブルが存在しなければテーブル作成する
  • User.sync({ force: true })
    • もしもテーブルがすでに存在していればテーブルをDropして作りなおす
  • User.sync({ alter: true })
    • テーブルが存在していて、テーブルのカラムとかに変更あるようならAlterテーブルする

データのINSERT

さて新規でデータを追加するにはどうするのか。

async function create(user, pass, address) {
   await connectDB();
   const result = await User.create( {
     username:user, password:pass, address:address
   });
}

usernameカラムにuser、passwordカラムにpass、addressカラムにaddressのデータがINSERTされる。

データのUPDATE

async function update(user, pass, address) {
  await connectDB();
  var result = await User.findOne( {where:{username:user} });
  if(!result) {
    throw new Error(`Not found for ${user}`);
  } else {
    await User.update(
      {password:pass, address:address},
      {where:{username:user}}
    );
    result = await User.findOne( {where:{username:user} });
  }
}

データのアップデートではまずアップデートする対象のユーザーデータが存在するかどうかチェックしている。もしなければエラーをthrowする。 データが存在すればupdateによって各カラムを更新している。

データのDELETE

async function destroy(user) {
  await connectDB();
  var result = await User.findOne( {where:{username:user} });
  if(!result) {
    throw new Error(`Not found for ${user}`);
  } else {
    await User.destroy( {where: {username: user} });
  }
}

データの削除でもまずアップデートする対象のユーザーデータが存在するかどうかチェックしている。もしなければエラーをthrowする。 データが存在すればdestoryによってユーザー名をキーにしてデータを検索して削除している。

全データの読み込み

生成(INSERT)、編集(UPDATE)、削除(DELETE)については見てきたが、デバッグ用にDBテーブル中の全データをダンプ表示する機能も欲しいので、それも実装として付け足す。

async function readall() {
  await connectDB();
  const results = await User.findAll({});
  console.log('currentDB status:');
  console.log(results.map(result => (
    {'username': result.username, 'password':result.password, 'address':result.address}
  )));
}

findAllで取り出した情報からusername、password、addressに関する部分だけ取り出してコンソールログ表示。

サンプルコードの実行確認

さて、今しがたのサンプルを実行してみよう。

$ node sequelize.mjs
******** Sequelizeテストスタート ********
******** データ作成 ********
currentDB status:
[
  { username: 'taro', password: 'taro123', address: 'Tokyo' },
  { username: 'jiro', password: 'jiro456', address: 'Osaka' },
  { username: 'sabro', password: 'jiro789', address: 'Hokkaido' }
]
******** jiroのデータアップート ********
currentDB status:
[
  { username: 'taro', password: 'taro123', address: 'Tokyo' },
  { username: 'jiro', password: '456jiro', address: 'Kanagawa' },
  { username: 'sabro', password: 'jiro789', address: 'Hokkaido' }
]
******** taroのデータ削除 ********
currentDB status:
[
  { username: 'jiro', password: '456jiro', address: 'Kanagawa' },
  { username: 'sabro', password: 'jiro789', address: 'Hokkaido' }
]
******** Sequelizeテスト終了 ********

問題なく実行できて、データの作成、編集、削除ができることが確認できた。

なお、SQLite3を扱う時は、DB Browser for SQLiteを入れておくとデータの確認など何かと便利であるのインストールしておくといいだろう。

https://sqlitebrowser.org/ - DB Browser for SQLite

ここから自分の場合だとMac版をダウンロードしてインストールすれば良い。

DB Browser for SQLiteをインストールしてあれば、GUI上で最終的なDBのデータをこんな感じでデータ閲覧が可能になる。