- 投稿日:2019-07-04T23:23:07+09:00
nodejs + mysql + React + ReduxでCRUDアプリを作る Part1
概要
シンプルなCRUD(create, read, update, delete)アプリをデータベースはmysql, フロントエンドはReact + Reduxで作ってみます.
このPart1ではmysqlの設定, node.jsでAPIサーバーの作成まで行います.mysqlのインストール, 接続
まずmysqlをインストールします.
$ brew update $ brew install mysqlインストールが終わったら内容を見てみます.
$ brew info mysql次にデータベースを起動します. 起動は
mysql.server start(停止はmysql.server stop)$ mysql.server start Starting MySQL . SUCCESS!パスワードを設定しておきます.
$ mysql_secure_installation聞かれることは基本的にYesで答えます. パスワードは覚えておきましょう.
設定が終わったら接続します.$ mysql -uroot -p Enter password:[設定したパスワード]接続は
exitで抜けれます.mysqlでDB, テーブルの作成
mysqlに接続した状態でまずDBを作成します.
mysql> CREATE DATABASE sample;DBの作成結果を確認します.
mysql> show databases;作成したDBを使用可能にします.
mysql> use sample;次にテーブルを作成します.
idカラムにはauto_incrementを設定しておきます.
nameとstatusにはnot nullを設定します.
nameはuniqueにしておきます.mysql> create table user (id int auto_increment, name varchar(10) not null unique, status varchar(10) not null,テーブルの作成結果を確認します.
mysql> show tables; +------------------+ | Tables_in_sample | +------------------+ | user | +------------------+ 1 row in set (0.01 sec)テーブルの定義を確認する
mysql> desc user; +--------+-------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +--------+-------------+------+-----+---------+----------------+ | id | int(11) | NO | MUL | NULL | auto_increment | | name | varchar(10) | NO | PRI | NULL | | | status | varchar(10) | NO | | NULL | | +--------+-------------+------+-----+---------+----------------+ 3 rows in set (0.01 sec)ユーザーを作成します.
nodeがユーザー, localhostがホストになります.
パスワードは後で使うので覚えておきましょう.mysql> create user 'node'@'localhost' identified with mysql_native_password by 'パスワード';ユーザーの作成結果を確認します.
mysql> SHOW GRANTS for 'node'@'localhost';参照可能テーブルを指定します.
mysql> GRANT ALL ON sample.* to node@localhost;権限の付与結果を確認します.
mysql> SHOW GRANTS for 'node'@'localhost';試しにサンプルデータを作成してみます.
mysql> insert into user(name, status) values('Katsuomi', 'student');確認してみます.
mysql> select * from staff; +----+----------+---------+ | id | name | status | +----+----------+---------+ | 1 | Katsuomi | student | +----+----------+---------+ 1 row in set (0.01 sec)もう1人作ってみます
mysql> insert into user(name, status) values('Junki', 'student');確認します.
mysql> select * from user; +----+----------+---------+ | id | name | status | +----+----------+---------+ | 1 | Katsuomi | student | | 2 | Junki | student | +----+----------+---------+ 2 rows in set (0.00 sec)無事にidがauto_incrementされていることを確認しました.
APIサーバーの作成
適当にディレクトリを作って
server.jsを作り, そこに書いていきます.$ mkdir crud-node & cd crud-node $ npm init -y $ npm i mysql express body-parserserver.jsconst express = require('express'); const mysql = require('mysql'); const bodyParser = require('body-parser'); const cors = require('cors')({origin: true}); const app = express(); app.use(bodyParser.json()); app.use(cors); const client = mysql.createConnection({ host: 'localhost', user: 'node', password: 'HashSignBack1484?_!', port : 3306, database: 'sample' }); client.connect(function (err) { if (err) { console.error('error connecting: ' + err.stack); return; } console.log('connected as id ' + client.threadId); }); // read app.get('/user', (req, res) => { client.query('SELECT * from user;', (err, rows, fields) => { if (err) throw err; res.send(rows); }); }); // create app.post('/user/create', (req, res) => { const name = req.body.name; const status = req.body.status; client.query('INSERT INTO user SET ?', {name: name, status: status}, (err, result) => { if (err) throw err; res.send(result); }) }); // update app.put('/user/update', (req, res) => { const id = req.body.id; const status = req.body.status; client.query('UPDATE user SET status = ? WHERE id = ?', [status, id], (err, result) => { if (err) throw err; client.query('SELECT * from user;', (err, rows, fields) => { if (err) throw err; res.send(rows); }); }) }); // delete app.delete('/user/delete', (req, res) => { const id = req.body.id; client.query(`DELETE FROM user WHERE id = ?`, [id], (err, result) => { if (err) throw err; client.query('SELECT * from user;', (err, rows, fields) => { if (err) throw err; res.send(rows); }); }); }); app.listen(3001, () => console.log('Listening on port 3001!'))それではcurlでテストしていきましょう.
作成
$ curl -X POST -H "Content-Type:application/json" http://localhost:3000/user/create -d '{"name":"taro", "status": "adult"}'更新
$ curl -X PUT -H "Content-Type:application/json" http://localhost:3000/user/update -d '{"name":"taro", "status": "student"}'閲覧
curl http://localhost:3000/user削除
$ curl -X DELETE -H "Content-Type:application/json" http://localhost:3000/user/delete -d '{"name":"taro"}'最後に
次回はReact + Reduxでフロントエンドを作っていきます.
Happy Hacking
!
- 投稿日:2019-07-04T21:09:08+09:00
RDS ではパラメーターにまったく問題ないのに Incompatible-parameters が発生する場合がある
AWS の RDS では Incompatible-parameters というエラーが発生し、インスタンスを起動できなくなる場合があります。そのエラーに遭遇してしまったのですが、一般に言われている方法では解決できなかったので報告します。
結論
再起動を繰り返すとパラメーターが正常でも発生する場合がある。しばらく待つ事で起動できるようになる。
経緯
RDS に限らず RDBMS では起動直後の状態と、インデックス等必要な情報を十分メモリに蓄積した状態とでは性能にかなりの違いがあります。今回、RDS で作成した MySQL のインスタンスの起動直後のパフォーマンスを調査するために何度も再起動を繰り返していたのですが、データベースのステータスが突然 Incompatible-parameters になってしまい起動できなくなりました。
公式情報を調べる
調べてみると、すぐに日本語の公式なナレッジが見つかりました。
要約するとカスタムパラメータグループの設定に問題があり、インスタンスの起動に失敗しているという事になります。カスタムパラメーターグループとはデータベースをチューニングするためのパラメーターのグループであり、MySQL の my.cnf に相当する物です。
自分でインストールした MySQL でも my.cnf の内容が間違っていれば起動できない場合があります。Incompatible-parameters はその状態に相当するエラーであると考えられます。
パラメーターに問題のない事を確認した
RDS は十分なパフォーマンスを発揮できる状態にチューニングされていると考えられらますが、必要に応じてパラメーターの調節ができるようになっているわけです。今回、確かにパラメーターグループを新規作成し一部のパラメーターを書き換えていますので、その設定が間違っているのだと言われたら、確かにそうなのかも知れません。
しかし、パラメーターに問題があるのなら、起動できない時は起動できないし、起動できる時は起動できるはずです。状況によって起動できたりできなかったりするのなら、それはパラメーター起因のエラーとは言えないでしょう。しかし、
- 直前まで同じパラメーターで起動できていた
- パラメーターを再度確認し、起動が不可能な要素を見つけられなかった
- 同じパラメーターで新たにインスタンスを作成したが、起動も再起動も問題なく実行できた
というような状態であり、どうも公式ナレッジに記載されている状態とは異なるようでした。
サポートに問い合わせ (解決)
サポートに問い合わせた所、1営業日程度で返事が届きました。結論としては最初に書いた通りになりますが、カスタムパラメーターグループを利用しているインスタンスにおいて、何度も再起動を繰り返していると incompatible-parameters になり、起動できなくなる場合があるとの事です。今回は起動直後の状態の試験を行っていた最中でしたので、再起動は10回程度行っていました。しばらくすると自然回復するそうで、確かに問題のインスタンスはいつの間にか起動していました。
そういう物だと受け入れましょう
詳細な原因については非公開との事でしたが、再起動を繰り返す事で、RDS がなんらかの異常が起こっていると認識してしまったのかも知れません。
今回は検証環境での出来事なので実害はありませんでしたが、本番環境をメンテナンスする時等には必要以上に再起動を行わない等の注意が必要だと思います。特にパラメーターチューニングを伴うメンテナンスの時には、本当にパラメーターが間違っている場合との区別が難しい可能性があります。そんな時は是非この記事を思い出して下さい。
何度確認してもパラメーターの間違いが見つけられないのに incompatible-parameters が出続ける場合は、同じパラメーターグループで別のインスタンスを立ち上げてみると良いと思います。
- 投稿日:2019-07-04T16:40:59+09:00
AWS上でmysqlをrpmでインストールするには。
mysqlをrpmで構築した
案件でバージョンを細かく指定されたmysqlのリプロダクションを行った時に気づいたことのまとめです。
実際に入れたもの
mysql-community-client.x86_64 5.6.36-2.el6 installed mysql-community-common.x86_64 5.6.36-2.el6 installed mysql-community-libs.x86_64 5.6.36-2.el6 installed mysql-community-server.x86_64 5.6.36-2.el6 installedclientは本体。
commonはクライアントライブラリから必要とされ るファイルの全て。(MySQL データベース共通ファイル (/etc/mysql/my.cnf など)がある。
libsは各種ライブラリ
serverは実行ファイル。コレがないとmysqlの起動がそもそもできない。なぜか、こいつはrpmのホームから検索かけても引っかからなかった。
myqal-server5.6 el6 86_64el6 とか el7
RedHat Enterprise Linux 5, 6, 7 互換の事。AWSだと、Amazon Linuxがel6ベースだし、Amazon Linux2ならel7ベースだからコレにあわせて入れるようにすればスンナリ入る。
ただし、Amazon Linux2はamazon-linux-extrasっていう独自のライブラリを持っていて、あまりにも古いものだと強制的にバージョンの高いものを入れるように働いてくるので、注意。php5.3以下なんかはrpm使っても落としてこれなかったです。
- 投稿日:2019-07-04T13:09:27+09:00
Node.JSのSequelize ORM入門
開発者はもう観察しているように、バックエンドウェブ開発のためのExpress.JSというフレームワークが少しずつ人気になっています。それに加えて、Express.JSはMongoose ORMと共に、一般的にREST APIの作成に使います。しかし、MySQL、PostgreSQL、SQLiteなどのような他のDatabase Schemaを使いたい開発者の場合に、どうするのか?SQLに関するデータベース言語のための探せるORMがどこかにありますか?ありがたいことに、そんなことがあります。こういうことはSequelizeというORMです。前の書いた記事とは違って、この記事にExpress.JSにデータベースを統合するという話が書いてあります。これから、Express.JSをSequelizeで使用して開始する方法を紹介します。
こんにちは!マルクス(Twitter Account: @markusveeyola)と申します。現在、奥多摩日本語学校の学生で、同時に、IT開発者としてアルバイトをしています。宜しくお願い致します!
それでは、始めましょうか。
ORMとは
Object Relation Mappingというモデルはオブジェクトとリレーショナルデータベースの接続を手順としています。手動でSQLステートメントを書く必要性を排除するために使用されますが、むしろ、オブジェクトを通して、はるかに速くそしてより読みやすいQuery操作をすることができます。
Sequelizeとは
Sequelizeは、Node.js用のpromised-basedのORMです。それは習得が容易で、同期、関連付けのような機能を持っています。Sequelizeで PostgreSQL、MySQL、MariaDB、SQLite、MSSQLはサポートされています。
前提条件
後は、申請をするとき、我々の手順が正しく機能するようにするために、これらは必要な条件です。
- Node.jsとNPMはインストールした必要があります。
- Node Package Manager「NPM」の基本的な知識
- Node.js言語の基本的な知識
- Postman: HTTP Requestをテストするものを責任としています。
- 設定したSQLデータベース
開発時間
プロジェクトを初期化するために、先ずはアプリを作りたい場所のフォルダに行ってください。そのフォルダーから、ターミナルに行って、「mkdir」コマンドラインを使って、プロジェクトの名前として「SequelizeCRUD」に付けて、入力してください。新しいフォルダーは作られるはずです。その後はそのフォルダーに行くため、「cd」コマンドラインと「SequelizeCRUD」を付けて、入力してください。
例: D:\Programming\Testing\> mkdir SequelizeCRUD D:\Programming\Testing\> cd SequelizeCRUD D:\Programming\Testing\SequelizeCRUD\>「SequelizeCRUD」フォルダーはもう作られたら、とうとうプロジェクトを始めるために、ターミナルからこのNPMのコードを入力してください。普通に、各々のコマンドライン実行の中には時間がかかるかもしれないので、しばらくお待ちください。
(ターミナルから作ったフォルダに行く必要です。) npm init -y npm install -g nodemon sequelize-cli npm install -s express mysql2 sequelize body-parserそれでは、何が起こったんでしょうか。上のコードを見られるように、NPMの「init」を使って、プロジェクトを初期化するようになりました。初期化できた後、NPMの「install」でグローバルライブラリパッケージとしてsequelize-cliとnodemonをインストールするようになりました。sequelize-cliの目的は、アプリにORMコマンドを使用させるためです。次のインストールしたライブラリはREST APIアプリにデータベースを統合するものを目的としています。
必要なNPMパッケージはもうインストールされたので、今回は操作のコードを書き始めます!
最初は好んだコードエディタを開けてください。私にはVisual Studio Codeを使っています。エディタからSequelizeCRUDのフォルダーを開けてください。生成した「node_modules、package.json、package-lock.json」ファイル以外に、下のストラクチャーを基に他のフォルダとapp.jsを作ってください。
SequelizeCRUD > config/ > controllers/ > migrations/ > models/ > routes/ > app.js > node_modules/ > package.json > package-lock.json今回は、コードを書く時間です!「app.js」ファイルに行ってください。後で、下のコードを全て入れてください。
const express = require('express'); const bodyParser = require('body-parser'); const http = require('http'); const app = express(); const port = 8000; app.set('port', port); app.use(bodyParser.json()); const server = http.createServer(app); server.listen(port); module.exports = app;このファイルはウェブサービスを作るために、全てを縛るメインファイルです。だから、折々このファイルに戻ります。コードに見られたように、サーバーを設定するためのブラリとして、expressとbody-parserとhttpがインポートされました。そして、PORTとしては8000を使って、サーバーがランチされます。
次に、configというフォルダに行って、config.jsonを名前としてファイルを作ってください。また、提供したコードを入ってください。
{ "development": { "username": "root", "password": "mysql", "database": "sequelize", "host": "192.168.0.112", "dialect": "mysql" } }このファイルはデータベースの接続を設定するものを目的としています。あなたの好んだSQLに応じて、dialectというプロパティの価値になります。SQLデータベースに対応する必要なプロパティを変更してください。
さて、データベースのモデルを作り始める前に、モデルのフォルダにindex.jsを作るべきです。次の見せるコードをいれてください。
'use strict'; const fs = require('fs'); const path = require('path'); const Sequelize = require('sequelize'); const basename = path.basename(__filename); const env = process.env.NODE_ENV || 'development'; const config = require(__dirname + '/../config/config.json')[env]; const db = {}; let sequelize; if (config.use_env_variable) { sequelize = new Sequelize(process.env[config.use_env_variable], config); } else { sequelize = new Sequelize(config.database, config.username, config.password, config); } fs .readdirSync(__dirname) .filter(file => { return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js'); }) .forEach(file => { const model = sequelize['import'](path.join(__dirname, file)); db[model.name] = model; }); Object.keys(db).forEach(modelName => { if (db[modelName].associate) { db[modelName].associate(db); } }); db.sequelize = sequelize; db.Sequelize = Sequelize; module.exports = db;なぜ、こういうコードが必要なのか?理由は全てのモデルファイルを一つずつインポートするというより、むしろこのファイルがインポートの手順を自動化します。それに加えて、Sequelize ORMの接続をインスタンスするために、このところにconfig.jsonファイルから、先の設定した環境を使います。このため、SequelizeでデータベースのQueryを簡単に使えるようになります。
後は、これからモデルの作成に進めます。EmployeeとCompanyのモデルを作ります。この2つの間にそれぞれのEmployeeが1つのCompanyで働いてて、逆でそれぞれのCompanyがたくさんEmployeeを持てるという関係になります。だから、後に、SequelizeでbelongsToが
One-is-to-Oneを表し、hasManyがOne-is-to-Manyを表すというassocationを使うと、テーブル関係を確立できるようになります。ターミナルから、モデルを作るために、下の部分に書いてあるように、こうやって入力する:
// Employee sequelize model:generate --name Employee --attributes name:string,salary:integer // Company sequelize model:generate --name Company --attributes name:string 「 sequelize-cliというライブラリはもうインストールされたから、こういうことができます。」上のコマンドを正しく入力すれば、4つのファイルが生成されるはずです。このファイルは2つに分けられて、MigrationsとModelsを一部としています。
Migrations: Company & Employee
'use strict'; module.exports = { up: (queryInterface, Sequelize) => { return queryInterface.createTable('Companies', { id: { allowNull: false, autoIncrement: true, primaryKey: true, type: Sequelize.INTEGER }, name: { type: Sequelize.STRING }, createdAt: { allowNull: false, type: Sequelize.DATE }, updatedAt: { allowNull: false, type: Sequelize.DATE } }); }, down: (queryInterface, Sequelize) => { return queryInterface.dropTable('Companies'); } };CompanyのMigrationファイルが見られるように、idとcreatedAtとupdatedAtと共に、nameフィールドがすでに設定されています。
一方でEmployeeというMigrationにはnameとsalaryの両方フィールドがあります。しかし、EmployeeはCompany次第なので、手動で、FKフィールドはcompanyIdを名前として、追加するべきです。参照として、すでに下のコードでどのように書かれているかを見るはずです。
'use strict'; module.exports = { up: (queryInterface, Sequelize) => { return queryInterface.createTable('Employees', { id: { allowNull: false, autoIncrement: true, primaryKey: true, type: Sequelize.INTEGER }, name: { type: Sequelize.STRING }, salary: { type: Sequelize.INTEGER }, companyId: { type: Sequelize.INTEGER, onDelete: 'CASCADE', references: { model: 'Companies', key: 'id', } }, createdAt: { allowNull: false, type: Sequelize.DATE }, updatedAt: { allowNull: false, type: Sequelize.DATE } }); }, down: (queryInterface, Sequelize) => { return queryInterface.dropTable('Employees'); } };今回は、データーベースのAssocationを設定するために、モデルに行きましょう。AssociationというSequelize特徴はQueryを簡単に作る上で、使う必要なのです。なぜなら、こういう特徴を使って、FKでお互いに接続されているテーブルにQueryを直接に作れます。
Models: Company & Employee
Companyというモデルは
One-is-to-ManyのCardinalityを持っているので、次に示すコードのようにHasManyというAssociationが使用されます。'use strict'; module.exports = (sequelize, DataTypes) => { const Company = sequelize.define('Company', { name: DataTypes.STRING }, {}); Company.associate = function(models) { // associations can be defined here Company.hasMany(models.Employee, { foreignKey: 'companyId', as: 'employees', }) }; return Company; };次に、Employeeの場合に、One-is-to-ManyのCardinalityを持ってて、Company次第なので、BelongsToというAssocationを使います。
'use strict'; module.exports = (sequelize, DataTypes) => { const Employee = sequelize.define('Employee', { name: DataTypes.STRING, salary: DataTypes.INTEGER }, {}); Employee.associate = function(models) { // associations can be defined here Employee.belongsTo(models.Company, { foreignKey: 'companyId', onDelete: 'CASCADE', }) }; return Employee; };Model Synchronization
今、Associationをちゃんと作動してもらうものを目的として、モデルのメインファイルにシンクロナイズします。そうするために、app.jsに戻って、下の書いてあるコードと同じように、そういうコードをいれてください。Employeeモデルのウェブメソッドだけを作るので、Companyのデーターを直接にイニシャライズします。
このコードに表示しているように、モデル変数としてindex.jsをインポートしました。そういう変数を使ってCompanyのモデルを使えます。
const express = require('express'); const bodyParser = require('body-parser'); const http = require('http'); const models = require('./models/index.js'); const Company = models.Company; const app = express(); const port = 8000; app.set('port', port); app.use(bodyParser.json()); models.sequelize.sync().then(() => { initial(); console.log('Seems like the backend is running fine...'); }).catch((err) => { console.log(err, 'Something went wrong with the operation'); }); require('./routes/employee.route')(app); function initial(){ Company.create({ name: "ABC Company" }); Company.create({ name: "DEF Company" }); Company.create({ name: "GHI Company" }); } const server = http.createServer(app); server.listen(port); module.exports = app;この後は、ターミナルに、設定したモデルをテーブルにするために、このようなコードを入力してください。
sequelize db:migrateController: Employee
const db = require('../models/index.js'); const Employee = db.Employee; const Company = db.Company; module.exports = { create(req, res) { Employee.create({ name: req.body.name, salary: req.body.salary, companyId: req.body.companyId, }).then(employee => { res.send(employee); }); }, findAll(req, res) { Employee.findAll({ include: [{ model: Company }] }).then(companies => { res.send(companies); }); }, findOne(req, res) { Employee.findOne({ where: { id: req.params.id }, include: [{ model: Company }] }) .then(company => { res.send(company); }) }, update(req, res) { Employee.update( { name: req.body.name, salary: req.body.salary }, { where: { id: req.params.id } } ).then(() => { res.status(200).send("Successfully updated a Employee ID: " + req.params.id); }); }, delete(req, res) { Employee.destroy({ where: { id: req.params.id } }).then(() => { res.status(200).send("Successfully deleted a Employee ID: " + req.params.id); }); } }上に見せられるように、データベースにEmployeeとCompanyのテーブルが生成されたので、Controllerを作る時間になりました。Controllerフォルダに行って、employee.controller.jsを名前として、新しいファイルを作ります。このところにウェブメソッドを作っていきます。Sequelize接続をアクセスするために、モデルフォルダのindex.jsファイルをインポートして、EmployeeとCompanyのモデルを変数に入ります。
SequelizeはORMなので、手動でQueryを作らなくていいです。
create、findAll、findOne、update、destroyのような定義済み関数を使うだけです。また、一目瞭然の関数名前なので、使い方はすぐ分かります。SequelizeのQueryがpromise-basedの関数だから、callbackとerrorは管理しやすいです。このコードのハイライトはその
findOneとfindAllというメソッドです。なぜなら、SequelizeのAssociationで、EmployeeのデーターをQueryでもらうと、includeのタッグを使ってCompanyのデーターも直接にもらえます。とても便利ですよね!Route: Employee
自分でこのアプリをテストする前に、Routeフォルダに行って、最後の新しいファイルを作って、employee.route.jsを名前としていくべきです。
module.exports = function(app) { const employeesController = require('../controllers/employee.controller'); const employeesAPI = '/api/employees' // Create a new Employee app.post(employeesAPI, employeesController.create); // Retrieve all Employees app.get(employeesAPI, employeesController.findAll); // Retrieve a Employee by ID app.get(employeesAPI + '/:id', employeesController.findOne); // Update a Employee by ID app.patch(employeesAPI + '/:id', employeesController.update); // Delete a Employee by ID app.delete(employeesAPI + '/:id', employeesController.delete); };コードに見られるように、先の作ったcontrollerをインポートして、controllerのエンドポイントを変数に保存しました。そして、それぞれの作ったメソッドにrouteがセットされました。
最終的に、app.jsファイルに戻って、その作ったrouteをインポートして、とうとうテストがもうできます。そうするために、ターミナルに行って、次に見せられるように、このコマンドように書きます:
nodemon app.jsもう今、PostmanのHTTP Clientで、テストできますよ!
終わり
全部の手順を正確にすれば、作動しているExpressとSequelizeを統合されたNode.js REST APIのアプリをもう書くようにできました!やったね!日本語勉強中なので、この記事は内容が分かりにくいかもしれなくても、読んで頑張ってありがとうございました!
じゃあまたね!
