express.js(node.js)の経験が浅い人はどのようにデータベースに接続して、データを取得/追加/更新/削除を行うのか知りたい人も多いかと思います。そんな人を対象に本文書では、簡単な例を通してexpress.jsからMySQLを操作する下記のSQL文の説明を行なっています。本書を読み終えるとCRUD(Create, Read, Update, Delete)操作を理解することができます。

  • create database
  • creata table
  • select
  • insert
  • delete
  • update

動作確認は、MAC環境で行なっており、node.jsを事前にインストールしておく必要があります。

MySQLのインストール

Homebrewを利用してMySQLのインストールを行います。MAC環境へのMySQLへのインストール方法については、下記の文書の前半を参考に行なってください。

Express.jsのインストール

Express.jsをインストールするディレクトリを任意の名前で作成します。ここではexpress_mysqlとしています。


$ mkdir express_mysql
$ cd express_mysql/

Express.jsで使用するパッケージ管理を行うためnpm initコマンドでpackage.jsonファイルの作成を行います。-yオプションをついて対話モードをスキップします。


$ npm init -y

npmを実行したディレクトリの中にpackage.jsonファイルが作成されます。

次にexpress.jsのインストールを行います。


$ npm install express

Express.jsの動作確認

Hello Worldの表示

インストールが完了したら、Express.jsの動作確認を行うためにアクセスしたブラウザにHello Worldを表示させます。

express_mysqlディレクトリの中にapp.jsファイルを作成して、下記を記述してください。


const express = require('express')
const app = express()
const port = 3000

app.get('/', (req, res) => res.send('Hello World!'))

app.listen(port, () => console.log(`Example app listening on port ${port}!`))

nodeコマンドを使って、作成したapp.jsファイルを実行します。


 $ node app.js
Example app listening on port 3000!

ブラウザを起動し、localhostのポート3000にアクセスして下記の画面が表示されたらExpress.jsは正常に動作しています。

Hello Worldがブラウザに表示
Hello Worldがブラウザに表示

nodemonのインストール

node app.jsコマンドを実行して、express.jsの動作確認を行いましたが、nodeコマンドではファイルの変更を行う度にコマンドを再実行する必要があります。ファイルの更新を監視して自動でnodeコマンドを実行してくれるnodemonをインストールします。


$ npm install nodemon

インストール完了後、npxコマンドでnodemonを実行してください。今後はapp.jsファイルの更新を行うと自動でnode app.jsが再実行されます。


$ npx nodemon app.js
[nodemon] 1.19.2
[nodemon] to restart at any time, enter `rs`
[nodemon] watching dir(s): *.*
[nodemon] starting `node app.js`
Example app listening on port 3000!

MySQLとの動作確認

MySQL接続ドライバのインストール

express.jsからMySQLへ接続するためには、MySQLへ接続するためのドライバをインストールする必要があります。


 $ npm install mysql
ここでインストールするのはmysqlのドライバでmysql本体ではありません。

express.jsからMySQLへの接続

app.jsからMySQLへの接続確認を行います。app.jsファイルのport行の下にmysqlへの接続に関するコードを追加します。MySQLに接続するためには、host, user, passwordの情報が必要になります。passwordが”(ブランク)になっているのはパスワードが設定されていないためです。インストールしたMySQLのパスワードを設定している場合はここにパスワードを入れてください。


const port = 3000

const mysql = require('mysql');

const con = mysql.createConnection({
  host: 'localhost',
  user: 'root',
  password: ''
});

con.connect(function(err) {
  if (err) throw err;
  console.log('Connected');
});

追加後、ファイルを保存し、接続が正常に行われるとnodemonコマンドを実行した端末上にConnected!のログが表示されます。


[nodemon] restarting due to changes...
[nodemon] starting `node app.js`
Example app listening on port 3000!
Connected

[commen]console.logコマンドの内容をnodemonコマンドを実行した端末に表示されます。[/comment]

MySQLデータベースの作成と接続

MySQLにデータベースの作成を行なっていないので、接続後にデータベースの作成を行います。データベースの作成はCREATE DATABASEコマンドで行います。Express.jsではSQL文を実行するためにqueryメソッドを利用します。


con.connect(function(err) {
  if (err) throw err;
  console.log('Connected');
  con.query('CREATE DATABASE express_db', function (err, result) {
  if (err) throw err; 
    console.log('database created');
  });
});

実行するとdatabase Createdというログが表示されます。データベースは1つしか作成できないので作成後はデータベース作成のコードは削除してください。

nodemonを実行しているのでdatabase createdが表示されたあとにファイルの更新を行うと再度CREATE DATABASEが実行され、Error: ER_DB_CREATE_EXISTS: Can’t create database ‘express_db’; database exists(データベースが存在している)エラーが発生します。

データベースの作成が完了したので接続オプションに作成したデータベース情報を追加して、createConnectionメソッドでデータベースへの接続まで行います。


const con = mysql.createConnection({
  host: 'localhost',
  user: 'root',
  password: '',
  database: 'express_db' //追加
});

テーブルの作成

データベースへの接続が完了したのでテーブルの作成を行います。テーブルの作成はCREATE TABLE文で行います。

作成するテーブルの名前はusersでid, name, emailの3つの列を持ち、idはオートインクリメントで自動で1ずつ増える整数でプライマリキーに設定しています。残りの2つのname, emailはVARCHARでNOT NULLを設定します。

SQL文だけ取り出すと以下の通りです。


CREATE TABLE users (id INT NOT NULL PRIMARY KEY AUTO_INCREMENT, name VARCHAR(255) NOT NULL, email VARCHAR(255) NOT NULL)

CREATE TABLE文を追加したコードは以下の通りです。


con.connect(function(err) {
	if (err) throw err;
	console.log('Connected');
	const sql = 'CREATE TABLE users (id INT NOT NULL PRIMARY KEY AUTO_INCREMENT, name VARCHAR(255) NOT NULL, email VARCHAR(255) NOT NULL)';
	con.query(sql, function (err, result) {  
	if (err) throw err;  
	console.log('table created');  
	});
});

変更を保存するとnodemonにより実行され、テーブルが作成されます。

何か書き換えて保存するとnodemonにより処理が再実行されるので、CREATE TABLE IF NOT EXISTS usersに書き換えるとusersテーブルが存在する場合は再度usersテーブルを作成する処理は行われません。

作成したテーブルはデータベース管理GUIのTablePlus等を使って確認することができます。

確認するツールはTablePlusでもsequelProでもなんでも構いません。mysqlコマンドがわかる人はコマンドでも構いません。

TablePlusで確認すると作成したusersテーブルを確認することができます。

TablePlusでusersテーブル確認
TablePlusでusersテーブル確認

select文の動作確認

select文によりデータの取得

データベースからselect文によりデータ取得を行うため、TAblePlusを利用して、直接データを挿入します。

TablePlusを使ってデータ挿入
TablePlusを使ってデータ挿入

登録後、usersテーブルの1件しかありませんがselect文を使って全データを取得するselect * from usersを実行します。


con.connect(function(err) {
	if (err) throw err;
	console.log('Connected');
	const sql = "select * from users"
	con.query(sql, function (err, result, fields) {  
	if (err) throw err;  
	console.log(result)
	});
});

実行後、先ほど登録したusersのデータが下記のように取得できます。


[ RowDataPacket { id: 1, name: 'John', email: 'John@example.com' } ]

emailだけ取り出したい場合は、下記のように記述することで取得したデータからemailのみ取り出すことができます。


console.log(result[0].email)

テーブル取得データをブラウザで表示

次はselect文を使用して取得したデータをアクセスしたブラウザに返します。


const con = mysql.createConnection({
  host: 'localhost',
  user: 'root',
  password: '',
  database: 'express_db'
});

con.connect(function(err) {
	if (err) throw err;
	console.log('Connected');
});

app.get('/', (request, response) => {
	const sql = "select * from users"
	con.query(sql, function (err, result, fields) {  
	if (err) throw err;
	response.send(result)
	});
});

ブラウザからlocalhost:3000にアクセスするとテーブルのデータ表示されます。

テーブルのデータがブラウザに表示
テーブルのデータがブラウザに表示
これまでcon.connectでSQLの前にデータベースへの接続を行なっていましたが、SQL発行時にデータベースへの接続が行われるので、con.connectの箇所を削除しても上記のコードは動作します。

insert文の動作確認

insert文によるデータの挿入

TablePlusを使って先ほどは1件ユーザデータを登録しましたが、今回はinsert文を利用してデータの挿入を行います。

実行するINSERT文は下記の通りです。


INSERT INTO users(name,email) VALUES('kevin','kevin@test.com')

実行するコードは下記の通りです。


const sql = "INSERT INTO users(name,email) VALUES('kevin','kevin@test.com')"

con.query(sql,function(err, result, fields){
	if (err) throw err;
	console.log(result)
})

console.logでresultに入っている情報を確認するとaffectedRow(追加した行)などがOKPacketオブジェクトに入って戻されることがわかります。


OkPacket {
  fieldCount: 0,
  affectedRows: 1,
  insertId: 2,
  serverStatus: 2,
  warningCount: 0,
  message: '',
  protocol41: true,
  changedRows: 0
}

TablePlusで確認するとinsertしたデータが追加していることを確認できます。

TablePlusでinsertを確認

またブラウザでlocalhost:3000にアクセスすると取得できるデータが2件になっていることも確認できます。

追加されたデータ

insert文で直接Valuesの中に値を入れていましたが、?を使うこともできます。


const sql = "INSERT INTO users(name,email) VALUES(?,?)"

con.query(sql,['Jack','jack@exsample.co.jp'],function(err, result, fields){
	if (err) throw err;
	console.log(result)
})

下記のようにSET ?を使うこともできます。


const sql = "INSERT INTO users SET ?"

con.query(sql,{name:'Tommy',email:'tommy@test.co.jp'},function(err, result, fields){
	if (err) throw err;
	console.log(result)
});

どちらも同じようにテーブルへのデータの追加を行うことができます。

入力フォームの作成

ブラウザから入力したデータをデータベースのテーブルに追加(insert)する方法を確認します。ブラウザ側で入力が行えるようにするためには、フォームを含んだHTMLをブラウザに渡す必要があります。

フォーム用のhtmlファイルを保存するhtmlディレクトリを作成し、その中にform.htmlファイルを作成します。中身はnameとemailのinput要素を2つ含んだシンプルな入力フォームです。CSS等のスタイル設定は全く行なっていません。


<!DOCTYPE html>
<html lang="ja">
<head>
	<meta charset="UTF-8">
	<title>入力フォーム</title>
</head>
<body>
<h1>入力フォーム</h1>
<form action="/" method="POST">
    name <input type="text" name="name"><br>
    email <input type="text" name="email"><br>
    <button type="submit">送信</button>
</form>
</body>
</html>

入力フォームが表示されるようにapp.jsの”/(ルート)”のルーティングを変更します。sendFileメソッドを使用して、html/form.htmlファイルを”/(ルート)”にアクセスしてきたブラウザに渡しています。


app.get('/', (req, res) => 
	res.sendFile(path.join(__dirname, 'html/form.html')))

ブラウザで確認するとnameとemailの入力欄があるフォームが表示されます。

express.js入力フォーム
express.js入力フォーム

body-parserのインストール

ブラウザで入力したnameとemailはPOSTリクエストによってexpress.js側に送信されます。送信されたnameとemailをexpress.jsで処理するためには、body-parserモジュールをインストールする必要があります。


$ npm install body-parser

インストールが完了したら、body-parserを読み込みます。


const path = require('path')
const express = require('express')
const bodyParser = require('body-parser')
const app = express()
const port = 3000

読み込んだbody-parserをミドルウェアとして設定します。


app.use(bodyParser.urlencoded({ extended: true }));

入力フォームからPOSTリクエストで送信されるデータを取得できるか確認を行うため追加のルーティングを設定します。req.bodyの中に送られてきたデータが保存されています。


app.get('/', (req, res) => 
	res.sendFile(path.join(__dirname, 'html/form.html')))

app.post('/', (req, res) => res.send(req.body)) //追加

入力フォームに情報を入力した送信ボタンを押します。

入力フォームに値を入力
入力フォームに値を入力

express.js側ではPOSTリクエストから送られてきた内容を受け取り、res.sendでreq.bodyをブラウザに送り返しているので、ブラウザには以下のようにreq.bodyの中の値が表示されます。中身は入力フォームで入力した

入力フォームで入れた値を表示する
入力フォームで入れた値を表示する

フォームから送られてきたデータをinsert

入力フォームの値を先ほど確認したinsert文を使ってMySQLデータベースのテーブルに追加します。追加が正常に完了するとブラウザの画面には、”登録が完了しました”と表示されます。


app.post('/', (req, res) => {
	const sql = "INSERT INTO users SET ?"

	con.query(sql,req.body,function(err, result, fields){
		if (err) throw err;
		console.log(result);
		res.send('登録が完了しました');

	});
});

実際に入力フォームを入れて、送信ボタンを押すと下記の画面が表示されます。TablePlus等を使ってデータが追加されているか確認を行なってください。

insert文が正常に完了
insert文が正常に完了
res.send(‘登録が完了しました’)ではなくres.redirect(‘/’)に設定すると”/(ルート)”にリダイレクトされ、追加した行が表示されます。

テンプレートエンジンejsの設定

delete文やupdate文を行うためにhtml文の中で変数を使用したいのでHTMLとJavaScriptを一緒に記述することができるテンプレートエンジンejsを利用します。

ejsのインストール

npmコマンドを使用してejsモジュールのインストールを行います。


 $ npm install ejs

ejsの動作確認

ejsを利用して、テーブルから取得したユーザ情報の一覧を表示してみましょう。

ejsの利用するためにはrequire文を追加します。


const path = require('path')
const express = require('express')
const bodyParser = require('body-parser')
const ejs = require('ejs') //追加
const app = express()
const port = 3000

テンプレートエンジンにejsを利用するために下記の設定が必要となります。


app.set('view engine', 'ejs');

下記のように”/(ルート)”へのアクセスが行われるとselect文が実行され、実行結果をejsファイルに渡します。そのためにrenderメソッドを使って、indexという名前のejsファイルを指定しています。

下記ではindexと指定しますが、viewsディレクトリの中のindex.ejsファイルを指定していることになるため、viewsディレクトリを作成し、その中にindex.ejsファイルを保存します。{users : result}でusersという名前の変数にselect文の実行結果resultを渡しています。index.ejsファイルではusers変数を使用してselect文の実行結果を表示することができます。


app.get('/', (req, res) => {
	const sql = "select * from users";
	con.query(sql, function (err, result, fields) {  
	if (err) throw err;
	res.render('index',{users : result});
	});
});

index.ejsファイルは下記のように記述します。


<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>User list</title>
</head>
<body>
    <table>
        <tr>
            <th>名前</th>
            <th>email</th>
            <th>更新</th>
            <th>削除</th>
        </tr>
        <% users.forEach(function (value) { %>
        <tr>
            <td><%= value.name %></td>
            <td><%= value.email %></td>
            <td><a href="/edit/<%= value.id %>">更新</a></td>
            <td><a href="/delete/<%= value.id %>">削除</a></td>
        </tr>
        <% }); %>
    </table>
</body>
</html>
ここではテンプレートエンジンejsの構文について説明を行いません。users.forEachを使って、usersに保存されているユーザ情報をloopして取り出しています。

ブラウザから”/(ルート)”にアクセスするとこれまでにテーブルに追加したユーザ情報の一覧が下記のように表示されます。

ユーザ一覧
ユーザ一覧

delete文の動作確認

delete文を使って、ユーザ一覧にあるユーザ情報を削除する方法を確認します。

delete文によりデータ削除

実行するDELETE文は下記の通りです。?にはユーザデータのidを指定します。


const sql = "DELETE FROM users WHERE id = ?";

実行するコードは下記の通りです。ルーティングに/delete/:idを追加します。:idに入ってくる値は、ブラウザから送られてくるreq.params.idで取得することができます。WHERE句でidの値にreq.params.idを設定し、そのidを持つ行を削除する処理を行なっています。


app.get('/delete/:id',(req,res)=>{
	const sql = "DELETE FROM users WHERE id = ?";
	con.query(sql,[req.params.id],function(err,result,fields){
		if (err) throw err;
		console.log(result)
		res.redirect('/');
	})
});

ユーザ一覧に表示されるユーザ情報を削除するためには、各行の右側に表示されている削除リンクをクリックします。

ユーザ一覧
ユーザ一覧

削除リンクをクリックするとどのような処理が行われているか確認するためにユーザ一覧を表示させているindex.ejsの削除部分のコードを確認します。/delete/の後には、value.idでその行のidを取り出して設定しています。


<td><a href="/delete/<%= value.id %>">削除</a></td>

削除リンクには、/delete/id(行のidが設定)にリンクが貼られています。

上から6番目のTedの行にある削除リンクをクリックすると削除が行われ、”/(ルート)”にリダイレクトされ、Ted行が削除されたユーザ一覧が表示されます。

1行削除後のユーザ一覧
1行削除後のユーザ一覧

update文による動作確認

update文を使って、ユーザ一覧にあるユーザ情報を更新する方法を確認します。

updateによる更新

実行するUPDATE文は下記の通りです。SETの?にはブラウザから送られているPOSTリクエストの内容を設定します。


const sql = "UPDATE users SET ? WHERE id = " + req.params.id;

実行するコードは下記の通りです。ルーティングに/update/:idを追加しています。deleteと同様に:idに入る値は、req.params.idで取得することができます。WHERE句でidの値にreq.params.idを設定しそのidを持つ行を更新する処理を行なっています。更新内容はinsert文で使用したのと同じようにreq.bodyを使っています。


app.post('/update/:id',(req,res)=>{
	const sql = "UPDATE users SET ? WHERE id = " + req.params.id;
	con.query(sql,req.body,function (err, result, fields) {  
		if (err) throw err;
		console.log(result);
		res.redirect('/');
		});
});

更新するためのルーティングは追加できましたが、現在の値を更新するためには新しい値を入力するための更新フォームが必要となります。

更新フォームの作成

データを更新したい場合は、更新処理のためのルーティングを追加するだけではなく更新のデータを入力するフォームが必要となります。そのため更新画面を表示させるルーティングを追加します。

更新画面へのルーティング/edit/:idを追加します。select文とidを利用して更新したい行の現在の値を取得します。ブラウザに表示させる画面は取得した値を渡すためことができるejsファイルを使って作成します。ejsファイルには変数userを使って取得した値を渡します。


app.get('/edit/:id',(req,res)=>{
	const sql = "SELECT * FROM users WHERE id = ?";
	con.query(sql,[req.params.id],function (err, result, fields) {  
		if (err) throw err;
		res.render('edit',{user : result});
		});
});

viewsディレクトリの中にedit.ejsファイルを作成します。edit.ejsではselect文で取得したデータを持つ変数userの値を利用します。input要素のvalueにname, emailの現在の値であるuser[0].name, user[0].emailをそれぞれ設定します。POSTリクエストは先ほど追加した更新処理を行うルーティング先である/update/:idに行われます。


<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>User Edit</title>
</head>
<body>
    <h1>更新フォーム</h1>
    <form action="/update/<%= user[0].id %>" method="POST">
        name <input type="text" name="name" value="<%= user[0].name %>"><br>
        email <input type="text" name="email" value="<%= user[0].email %>"><br>
        <button type="submit">更新</button>
    </form>    
</body>
</html>

更新画面へのURLはindex.ejsの更新のaタグのリンクに設定されています。


<td><a href="/edit/<%= value.id %>">更新</a></td>

下記のユーザ一覧の中の名前Jimmyの右側にある更新リンクをクリックします。

1行削除後のユーザ一覧
更新前のユーザ一覧

更新フォーム画面が表示され、入力フォームの中には現在の値が設定されています。

現在の値が設定された入力フォーム
現在の値が設定された入力フォーム

nameをJimmyからJohnsonに変更します。入力が完了したら、更新ボタンをクリックします。

nameに変更内容を入力
nameに変更内容を入力

先ほどまでJimmyだった行の名前がJohnsonに変更されていることがわかります。更新は完了です。

更新されたユーザ一覧
更新されたユーザ一覧

更新処理は完了です。

まとめ

express.jsからMySQLデータベースへの接続だけではなく、データベースの作成、テーブルの作成、データの追加/更新/削除など基本操作を理解することができたと思うので、次は実践的なアプリケーションの構築にチャレンジしてください。

今回作成したapp.jsファイルは下記の通りです。


const path = require('path')
const express = require('express')
const bodyParser = require('body-parser')
const ejs = require('ejs')
const app = express()
const port = 3000

app.use(bodyParser.urlencoded({ extended: true }));
app.set('view engine', 'ejs');

const mysql = require('mysql');

const con = mysql.createConnection({
  host: 'localhost',
  user: 'root',
  password: '',
  database: 'express_db' 
});

// con.connect(function(err) {
// 	if (err) throw err;
// 	console.log('Connected');
// });

app.get('/', (req, res) => {
	const sql = "select * from users";
	con.query(sql, function (err, result, fields) {  
	if (err) throw err;
	res.render('index',{users : result});
	});
});

app.post('/', (req, res) => {
	const sql = "INSERT INTO users SET ?"
	con.query(sql,req.body,function(err, result, fields){
		if (err) throw err;
		console.log(result);
		res.redirect('/');
	});
});

app.get('/create', (req, res) => 
	res.sendFile(path.join(__dirname, 'html/form.html')))

app.get('/edit/:id',(req,res)=>{
	const sql = "SELECT * FROM users WHERE id = ?";
	con.query(sql,[req.params.id],function (err, result, fields) {  
		if (err) throw err;
		res.render('edit',{user : result});
		});
});

app.post('/update/:id',(req,res)=>{
	const sql = "UPDATE users SET ? WHERE id = " + req.params.id;
	con.query(sql,req.body,function (err, result, fields) {  
		if (err) throw err;
		console.log(result);
		res.redirect('/');
		});
});

app.get('/delete/:id',(req,res)=>{
	const sql = "DELETE FROM users WHERE id = ?";
	con.query(sql,[req.params.id],function(err,result,fields){
		if (err) throw err;
		console.log(result)
		res.redirect('/');
	})
});


app.listen(port, () => console.log(`Example app listening on port ${port}!`));

入力フォームを表示されるルーティングを”/(ルート)”から/createに変更しています。

ユーザ情報一覧のindex.ejsは下記となります。


<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>User list</title>
</head>
<body>
    <p><a href="/create">ユーザ追加</a></p>
    <table>
        <tr>
            <th>名前</th>
            <th>email</th>
            <th>更新</th>
            <th>削除</th>
        </tr>
        <% users.forEach(function (value) { %>
        <tr>
            <td><%= value.name %></td>
            <td><%= value.email %></td>
            <td><a href="/edit/<%= value.id %>">更新</a></td>
            <td><a href="/delete/<%= value.id %>">削除</a></td>
        </tr>
        <% }); %>
    </table>
</body>
</html>

index.ejsは上部に入力フォームへのリンクを追加しています。

入力フォームのform.htmlは下記の通りです。


<!DOCTYPE html>
<html lang="ja">
<head>
	<meta charset="UTF-8">
	<title>入力フォーム</title>
</head>
<body>
<h1>入力フォーム</h1>
<form action="/" method="POST">
    name <input type="text" name="name"><br>
    email <input type="text" name="email"><br>
    <button type="submit">送信</button>
</form>
</body>
</html>

更新フォームのedit.ejsファイルは下記の通りです。


<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>User Edit</title>
</head>
<body>
    <h1>更新フォーム</h1>
    <form action="/update/<%= user[0].id %>" method="POST">
        name <input type="text" name="name" value="<%= user[0].name %>"><br>
        email <input type="text" name="email" value="<%= user[0].email %>"><br>
        <button type="submit">更新</button>
    </form>    
</body>
</html>