LoginSignup
5
7

More than 5 years have passed since last update.

Express.js(ejs) + Sequelize(MySQL)でrailsのscaffoldアプリを作る

Last updated at Posted at 2017-04-25

Sequelizeの挙動を確認するために、rails generate scaffoldでできるサンプルアプリをSequelizeとexpress.jsで作ってみる。
今回やるのは、rails g scaffold user name:string age:integerした結果と同じようなものを作る。
コレ↓ (下はrailsのscaffoldで作ったもの)
Kobito.iPf3wC.png

Sequelizeはこちら。
https://github.com/sequelize/sequelize
ドキュメントはこちら。
http://docs.sequelizejs.com/en/v3/

セットアップ

$ express -e sequelize-express
$ npm install
$ npm install --save sequelize
$ npm install --save mysql
# 関係無いけどlint入れた
$ npm install --save-dev eslint

sequelizeコマンド使ってモデルやmigration作ったりすると便利なので、入れてない場合はsequelize-cliも入れておく。
https://github.com/sequelize/cli

$ npm install -g sequelize-cli

Model, Migration

ここからmodel, migrationをざっと作っていく。
この辺りの流れはこちらの記事がわかりやすかった。
参考: nodejs + express + sequelize webサーバ最小構成
また、あんまりドキュメントが詳しく無いけど、$ sequelize help:[command name]みたいな感じでhelp見ると詳しく書いてるのでオススメ。

$ sequelize init

これで、下記のように作成される。/config/config.jsonにDBの情報が記載されている。

├─config
│ └─config.json
├─migrations
├─seeders
└─models

利用するDBをローカルに作っておく。今回は”sequelize_express_development”というdbを作成して、/config/config.jsonの記載を適宜訂正した。

次にUserモデルを作成する。
はじめ下記のようにコマンド叩いていたがエラーになった。

$ sequelize model:create --name User --attributes name:string, age:integer
Task 'age:integer' is not in your gulpfile

エラーになる。$ sequelize help:model:createを見るに、

$ sequelize model:create --name User --attributes 'name:string, age:integer'$ sequelize model:create --name User --attributes name:string,age:integer

みたいな感じにしないといけない。
これで、/models//migrations/以下にファイルが作成される。便利!!

作成されたmigrationファイルからUserテーブルを作成する。

$ sequelize db:migrate

Controller, View

Controllerは/routes/user.js、Viewは/views/users/に作っていく。下記に完成したものを貼っておく。

/routes/user.js
var express = require('express');
const models = require('../models');

var router = express.Router();

// index
router.get('/', function(req, res, next) {
  models.User.findAll().then(function(results) {
    res.render('users/index', {users: results});
  });
});

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

// create
router.post('/create', function(req, res) {
    models.User.create({
        name: req.body.name,
        age: req.body.age
    }).then(function() {
        res.redirect('/users');
    });
});

// show
router.get('/:user_id', function(req, res) {
    models.User
        .findOne({where: {id: req.params.user_id}})
        .then(function(result) {
            res.render('users/show', {user: result});
        });
});

// edit
router.get('/:user_id/edit', function(req, res) {
    models.User
        .findOne({where: {id: req.params.user_id}})
        .then(function(result) {
            res.render('users/edit', {user: result});
        });
});

// update
router.put('/:user_id', function(req, res) {
    models.User
        .update({
            name: req.body.name,
            age: req.body.age
        }, {
            where: {
                id: req.params.user_id
            }
        }).then(function() {
            res.redirect('/users');
        });
});

// destroy
router.delete('/:user_id', function(req, res) {
    models.User
        .destroy({
            where: {
                id: req.body.userId
            }
        }).then(function() {
            res.redirect('/users');
        });
});


module.exports = router;

viewは/views/users以下に、_form.ejs, index.ejs, new.ejs, show.ejs, edit.ejsを作った。
htmlのformでは、PUTDELETE直接対応されてないので、少し工夫する必要あり。
参考:[html/css] httpのフォームでDELETEやPUTのメソッドを送る方法

で、expressで、この辺りを対応するには、npmでmethod-overrideを入れる必要があるみたい。
参考:Express 4.x でmethod-overrideでput / delete メソッドを使いたいにハマる。
これですね。 expressjs/method-override
ということで、入れる。

$ npm install --save method-override

今回はこの書き方で対応した。
override using a query value

app.js
var express = require('express')
var methodOverride = require('method-override')
var app = express()

app.use(methodOverride('_method'))

として、viewでは、

<form method="post" action="/users/1?_method=put">
  <button type="submit">Update!!</button>
</form>

みたいな感じで、put, delete使うことができる。
また、input type="hidden"の形式で書く場合はこちら形で対応するといけるみたい。 custom logic

ここでは、/views/users/index.ejs/views/users/_form.ejsを下記に記載する。(さっと書いたので汚いですが:bow:

/views/users/index.ejs
<!DOCTYPE html>
<html>
  <head>
    <title>Sample App</title>
    <link rel='stylesheet' href='/stylesheets/style.css' />
  </head>
  <body>
    <h1>Sequelize Express</h1>
    <p>Welcome!!</p>

    <% if (users) { %>
        <table>
            <tr>
                <th>Name</th>
                <th>Age</th>
            </tr>
        <% users.forEach(function(value, key) { %>
                <tr>
                    <td><%= value.name %></td>
                    <td><%= value.age %></td>
                    <td><a href="/users/<%= value.id %>">Show</a></td>
                    <td><a href="/users/<%= value.id %>/edit">Edit</a></td>
                    <td>
              <form name="delete<%= value.id %>" action="/users/<%= value.id %>?_method=delete" method="post">
                <input type="hidden" name="userId" value="<%= value.id %>">
                <a href="#" onclick="document.delete<%= value.id %>.submit()">Delete</a>
              </form>
            </td>
                </tr>
        <% }); %>
        </table>
    <% } %>
    <a href="/users/new">create new user</a>
  </body>
</html>

/views/users/_form.ejs
<%
let form_action = '#';
let form_name = '';
let form_age = '';

switch(template) {
    case 'new':
        form_action = '/users/create';
        break;
    case 'edit':
        form_action = `/users/${user.id}?_method=put`;
        form_name = `${user.name}`;
        form_age = `${user.age}`;
        break;
}
%>

<form action="<%= form_action %>" method="post">
    <label for="name">Name</label>
    <input type="text" name="name" value="<%= form_name %>">
    <label for="age">Age</label>
    <input type="number" name="age" value="<%= form_age %>">
    <input type="submit" value="submit">
</form>

あとは、new,edit,show辺りのviewを作れば完成。:thumbsup:

Kobito.mOhZ64.png

コードはgithubに置いたので、興味ある人いれば見てみて下さい。
mazeltov7/sequelize-express-scaffold

おまけ

Userに紐付いたCommentも作ってみる。
まずはモデルをさっと作る。

$ sequelize model:create --name Comment --attributes content:text

デフォルトでは、foreignKeyがCamelCaseになってるけど(userIdみたいな)、今回はsnake_caseに変更してやった。
migrationファイルを作って、Commentsテーブルにuser_idカラムを追加する。(今回はmigrationファイルを別途作ってColumn追加してみたかったので、テーブル作成と別にやってみた。)

$ sequelize migration:create --name AddColumnToComment
/migrations/201732323-AddColumnToComment.js
'use strict';

module.exports = {
  up: function (queryInterface, Sequelize) {
    return queryInterface.addColumn(
      'Comments',
      'user_id',
      {
        type: Sequelize.INTEGER,
        allowNull: false
      }
    );
  },

  down: function (queryInterface, Sequelize) {
    return queryInterface.removeColumn(
      'Comments',
      'user_id'
    );
  }
};

で、次にモデル。Commentモデル側にはbelongsToを記載する。

/models/comment.js
'use strict';
module.exports = function(sequelize, DataTypes) {
  var Comment = sequelize.define('Comment', {
    content: DataTypes.TEXT,
    user_id: DataTypes.INTEGER
  }, {
    classMethods: {
      associate: function(models) {
        // associations can be defined here
        Comment.belongsTo(models.User, {'foreignKey': 'user_id'})
      }
    }
  });
  return Comment;
};

Userモデル側にはhasMany記載する。

/models/user.js
'use strict';
module.exports = function(sequelize, DataTypes) {
  var User = sequelize.define('User', {
    name: DataTypes.STRING,
    age: DataTypes.INTEGER
  }, {
    classMethods: {
      associate: function(models) {
        // associations can be defined here
        User.hasMany(models.Comment, {'foreignKey': 'user_id'})
      }
    }
  });
  return User;
};

テーブル作成。

$ sequelize db:migrate

コントローラ側も修正する。includeして、getで取得してくる。

/routes/user.js
// show
router.get('/:user_id', function(req, res) {
    models.User
        .findOne({
            where: {id: req.params.user_id}
        }, {
            include: [ models.Comment]
        })
        .then(function(userResult) {
            userResult.getComments().then(function(commentResults) {
                res.render('users/show', {user: userResult, comments: commentResults});
            })
        });
});

view側も修正。

/views/users/show.ejs
    <% if (comments) { %>
      <ul>
        <% comments.forEach(function(value, key) { %>
          <li><%= value.content %></li>
        <% }) %>
      </ul>
    <% } %>

で、こんな感じ。

Kobito.xZpc4l.png

5
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
7