sequelize CRUD - Express.js

  • 作成日:
  • 最終更新日:2025/10/11

パッケージのインストール

以下のパッケージを利用します。

  • connect-flash@0.1.1
  • cookie-parser@1.4.6
  • debug@2.6.9
  • ejs@3.1.10
  • express-session@1.18.0
  • express@4.19.2
  • http-errors@1.6.3
  • morgan@1.9.1
  • mysql2@3.9.4
  • sequelize@6.37.3

パッケージをインストールするには、以下のコマンドを実行します。

npm install mysql2
npm install sequelize
npm install -g sequelize-cli
npm install connect-flash
npm install express-session

ディレクトリ構成

Express Generator で作成します。

  • example
    • bin
      • www
    • config
      • config.json
    • migrations
      • ...中略...
    • models
      • index.js
      • user.js
    • node_modules
      • ...中略...
    • public
      • images
      • javascripts
      • stylesheets
        • style.css
    • routes
      • crud.js
    • seeders
      • file
    • views
      • crud
        • add.ejs
        • delete.ejs
        • edit.ejs
        • index.ejs
        • show.ejs
    • app.js
    • package-lock.json
    • package-json

sequelize の作業

次のコマンドを実行し、sequelize の初期化をします。

sequelize init

config/config.json が作成されるため、データベースの設定を自身の環境に合わせて編集します。

設定が終われば、次のコマンドを実行しデータベースを作成します。

sequelize db:create

データベースが作成されたら、次のコマンドを実行しモデルを作成します。

sequelize model:create --underscored --name user --attributes "name:string,age:integer"

models ディレクトリの中に、user.js というファイルが作成されます。

最後に次のコマンドを実行しマイグレーションをします。

sequelize db:migrate

ファイルの内容

app.js

var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
const session = require("express-session"); // +
const flash = require('connect-flash'); // +

var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
var crudRouter = require('./routes/crud'); // +

var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

// +
app.use(session({
  secret: "secret",
  resave: false,
  saveUninitialized: true,
}));
app.use(flash()); // +

 // +
app.use((req, res, next) => {
  res.locals.messages = req.flash();
  next(); 
});


app.use('/', indexRouter);
app.use('/users', usersRouter);
app.use('/crud', crudRouter); // +

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  next(createError(404));
});

// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

module.exports = app;

routes/crud.js

var express = require('express');
var router = express.Router();
var db = require('../models/');

router.get('/', function(req, res, next) {
  db.user.count().then((total) => {
db.user.findAll().then(users => {
  res.render('crud/index', { users: users, count: total});
});    
  });
});

router.get('/add', function(req, res, next) {
  res.render('crud/add');
});

router.post('/add', function(req, res, next) {
  console.log(req.body)
  db.user.create({
name: req.body.name,
age: req.body.age
  }).then((result) => {
console.log(result);
req.flash("success", "登録されました。");
res.redirect('/crud');
  });
});

router.get('/show/:id', function(req, res, next) {
  db.user.findOne({
where: { id: req.params.id }
  }).then((user) => {
console.log(user);
res.render('crud/show', { user: user });
  });
});

router.get('/edit/:id', function(req, res, next) {
  db.user.findOne({
where: { id: req.params.id }
  }).then((user) => {
console.log(user);
res.render('crud/edit', { user: user });
  });
});

router.post('/edit', function(req, res, next) {
  db.user.update(
{ name: req.body.name, age: req.body.age},
{ where: { id: req.body.id} }
  ).then((result) => {
console.log(result);
req.flash("success", "更新されました。");
res.redirect(`/crud/edit/${req.body.id}`);
  });
});

router.get('/delete/:id', function(req, res, next) {
  db.user.findOne({
where: { id: req.params.id }
  }).then((user) => {
console.log(user);
res.render('crud/delete', { user: user });
  });
});

router.post('/delete', function(req, res, next) {
  db.user.destroy(
{ where: { id: req.body.id }}
  ).then(() => {
req.flash("success", "削除されました。");
res.redirect('/crud');
  });
});

module.exports = router;

VIEWS

views/crud/add.ejs

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <div>
<h1>CRUD Add</h1>
<p><a href="/crud">ページトップへ</a></p>
<form method="POST" action="/crud/add" >
  <p><label for="name_form">名前:<input type="text" name="name" id="name_form"></label></p>
  <p><label for="age_form">年齢:<input type="number" name="age"></label></p>
  <p><input type="submit" value=" 送信 " /></p>
</form>
  </div>
</body>
</html>

views/crud/delete.ejs

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <div>
<h1>CRUD - Delete</h1>
<p><a href="/crud">ページトップへ</a></p>
<table>
  <tr>
    <th>id</th>
    <th>name</th>
    <th>age</th>
  </tr>
  <tr>
    <td><%= user["id"] %></td>
    <td><%= user["name"] %></td>
    <td><%= user["age"] %></td>
  </tr>
</table>
<form method="POST" action="/crud/delete" >
  <input type="hidden" name="id" value="<%= user['id'] %>">
  <p><input type="submit" value="削除" /></p>
</form>
  </div>
</body>
</html>

views/crud/edit.ejs

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <div>
<h1>CRUD - Edit</h1>
<p><a href="/crud">ページトップへ</a></p>
<% if(messages.success){ %>
  <p><%= messages.success %></p>
<% } %>
<form method="POST" action="/crud/edit" >
  <input type="hidden" name="id" value="<%= user['id'] %>">
  <p>ID:<%= user['id'] %></p>
  <p><label for="name_form">名前:<input type="text" name="name" id="name_form" value="<%= user['name'] %>"></label></p>
  <p><label for="age_form">年齢:<input type="number" name="age" value="<%= user['age'] %>"></label></p>
  <p><input type="submit" value=" 送信 " /></p>
</form>
  </div>
</body>
</html>

views/crud/index.ejs

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link rel="stylesheet" href="/stylesheets/style.css">
  <title>Document</title>
</head>
<body>
  <div>
<h1>CRUD index</h1>
<p><a href="/crud/add">新規追加</a></p>
<% if(messages.success){ %>
  <p><%= messages.success %></p>
<% } %>
<p>レコード総数:<%= count %></p>
<table>
  <tr>
    <th>id</th>
    <th>name</th>
    <th>age</th>
    <th>action</th>
  </tr>
<% for(let i of users){ %>
  <tr>
    <td><%= i["id"] %></td>
    <td><a href="/crud/show/<%= i['id'] %>"><%= i["name"] %></a></td>
    <td><%= i["age"] %></td>
    <td><a href="/crud/edit/<%= i['id'] %>">変更</a><a href="/crud/delete/<%= i['id'] %>">削除</a></td>
  </tr>
<% } %>
</table>
  </div>
</body>
</html>

views/crud/show.ejs

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>CRUD - Show</title>
</head>
<body>
  <div>
<h1>CRUD - Show</h1>
<p><a href="/crud">ページトップへ</a></p>
<table>
  <tr>
    <th>id</th>
    <th>name</th>
    <th>age</th>
  </tr>
  <tr>
    <td><%= user["id"] %></td>
    <td><%= user["name"] %></td>
    <td><%= user["age"] %></td>
  </tr>
</table>
<p><a href="/crud/edit/<%= user['id'] %>">変更ページへ</a></p>
<p><a href="/crud/delete/<%= user['id'] %>">削除ページへ</a></p>
  </div>
</body>
</html>

楽観的ロックの確認

楽観的ロックを追加し動作するかの確認です。

まずモデルに「 version: true 」を追加します。

models/user.js

'use strict';
const {
  Model
} = require('sequelize');
module.exports = (sequelize, DataTypes) => {
  class user extends Model {
/**
 * Helper method for defining associations.
 * This method is not a part of Sequelize lifecycle.
 * The `models/index` file will call this method automatically.
 */
static associate(models) {
  // define association here
}
  }
  user.init({
name: DataTypes.STRING,
age: DataTypes.INTEGER
  }, {
sequelize,
modelName: 'user',
underscored: true,
version: true, // +
  });
  return user;
};

一度、データベースの usersテーブルを削除し、sequelize db:migrate を再度実行します。

とりあえずテーブルにデータを1つ投入します。

マイグレーションが終わった後、「 routes/crud.js 」の edit に関するルーティングを次のように変更します。

routes/crud.js

router.get('/edit/:id', function(req, res, next) {
  try {
db.user.findOne({
  where: { id: req.params.id }
}).then((user) => {
  res.render('crud/edit', { user: user });
}).catch((error => {
  req.flash("errors", "問題が発生しました。");
  res.redirect('/crud');
}));
  } catch(error) {
req.flash("errors", "問題が発生しました。");
res.redirect('/crud');
  }
});

router.post('/edit', function(req, res, next) {
  (async function(){
let transaction = await db.sequelize.transaction();
try {
  const user = await db.user.findOne({
      where: {
        id: req.body.id
      }
  }, { transaction: transaction });
  console.log(`age: ${user.age} version: ${user.version}`);
  user.name = req.body.name;
  user.age = req.body.age;
  user.dataValues.version = req.body.version;
  await user.save({ transaction: transaction });
  transaction.commit();
  transaction = null;
  req.flash("success", "更新されました。");
  res.redirect(`/crud/edit/${req.body.id}`);
} catch(error) {
  console.log(error); // ※  OptimisticLockError [SequelizeOptimisticLockError]: Attempting to update a stale model instance: user
  if (transaction) {
    transaction.rollback();
  }
  req.flash("errors", "更新できませんでした。");
  res.redirect('/crud');
}
  })();
});

「 views/crud/edit.ejs 」を次のようにします。

views/crud/edit.ejs

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <div>
<h1>CRUD - Edit</h1>
<p><a href="/crud">ページトップへ</a></p>
<% if(messages.success){ %>
  <p><%= messages.success %></p>
<% } %>
<form method="POST" action="/crud/edit" >
  <input type="hidden" name="id" value="<%= user['id'] %>">
  <!-- ↓ 変更箇所 -->
  <input type="text" name="version" value="<%= user['version']%>" >
  <p>ID:<%= user['id'] %></p>
  <p><label for="name_form">名前:<input type="text" name="name" id="name_form" value="<%= user['name'] %>"></label></p>
  <p><label for="age_form">年齢:<input type="number" name="age" value="<%= user['age'] %>"></label></p>
  <p><input type="submit" value=" 送信 " /></p>
</form>
  </div>
</body>
</html>

別々のウィンドウで、同じデータの変更のページを開くと「 name="version" 」のテキストエリアに同じ数字が入っています。

まず、1つのウィンドウの更新作業を行います。次に、もう一つのウィンドウも更新をしてみます。

2つ目のウィンドウで更新作業をするとコンソールに「 OptimisticLockError [SequelizeOptimisticLockError]: Attempting to update a stale model instance: user 」と表示されていれば、楽観的ロックは機能しています。