Lesson 14
APIの実装
Lesson 14
Chapter 1
事前準備
このレッスンでは、TODOアプリの実装を行なっていきます。 このレッスンを完了することでTODOアプリのAPIサーバが完成します。
nodeを実行する環境を作るために任意のディレクトリでnpm init
します。
いくつか質問をされますが、デフォルトの選択肢で問題ありません。
npm
npmとは、Node.jsのパッケージマネージャーです。 パッケージとは、再利用可能なコードやライブラリ、フレームワークなどのことを指します。 npmを使用すると、Node.jsプロジェクトで必要なパッケージを簡単にインストールしたり、 アップグレードしたり、削除したりできます。 npmには、世界中の開発者が公開している数百万ものパッケージがあります。
npm init
npm init
は、Node.jsプロジェクトを始めるために必要な設定をするコマンドです。
npm init
コマンドを使うと、プロジェクトの基本情報(プロジェクト名、バージョン、説明など)
を設定することができます。
npm init
コマンドを実行すると、プロジェクトの基本情報を設定するためにいくつかの質問が表示されます。
そして、いくつかの質問に答えていくことで、package.jsonというファイルが生成されます。このファイルには、プロジェクトの情報や依存関係の情報が記述されています。このファイルがあることで、npmコマンドを使ってライブラリをインストールしたり、プロジェクトを公開したりすることができます。
今回必要なnpmモジュールは下記の通りです。
モジュール名 | 機能 | 説明 |
---|---|---|
express | Webサーバフレームワーク | Webサーバとして機能します。APIサーバとしての機能を持たせます |
mysql | データベースを操作する | MySQLデータベースのデータを取得したり更新したりするのに使います |
ejs | テンプレートエンジン | データとHTMLの雛形を組み合わせて出力します |
uuid | uuidを出力する | uuidを出力するのに使用します |
テンプレートエンジンとは
テンプレートエンジンとは、動的なWebページを生成する際にHTMLの中に変数や制御構文を埋め込み、 その部分をプログラムで生成した値や処理結果に置き換える機能を提供する仕組みです。
UUID
UUID (Universally Unique Identifier) は一意の識別子を生成するための仕様であり、ランダムに生成される128ビットの数値です。 UUIDはネットワーク上のオブジェクトの一意の識別子やデータベースのプライマリーキーとしても使用されます。今回はタスクを識別するために使っていきます。
下記のコマンドを実行しexpressをインストールします
ターミナル
$ npm install express mysql ejs uuid
また、開発を容易に進めていくために下記のnpmモジュールをインストールします。
モジュール名 | 機能 | 説明 |
---|---|---|
nodemon | ファイルに変更があった際自動的に再起動してくれる | このモジュールを使用してnodeを実行するとファイルに変更があると自動的に再起動されます |
semistandard | コードに一定のルールを持たせ品質を保つ | コードを書いている途中時々実行することでルールに基づいた品質の高いコードを書くことができます |
これらは、
ターミナル
$ npm install -D nodemon semistandard
でインストールできます。
-D オプションについて
nodemonとsemistandardは開発には必要ですが、 実際にサーバとして動かすプロダクション環境では不要なモジュールです。 これらをdevDependenciesモジュールとして区別して管理することで 本番環境の負荷を下げることができます。
scriptsについて
package.jsonのscriptsは、 プロジェクトで使用できるスクリプトの一覧を定義するために使用されます。 npm runコマンドで呼び出すことができるキーと値のペアが含まれます。 今回はnodemonとsemistandardを簡単に起動できるようにするためのものを追加します。 initした際に自動的に追加されたものは削除して構いません。
package.json
"scripts": {
"watch": "nodemon ./index.js",
"lint": "semistandard --fix"
},
$ npm run watch
でnodemonを起動できます。
$ npm run lint
でsemistandardを起動できます。
ES6対応について
ECMAScript2015(ES6)で導入されたECMAScriptModulesを利用するために package.jsonにtypeを追加します。
これによりモジュールの定義やimport/exportの記述ができ、 また動的読み込みが可能になること、 ビルドが高速化する、スコープ汚染の防止など様々なメリットがあります。
package.json
"type": "module",
ここまでで、package.jsonは下記のようになったかと思います。
type: module
、scripts、dependencies、devDependenciesが合っていれば問題ありません。
また各モジュールの数字の部分は違っていても問題ありません。(バージョンを表す数字です。)
package.json
{
"name": "express_todo_sample",
"version": "1.0.0",
"description": "",
"main": "main.js",
"type": "module",
"scripts": {
"watch": "nodemon ./main.js",
"lint": "semistandard --fix"
},
"author": "",
"license": "ISC",
"dependencies": {
"ejs": "^3.1.9",
"express": "^4.18.2",
"mysql": "^2.18.1",
"uuid": "^9.0.0"
},
"devDependencies": {
"nodemon": "^2.0.22",
"semistandard": "^16.0.1"
}
}
mainファイルの設定
作業ディレクトリにmain.jsを作成します。
このファイルにexpressのプログラムを書いていきます。
下記のように記述してください。
main.js
'use strict';
import express from 'express';
// 定数定義
const PORT = 3000;
const TOP_LINK = '<a href="/">トップへ</a>';
// express 初期化
const app = express();
// ミドルウェアの設定
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// 静的なファイルを提供
app.use(express.static('static'));
// サーバをスタート
app.listen(PORT, () => {
console.log('Server started on port 3000');
});
順番に見ていきます
厳格モード(use strict)について
main.js
'use strict';
これは厳格モードを有効にするものです。.jsファイルの先頭に記述すると有効になります。
これによりJavaScriptの一部の古い構文や慣用句が禁止されます。 一部の非推奨になった構文や慣用句は意図しないバグを引き起こす可能性があるため、これらをエラーとして扱いバグを防止します。
また、変数宣言が強制されたり、意図しないグローバル変数の生成を防止したりするなど、 より安全なコードを書くための様々なチェックが行われます。
下記のようなルールが適用されます。
- var, let, constなどを用いない変数宣言が禁止する
- 重複するプロパティ名を持つオブジェクトの定義を禁止する
- 関数内でthisがundefinedになる
- with文の使用を禁止する
- いくつかの識別子が予約語になる
インポートと初期化
main.js
import express from 'express';
const app = express();
expressをインポートし初期化しています。
node_modulesの中からexpressを探してきてインポートしてくれます。
そしてapp定数に初期化します。express()
は、expressのインスタンスを作成します。
インスタンス
インスタンスは、あるクラスやコンストラクター関数を元に生成されたオブジェクトのことを指します。 JavaScriptでは、クラスとコンストラクター関数の両方を使ってインスタンスを作成することができます。
このインスタンスには様々なメソッド、プロパティなどが含まれており、 app定数に代入することでインスタンスを使用することができます。
定数定義
main.js
const PORT = 3000;
const TOP_LINK = '<a href=/>トップへ</a>';
ポート番号はlisten関数に直接書いても良いのですが、 より複雑なアプリケーションを作る時に別の関数から定数として必要になる場合があります。
様々な場所に3000という文字列があると編集が大変になってしまうため、 定数として1箇所で保管しておきましょう。
またTOP_LINKは後続の解説で使用する定数です。 レスポンスを返す時にトップページに飛べるリンクを送ってあげると親切です。
変数名等の命名について
定数や変数、クラス名などは半角英数字であればなんでも使えます。何なら日本語を変数名として使える環境もあります。 しかし、ある程度命名規則を持っていないと読み難く、理解しにくいコードになってしまいます。
代表的な命名規則を列挙しますのでぜひ今後の開発の時に使ってみてください。
・アッパーキャメルケース
最初の単語を含め、各単語の先頭を大文字にしたものです。JSでは主にクラス名で採用されます。 例:FirstName、FullName
・キャメルケース
最初の単語を小文字で、各単語の先頭を大文字にしたものです。JSでは関数名、変数名に用いられることが多いです。 例:firstName、fullName
・アッパーケース
すべての文字を大文字にして単語と単語の間にアンダースコア(_)を挿入したものです。JSでは定数によく用いられます。 例:FIRST_NAME、FULL_NAME、PORT
・スネークケース
すべての文字を小文字にして単語と単語の間にアンダースコア(_)を挿入したものです。 JavaScriptではあまり用いられませんが、Pythonの変数やHTMLのクラス名などに用いられます。 例:first_name、full_name
ミドルウェアの設定
main.js
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
expressに標準搭載されているミドルウェアの設定です。
express.json()
はJSON形式のリクエストボディをパースするために使用されます。
これにより、リクエストのbodyがJavaScriptのオブジェクトに変換され、
Expressアプリケーション内で扱えるようになります。
これを書かない場合少し手間をかけでデータをパースする必要が出てきます。
express.urlencoded({ extended: true })
はエンコードされたフォームデータをパースするために使用されます。
この場合、リクエストのbodyはキーと値のペアのオブジェクトに変換され、
Expressアプリケーション内で扱えるようになります。
これを書かない場合少し手間をかけでデータをパースする必要が出てきます。
パースとは
パースとは文字列やデータなどの解析を行い構造化することを指します。 文字列を解析してそれがどのような構造を持っているのかを判断し、 プログラムで扱いやすい形式に変換することができます。 例えば、JSON形式の文字列をパースすることで、 JavaScriptのオブジェクトとして利用できるようになります。 また、HTMLやXMLなどの文書に対しても、パース処理を行うことができます。
main.js
app.use(express.static('static'));
これも標準搭載ミドルウェアの設定です。
expres.static('ディレクトリ名')
は指定したディレクトリに入っているファイルを提供します。
この例ではstatic
ディレクトリに入っているhtmlや画像、動画データなどを提供することができます。
サーバの起動
main.js
app.listen(PORT, () => {
console.log(`Server started on port ${PORT}`);
});
Webサーバとして起動します。ポート番号は先ほど定数に代入した値を使っています。
これより上にある、 つまり先に評価されたapp.use()やapp.get()などが適応されたWebサーバとして動作を開始します。 必ずファイルの最後に記述しましょう。
index.htmlの作成
作業ディレクトリ内にstatic
というディレクトリを作り、その中にindex.html
というファイルを作成してください。
indexというのはWebサイトやWebアプリのルートのページとしてよく用いられる名前です。
http://localhost:3000/
へのアクセスは
http://localhost:3000/index.htmlへのアクセスと解釈されます。
もちろん http://localhost:3000/index.html
とアクセスしても機能します。
index.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<h3>メインメニュー</h3>
<p><a href="/tasks">タスク一覧</a></p>
<p><a href="/create.html">タスク作成</a></p>
<p><a href="/logs">ログ一覧</a></p>
</body>
</html>
VisualStudioCodeを利用している場合、拡張子が.htmlの空のファイルで
html:5
と入力すると雛形を作成できます。
上記はその雛形から言語を日本語に修正し、見出し(h3)をつけ、
今後作成するタスク一覧とタスク作成とログ一覧へのリンクをつけたものになります。
MySQLサーバの立ち上げ
MySQLサーバをローカルに構築するのは初心者には難しい部分が多いです。 複雑かつオペレーティングシステムやそのバージョンなどの要素にによって MySQL環境を作るために必要な操作が異なってきます。 また他プロジェクトでMySQLを利用していると干渉してしまう恐れがあります。 そのため今回は簡略化のためにDockerを使用します。
Dockerの講座をまだ受けておらずDockerとは何かわからない方もいるかもしれません。 今回はサンプルのDocker設定ファイルを載せますのでDockerの基礎について知らなくても進められます。 Dockerのインストールを行い3つのファイルを下記の通りにすれば簡単にMySQL環境が構築できます。 DockerのインストールはDocker Desktopを用いると簡単です。
compose.yml
version: "3"
services:
node:
build:
context: .
dockerfile: ./Dockerfile
volumes:
- .:/app
tty: true
depends_on:
- mysql
networks:
- default
ports:
- 3000:3000
environment:
QUICK: true
command:
sh -c "npm run watch"
mysql:
image: mysql:5.7.41
ports:
- 3306:3306
environment:
MYSQL_ROOT_PASSWORD: mysql
MYSQL_DATABASE: express_todo
MYSQL_USER: user
MYSQL_PASSWORD: password
restart: always
volumes:
- db_data:/var/lib/mysql
- ./init_sql:/docker-entrypoint-initdb.d
- ./logs:/var/log/mysql
volumes:
db_data: {}
Dockerfile
FROM node:18-alpine
WORKDIR /app
RUN apk update
RUN apk add npm
ADD package.json ./
COPY . .
RUN npm install
init_sqlというディレクトリを作りその中にinit.sqlを作成します。
init.sql
USE express_todo
CREATE TABLE IF NOT EXISTS task (
id VARCHAR(36) NOT NULL,
title VARCHAR(255) NOT NULL,
detail TEXT,
created_at DATETIME NOT NULL,
updated_at DATETIME NOT NULL,
by_using BOOLEAN NOT NULL DEFAULT true,
PRIMARY KEY (id)
);
CREATE TABLE IF NOT EXISTS log (
task_id VARCHAR(36) NOT NULL,
action VARCHAR(10) NOT NULL,
created_at DATETIME NOT NULL,
updated_at DATETIME NOT NULL,
by_using BOOLEAN NOT NULL DEFAULT true,
FOREIGN KEY (task_id) REFERENCES task(id)
);
INSERT INTO task (id, title, detail, created_at, updated_at, by_using)
VALUES
('1', 'sample_task1', 'sample_task1_value', NOW(), NOW(), true),
('2', 'sample_task2', 'sample_task2_value', NOW(), NOW(), true),
('3', 'sample_task3', 'sample_task3_value', NOW(), NOW(), true);
MySQLのバージョンについて
MySQLの最新バージョンは8系ですが、npmで公開されているmysqlモジュールを利用しアクセスしようとすると下記のようなエラーが出ます。
error
Error: ER_NOT_SUPPORTED_AUTH_MODE: Client does not support authentication protocol requested by server; consider upgrading MySQL client
これはMySQL8から認証方法がSHA256認証に限定されたためです。 mysql npmモジュールはSHA256認証に対応していないため使うことができません。 この教材ではMySQL5.7を使用していきますが、SHA256認証に対応したモジュールとしてmysql2というnpmモジュールも存在しています。
Docker
Dockerはコンテナ仮想化を利用してアプリケーションの実行環境を構築するためのオープンソースのプラットフォームです。
コンテナ内に必要なライブラリやツール、アプリケーションをパッケージ化し、開発者や運用担当者が異なる環境で同じアプリケーションを実行することを可能にします。
Dockerを使うことでアプリケーションの開発・テスト・デプロイを簡単に行うことができ、環境に依存しない実行環境を提供することができます。
また複数のアプリケーションを単一のサーバ上で隔離して実行することができるため、セキュリティ上のリスクを低減することができます。
今回の例では筆者がNode.jsv18とMySQL5.7.41+初期テーブル情報の環境をcompose.yml/Dockerfile/init.sqlに書き起こし、読者の皆さんが簡単にそれを再現できています。
ここまでで下記のようなディレクトリ構造になっていれば準備は完了です。
file_tree
.
├── Dockerfile
├── compose.yml
├── init_sql
│ └── init.sql
├── main.js
├── node_modules
│ └── any_file
├── package.json
├── package-lock.json
└── static
└── index.html
起動
下記コマンドを実行します。
terminal
$ docker-compose up
初回はMySQL側で初期データ登録が起動後に走るためnodeプロセスがエラーで終了すると思われます。
MySQLのログがしばらく流れてくるので止まったらmain.js
をエディタで開き、 WindowsまたはLinuxの方はCtrl+S
、Macの方はcommand+S
をします。
するとnodemonが更新を検知してnodeプロセスが再起動します。
ターミナルのログにServer started on port 3000 と表示されれば成功です。
http://localhost:3000 にブラウザでアクセスしてみてください。先ほど作ったindex.htmlが表示されます。

Lesson 14
Chapter 2
TODO登録処理を実装する
このチャプターではTODOアプリのタスク登録処理を実装していきます。
設計を図解すると下記の通りになります。
ブラウザからリクエストをmain.jsで受け取り、controller.jsを呼び出します。 controller.jsではmysqlモジュールを使用してデータベースに問い合わせを行い、値をmain.jsに返り値として渡します。 その後レスポンスが送信されます。
TODO登録処理のコードは下記の通りとなります。
main.js
app.post('/api/task', async(req, res) => {
if (!req.body || !req.body.title || !req.body.detail) {
res.send('データが足りないか破損しています' + TOP_LINK);
return;
}
if (req.body.title.length > 30) {
res.send('タイトルの文字数が30文字を超過しています' + TOP_LINK);
return;
}
await controller.create(req.body.title, req.body.detail);
res.send('作成しました' + TOP_LINK);
});
順番に見ていきます
リクエストの処理
main.js
app.post('/api/task', async (req, res) => {
これは/api/task
に
POSTメソッドでアクセスが来た時にreq変数とres変数を引数に取り動く関数です。
if文は後のバリデーションの欄で説明していきます。
controllerの呼び出し
main.js
await controller.create(req.body.title, req.body.detail);
これは後で説明するcreate関数に、bodyの内容を引数として渡して呼び出しています。
req.body
はPOSTメソッドを送ってきたクライアントからの情報を受け取ることができます。
main.js
res.send('作成しました' + TOP_LINK);
res.send
はクライアントにタスクの作成が成功したことをレスポンスするものです。
TOP_LINKは先述した定数で、トップページへのリンクが入っています。
バリデーションを実装する
今回のTODOアプリにおいてバリデーションとは、リクエストボディの入力値が要件を満たしているかどうかを検証することです。 具体的には、必須入力項目が入力されているか、文字数や形式が正しいか、範囲内に収まっているかなどを確認します。 バリデーションは、システムに入力されたリクエストボディが正しくない場合にエラーメッセージを返し、 正しいMySQL記録領域に保存されている値を保証するために非常に重要な役割を果たします。
データを受け取る際、例えばタイトルだけが入っていて内容が入っていない通信が発生するかもしれません。今回はタイトルと詳細が両方入っているデータのみを登録するコードを考えていきます。
データ欠損に対するバリテーション
main.js
if (!req.body || !req.body.title || !req.body.detail) {
res.send('データが足りないか破損しています' + TOP_LINK);
return;
}
if文は()の中がtrueと判断された場合{}の中身を実行します。また!演算子は直後の変数を否定します。 例えば!trueならfalse、!falseならtrueを返します。
この例では、 !req.bodyであれば、req.bodyがfalse/0/-0/0n/''/null/undefined/NaNであればtrue、逆にそれ以外の値の場合はfalseを返します。 req.bodyの否定もしくはreq.body.titleの否定もしくはreq.body.detailの否定を評価しています。 つまりどれか一つでも欠けている場合にこのif文の中身が実行されます。
レスポンス
res.send
はクライアントに返答を送信します。この例では、メッセージと定数定義されているトップページへのリンクを送信しています。
関数の終了
return は関数の実行結果を呼び出し元に返すために使用されます。今回は値を返す必要はありませんが、returnにより関数が終了しこれ以降のミドルウェアの評価は必要ないことを明示します。
undefinedの参照
変数hogeにundefinedが入っているとします。hoge.fugaを参照するとエラーが発生します。
JavaScript
> let hoge = undefined
undefined
> hoge.fuga
Uncaught TypeError: Cannot read properties of undefined (reading 'fuga')
foo.barを参照したいがfooがundefinedかもしれない場合、次のようにすることでエラーを回避できます。
JavaScript
foo && foo.bar
もしくは
!foo || foo.bar
&&演算子を用いる例では、右項fooがfalseである場合この式全体はfalseであることが確定するため左項は評価されません。右項がtrueである場合左項が評価され、式全体の評価として左項の値が出力されます。
||演算子を用いる例では、fooがfalseである場合、否定されtrueとなり式全体がtrueであると確定するため左項は評価されません。右項がtrueである場合左項が評価され、式全体の評価として左項の値が出力されます。
上記例では2項のため同じような結果になりましたが、これが3項以上になると組み合わせた時に論理演算の真価を発揮します。論理演算は単純ゆえに深く様々な応用が効きます。基本はor、and、notですので、基礎を理解した上でターミナル上でnodeプロンプトを実行し試しながら最適な論理演算を作れるようになりましょう。
タイトル長によるバリデーション
タイトルがあまり長すぎるものもバリデーションで弾いてみましょう。
main.js
if (30
req.body.title
には文字列が入っている想定です。
文字列.length
は文字数を返します。
30文字以上が入っていた場合はif文の関数が評価されres.send
でメッセージを送りreturn
で関数を終了しています。
Controllerを実装
Webアプリケーションにおいてクライアントからのリクエストを受け取り処理を行い、 その結果をクライアントに返す部分を担当するのがcontrollerです。
今回はmain.jsでリクエストを受け取り、 controller.jsでデータベースとの読み書きを行うように役割分担します。
作業ディレクトリにcotrolle.jsを作成し、下記の内容を記述してください
cotrolle.js
'use strict';
import mysql from 'mysql';
import { v4 as uuidv4 } from 'uuid';
const con = mysql.createConnection({
host: 'mysql',
user: 'user',
port: 3306,
password: 'password',
database: 'express_todo'
});
// MySQL 接続確認
con.connect(function (err) {
if (err) throw err;
console.log('MySQL connected');
});
順番に見ていきます。
インポート
cotrolle.js
import mysql from 'mysql';
import { v4 as uuidv4 } from 'uuid';
npmモジュールとしてインストールしたmysqlとuuidを読み込んでいます。 mysqlはデータベースのMySQLにアクセスするために、uuidは一意となる識別子の文字列を出力するモジュールです。
MySQLへの接続
cotrolle.js
const con = mysql.createConnection({
host: 'mysql',
user: 'user',
port: 3306,
password: 'password',
database: 'express_todo'
});
MySQLへ接続するための情報を引数に取りMySQLとconnectionを張りcon
定数に代入しています。
hostは今回はdockerを使って説明しているためdockerのコンテナ名をホスト名としてアクセスしています。ローカルに構築した場合はmysql
ではなくlocalhost
、外部サーバのMySQLを利用する場合はexample.com
のように記述します。
他の情報はデータベース側の設定に合わせます。今回はdockerのcompose.ymlに定義したアクセス情報を記述しています。
接続確認
cotrolle.js
con.connect(function (err) {
if (err) throw err;
console.log('MySQL connected');
});
connectionが成功した場合に実行される関数です。MySQLへの接続が失敗した場合は引数errにエラー情報が入っているためthrowを行い例外を発生させアプリ全体が止まります。
接続に成功した場合はconsole.logが実行されコンソールから接続に成功したことが確認できます。
Controllerを実装
それでは実際にタスクをデータベースに格納する関数を書いていきます。
cotrolle.js
export const create = async (title, detail) => {
const sql = 'INSERT INTO task (id, title, detail, created_at, updated_at, by_using)\
VALUES(?, ?, ?, NOW(), NOW(), true);';
const id = uuidv4();
const data = await new Promise((resolve) => {
con.query(sql, [id, title, detail], (err, result, fields) => {
if (err) throw err;
resolve(result);
});
});
return data;
};
順番に見ていきます。
exportについて
cotrolle.js
export const create = async (title, detail) => {
exportはこの関数を外部へエクスポートします。
今回はmain.jsからimportすることで main.jsからこの関数を利用できるようにする演算子です。
createという関数を定義し、引数にtitle、detailの二つを取ります。
asyncは非同期関数を扱うための構文です
async/await
asyncが登場する前は、コールバックと呼ばれる形式でその関数の実行を待つ手法が使用されていました。
JavaScript
setTimeout(() => {
// 実行内容
},1000)
この形式だとsetTimeoutが終わった後に実行したい場合、//実行内容の部分に記述していく必要があります。 また二重三重とコールバック関数が重なった場合インデントが深く可読性も下がるので、 内部で条件分岐などがあると処理の流れを理解することが難解になってしまいます。所謂コールバック地獄と呼ばれる状態です。
async関数を定義することで、その中でawaitを使って他の非同期処理の完了を待つことができます。
await が付いた非同期処理が完了するまで、その行で処理が止まります。
そして、その結果が返ってきたら処理が再開されます。
例えばawaitに対応した関数の代表としてdelayというnpmモジュールがありますが、それであればawait delay(5000)のように記述することでdelay関数が終了したら次の行に進みます。
先ほどのsetTimeoutの例ではdelayというnpmモジュールがawaitに対応しており
JavaScript
await delay(5000)
// 5秒後に実行される
このように記述することができます。
しかし、従前のコールバック形式で作られた関数やモジュールはawaitを使うことができません。
テクニックとしてPromiseで囲んでしまえばawaitと同様のことができます。 Promiseは非同期処理を扱うためのオブジェクトで、非同期処理が完了した時に値が変化します。
JavaScript
await new Promise((resolve) => {
setTimeout(() => {
resolve()
}, 5000);
});
これはsetTimeoutをawaitで呼べるようにした例です。 5秒後にresolveが呼ばれてawait処理が完了し次に進んでいきます。
SQL文の定義
cotrolle.js
const sql = 'INSERT INTO task (id, title, detail, created_at, updated_at, by_using)\
VALUES(?, ?, ?, NOW(), NOW(), true);';
const id = uuidv4();
まずSQL文を定義しています。con.queryに直接記述しても良いですが長い文字列は変数か定数にした方が関数内でこれはsqlであるとわかるので可読性の向上につながります。
SQL
SQLとはデータベースに対して操作を行うために用いられる言語のことです。 SQLを使用することで、データベースに格納されたデータを取得、更新、削除、 挿入などの操作を行うことができます。
SQLは、データベース管理システムによって実装されており、 MySQL、PostgreSQL、SQliteなど多くの種類が存在します。
このSQL文はtaskテーブルのid, table, detail, created_at, updated_at, by_usingそれぞれに、id, table, detailはプレースホルダを利用した値、updated_atとcreated_atはSQLのNOW関数を利用し現在時刻を、by_usingにはtrueをそれぞれ持つフィールドを作成するという内容になっています。
idはUUIDを使用し一意となるような値が入ります。
実際にSQLを実行するコード
cotrolle.js
const data = await new Promise((resolve) => {
con.query(sql, [id, title, detail], (err, result, fields) => {
if (err) throw err;
resolve(result);
});
});
return data;
これはコールバック関数であるcon.queryを先ほど紹介したPromiseで囲み同期的に処理をできるようにしています。
con.queryは与えられたsql文をMySQLデータベースに問い合わせを行う関数です。ここではプレースホルダを利用し、id, title, detail, created_at, updated_at, by_usingの値を持つフィールドをtaskテーブルに追加するという処理になっています。
SQL文の実行が終わるとresolveが呼ばれ、awaitが完了し、returnでSQLの実行結果を返します。
SQLインジェクションとプレースホルダ
例として、ログイン画面でユーザ名とパスワードを検証するSQLを考えてみます。
SQL
SELECT * FROM users WHERE username = '$username' AND password = '$password';
これはuserテーブルでusernameとpasswordの両方が一致するユーザのデータを出力するものです。 $usernameと$passwordはユーザから受け取った値をJavaScriptでそのまま入れるとします。
このとき、悪意のあるユーザが、$usernameに下記を入れたとします。
悪意のあるユーザの入力
' or '1'='1'--
この時に実行されるSQL文は、
SQL
SELECT * FROM users WHERE username = '' or '1'='1'--' AND password = '$password';
となります。これは非常に危険で、usernameが''または1=1に一致するユーザを出力し'1'='1'は常に一致しますので全てのユーザと一致するという意味になります。 また--はSQLではコメントを意味しますのでこれ以降のパスワードの一致は評価されず、結果としてuserテーブルにある全情報を悪意あるユーザに取得されてしまいます。
この例では全ユーザを取得するものを書きましたが、情報の取得に限らず悪意あるユーザがデータベースを好き勝手にいじれる状態は非常に危険です。 例えばWebページのデータベースを操作され悪意ある内容がページに表示されてしまうこと、またDROP DATABASE文を実行するようなインジェクションをかけられるとデータベースが消えるなど様々な甚大な被害が発生しかねない非常な重要な問題です。
プレースホルダというのはSQL文では値を?としておき、後でその値を安全な形で置き換えてデータベースへアクセスします。これによりSQLインジェクションを防止することができます。これであればユーザ名"' or '1'='1'--"というユーザを検索するという意味になり、悪意あるSQLインジェクションを防げます。
ビジネスロジックを記述する
ビジネスロジックとは、アプリケーションにおいて具体的な業務処理を実行するためのプログラムのことです。
main.jsからcontroler.jsを呼び出すために、main.jsに下記を記述します。
main.js
import * as controller from './controller.js';
このレッスンの初期で記述した下記コードについて解説します。
main.js
await controller.create(req.body.title, req.body.detail);
res.send('作成しました' + TOP_LINK);
importしてきたcontrollerのcreate
関数に、タイトルと詳細の引数を渡しています。
create
関数は先述の通りデータベースにデータを登録し、完了を待ってreturnされます。
DB登録完了後、res.send
関数で作成した旨のコメントとトップページへ戻るためのリンクをブラウザに返します。
ここまででAPI側の用意は完了しました。
htmlの用意
ブラウザから操作できるようにhtmlを追加します。static
ディレクトリの中にcreate.html
を作成し、下記内容を記述してください。
create.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<h3>TODO登録画面</h3>
<form action="/api/task" method="POST">
タイトル
<input type="text" name="title" />
<br />
内容
<textarea type="textbox" name="detail"></textarea>
<br />
<button type="submit">登録</button>
</form>
</body>
</html>
順番に見ていきます。
create.html
<!DOCTYPE html>
<html lang="ja">
これはこのページが日本語のhtmlであることを宣言しています。
headタグはブラウザに対してこのページの情報を伝えています。
<body>
〜
</body>
の間が実際に閲覧者に表示される内容です。
h3タグは見出しです。h1~h5の大きさで見出しをつけることができます。
formタグはこの中にあるinputタグやtextareaタグの内容をsubmitボタンが押された時に送信します。
actionで送信先を、methodでメソッドを指定しています。
inputタグのnameは送信時に入力内容に対する名前をつけています。
buttonが押されるとformタグの内容が送信されます。
動作確認を行う
ThunderClientから、json形式のbodyにtitleとdetailを持つPOSTリクエストを送信してみます。
作成しましたと表示されましたでしょうか?
また/create.htmlをブラウザで開きそこからも登録を試してみてください。
内容の簡易確認
作成後にこのコードをcontroller.jsに記述することでデータベース内のデータが全てみれます。
controller.js
const getAllConsole = async () => {
const sql = 'SELECT * FROM task WHERE by_using = true';
const data = await new Promise((resolve) => {
con.query(sql, (err, result, fields) => {
if (err) throw err;
resolve(result);
});
});
console.log(data)
};
getAllConsole()
自分で登録したタスクは入っていますでしょうか?
データベースが作られる際に初期データとして入っている3件のタスクと今登録したタスクが入っているかと思います。
確認できたら今追加したタスク一覧簡易確認の部分は削除してください。
データが入っていればこのチャプターは完了です。お疲れ様でした。

Lesson 5
Chapter 3
TODO 一覧取得処理を実装する
このチャプターではTODOアプリのタスク一覧取得処理を実装していきます。
Controllerを実装
controller.jsに下記を記述します。
controller.js
export const getAll = async () => {
const sql = 'SELECT * FROM task WHERE by_using = true';
const data = await new Promise((resolve) => {
con.query(sql, (err, result, fields) => {
if (err) throw err;
resolve(result);
});
});
return data;
};
大枠は先述のタスク作成と同様ですがSQL文が変化し、プレースホルダを利用しない状態になっています。
controller.js
SELECT * FROM task WHERE by_using = true
このSQL文は、taskテーブルから、by_usingカラムの値が1であるレコードを選択することを意味します。
SELECT *
という部分は、taskテーブルの全てのカラムを選択することを表し、これを実行するとtaskテーブルでby_usingが1の全てのデータを出力することができます。by_usingについてはタスク削除のチャプターで解説します。
他はタスク作成と同様で、SQLを実行し結果が出るまで待ち、それをreturnで値を返します。
ビジネスロジックを作成する
タスク一覧を取得する関数を定義します。
main.js
// タスク一覧
app.get("/api/tasks", async (req, res) => {
res.send(await controller.getAll())
})
/api/tasksにアクセスが来た時にcontroller.jsのgetAll関数を呼び出しレスポンスします。
ejsについて
ejsによる簡易フロント画面を考えます。
タスク作成の時は静的なhtmlからformでPOSTを投げていましたが、今回はejsを使い動的に内容を表示してみます。
main.jsに以下を記述します。
main.js
app.set('view engine', 'ejs');
app.get('/tasks', async (req, res) => {
res.render('tasks', { tasks: await controller.getAll() });
});
app.set('view engine', 'ejs')
は、
Expressアプリで使用するビューエンジンを設定するメソッドです。第一引数には、設定するオプション名を文字列で指定し、第二引数にはその値を指定します。この場合、view
engineというオプションにejsという値を設定することで、expressはejsテンプレートエンジンを使用することができます。
app.render()は、Expressアプリケーションで使用されるビューエンジンを使用して、指定されたビューをレンダリングするためのメソッドです。 今回はejsをセットしたためそれが適応されます。第二引数にオブジェクトを渡すことでejsをレンダリングする中で変数として利用できます。
ejsのデフォルトで指定されるディレクトリは./viewsです。viewsというディレクトリを作成しその中にtasks.ejsというファイルを作成し下記を記述してください。
tasks.ejs
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>TODO_tasks</title>
</head>
<body>
<h3>タスク一覧</h3>
<table>
<tr>
<th>タイトル</th>
<th>内容</th>
<th>詳細へ</th>
</tr>
<% for (var i = 0; i < tasks.length; i++) { %>
<tr>
<td><%= tasks[i].title %></td>
<td><%= tasks[i].detail %></td>
<td>
<a href="/task/<%= tasks[i].id %>">詳細へ</a>
</td>
</tr>
<% } %>
</table>
<a href="/">トップへ</a>
</body>
</html>
<body>までの意味はタスク追加のコードと同義です。
<table>はHTMLのテーブルを作成するためのタグです。このタグを使うことで、表形式でデータを表示することができます。
<table>タグ内に<tr>タグを使って行を作成し、<th>タグを使ってヘッダーを定義することができます。 <td>タグを使って、各セルにデータを表示することができます。表の行数や列数は、必要に応じて自由に追加・削除することができます。
この例では、タイトル、内容、詳細へという見出しをつけ、ejsの機能で各データを表示しています。
ejs
ejsとはJavaScriptのテンプレートエンジンの1つであり、HTMLテンプレート内でJavaScriptの変数やループ処理などを利用できます。
構文 | できること |
---|---|
JavaScriptコードを記述することができます | |
JavaScriptの変数や式を埋め込むことができます。タグはエスケープされます。 | |
JavaScriptの変数や式を埋め込むことができます。タグはエスケープされません。 | |
コメントを記述することができます。 |
例として、このように記述するとvalue変数の内容を表示できます。
ejs
また、このような記述があったとします。
ejs
<p>合格です</p>
<p>不合格です</p>
この場合、score変数が70以上であれば合格です、69以下であれば不合格ですと表示されます。for文なども同様の構文で記述可能です。
今回の場合、全てのタスクの情報が入った配列をtasks変数としてejsに渡し、for文でtableタグの中に展開しています。
その結果をレンダリングしています。for文が回る回数はtasks.length
で配列の要素数を取得し表示するたびに動的に変化させています。
動作確認を行う
ここまででタスク一覧の表示ができるようになりました。
ThunderClientで/api/tasksにアクセスすると下記のようなレスポンスが得られます。
また、ブラウザでhttp://localhost:3000/tasks にアクセスしてみてください。
タスク一覧が表示できていればこのチャプターは成功です。

Lesson 14
Chapter 4
TODO 詳細表示処理を実装する
このチャプターではTODOアプリのタスク詳細表示機能を実装していきます。 タスクを一つずつ表示する機能を作っていきたいと思います。
main.jsに下記を追記してください。
main.js
// タスク取得
app.get("/api/task/:id", async (req, res) => {
res.send((await controller.get(req.params.id))[0])
})
:idはparamsと呼ばれるものです。 URLの一部分をデータを受け渡すために使うことができます。 上記例だとGETメソッドで/task/1にアクセスし関数が動くと、関数内でreq.params.idから1が取得できます。 paramsは幾つでも設定できます。 URLにデータを持たせられるため非常に有用な機能ですが、HTTPSで暗号化した通信を行なってもURLの部分は暗号化されないためパスワードなどの重要な情報をparamsでやりとりしないようにしましょう。
ejs向けのコードは下記の通りになります。
main.js
app.get('/task/:id', async (req, res) => {
res.render('task', { task: (await controller.get(req.params.id))[0] });
});
getを受け取ってrenderでejsをレンダリングする部分は従前の通りです。
Controllerを実装
controller.jsに下記を記述してください。
file_name
export const get = async (id) => {
const data = await new Promise((resolve) => {
con.query(
'SELECT * FROM task WHERE id = ?',
[id],
(err, result, fields) => {
if (err) throw err;
resolve(result);
}
);
});
return data;
};
実行するSQLの内容が変化しましたが、基本はこれまでと同じです。
今回のSQLの意味としては、タスクテーブルから引数で受け取ったidと一致するレコードをプレースホルダを使用して検索するという意味になります。
今回idは一意になるように設計していますが、例えばtitleで検索した時に同じタイトル名のレコードがあればそれが全てヒットするためmysqlモジュールからは下記のような配列が得られます。
mysql 戻り値
[
RowDataPacket {
id: '2874898d-44bd-4b25-8be8-b52a82a53b8a',
title: 'サンプルタスク1',
detail: 'サンプルタスクの詳細',
created_at: 2023-03-15T15:25:47.000Z,
updated_at: 2023-03-15T15:25:47.000Z,
by_using: 1
}
]
今回はidで検索しているため1件しかヒットしません。そのため、
controller.js
await controller.get(req.params.id)
を括弧で囲み、[0]とすることで一番目のレコードを取得しています。
controller.js
(await controller.get(req.params.id))[0]
JavaScriptをはじめ多くのプログラミング言語に言えることですが、0から数え始める点に注意しましょう。また、括弧をつけないと
controller.js
await (controller.get(req.params.id)[0])
と等価に評価されてしまい、正しい結果が得られないことに注意しましょう。awaitが不要な関数では括弧をつける必要はありません。
ejsについて
viewsディレクトリの中にtask.ejsを作成し、下記内容を記述してください。
task.ejs
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>TODO_task</title>
</head>
<body>
<h3>タスク詳細ページ</h3>
<h5>タイトル</h5>
<p><%= task.title %></p>
<h5>詳細</h5>
<p><%= task.detail %></p>
<h5>作成日時</h5>
<p><%= new Date(task.created_at).toJSON() %></p>
<h5>更新日時</h5>
<p><%= new Date(task.updated_at).toJSON() %></p>
<button onclick="location.href='/edit/<%= task.id %>'">編集</button>
<a href="/">トップへ</a>
</body>
</html>
paramsでidを指定しcontrollerで情報を取得、ejsに値を入れてレンダリングを行っています。
日付型のstring型へのキャストについて
task.ejs
task.created_at.toJSON()
task.created_atはDate型(日付型)のデータです。これはstring型ではないので本来はテキストとして表示できない形式です。
Date型の値をそのままテキストとして表示しようとすると.toString()
という関数を使い自動的にstring型にキャストする機能が標準で備わっています。
しかし、この自動キャストされた文字列は日本人からすると少し読みにくく、また環境によって表示が異なる問題があります。
JavaScript
Wed Mar 15 2023 15:25:47 GMT+0000 (Coordinated Universal Time)
Date型にはもう一つstring型にキャストする関数があり、それが.toJSON()
です。下記のようなstring型にキャストしてくれます。
JavaScript
2023-03-15T15:25:47.000Z
これはISO8601形式と呼ばれる国際規格に基づいた日付と時刻の表示方法です。
こちらの方が読みやすく、またtoString()
は環境により表示形式が少し異なる場合があるためこちらを採用しましたが、
toString()
の方が好みであればそちらを用いても構いません。
動作確認を行う
動作確認を行います。
ThunderClientから、/api/task/${先ほど取得したタスク一覧のどれかのid}にアクセスすると、そのタスクの詳細が得られます。
タスク詳細は表示できましたでしょうか?
ejsの方もタスクごとのページができました。タスク一覧の時に作ったリンクがこのページに飛ぶようにできていますのでクリックしてタスクの詳細が表示できるか確認してみてください。

Lesson 14
Chapter 5
TODO 更新処理を実装する
このチャプターではTODOアプリのタスク更新処理を実装していきます。
main.jsに下記を追記してください。
main.js
app.put('/api/task/:id', async (req, res) => {
await controller.edit(req.params.id, req.body.title, req.body.detail);
res.send('更新しました');
});
/api/task/:idにPUTメソッドでアクセスがあった際にこの関数が回り、controller.editを呼び出しています。完了したら更新しましたとメッセージを送信しています。
ejsの方も見ていきます。
main.js
app.get('/edit/:id', async (req, res) => {
res.render('edit', { task: (await controller.get(req.params.id))[0] });
});
ここでは/edit/idにgetメソッドでアクセスがあった場合にedit.ejsとparamsのidから得られたタスクの情報を用いてレンダリングを行っています。編集する上で初期値となるデータを取得するためidから一件データを取得しています。controllerは詳細表示の時に使った関数をそのまま流用しています。
Controllerを実装
controller.jsも見ていきましょう。
controller.js
export const edit = async (id, title, detail) => {
const data = await new Promise((resolve) => {
con.query(
'UPDATE task SET title = ?, detail = ?, updated_at = NOW() WHERE id = ?;',
[title, detail, id],
(err, result, fields) => {
if (err) throw err;
resolve(result);
}
);
});
return data;
};
エクスポートを使用しedit関数を定義しています。引数にid、title、detailを取ります。
今回のSQL文はtaskテーブルをidで検索し、ヒットしたレコードのtitle、detail、updated_atを上書きするというものです。
titleとdetailは例によってプレースホルダを用い、updated_atにはSQLのNOW()
関数を用いて現在時刻を入れています。
フロントの実装
viewsディレクトリの中にedit.ejsを作成します。
edit.ejs
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>TODO_edit</title>
</head>
<body>
<h3>タスク編集ページ</h3>
<form onsubmit="submitForm(event)">
<h5>タイトル</h5>
<input type="text" name="title" value="<%= task.title %>" />
<h5>詳細</h5>
<textarea name="detail"><%= task.detail %></textarea>
<button type="submit">更新</button>
</form>
<a href="/">トップへ</a>
</body>
<script>
const submitForm = (event) => {
event.preventDefault();
const url = "/api/task/<%= task.id %>";
const form = event.target;
const formData = new FormData(form);
fetch(url, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
title: formData.get("title"),
detail: formData.get("detail"),
}),
})
.then((response) => {
console.log("Success");
alert("更新しました");
location.href = "/";
})
.catch((error) => {
console.error(error);
});
return false;
};
</script>
</html>
解説していきます。
ejsを用いて値を入れるのは従前の通りですが、入れている場所が異なります。今回はinputタグの初期値として入れたいため、value
の値としてデータを入れています。
edit.ejs
<input type="text" name="title" value="<%= task.title %>" />
textareaタグは通常のタグのようにタグで挟むとそれが初期値として適応されます。
edit.ejs
<textarea name="detail"><%= task.detail %></textarea>
formタグのonsubmitですが、これはsubmitがされた際にscriptタグ内のsubmitForm関数にevent引数を持たせて呼び出すものです。
htmlからPUTメソッドを送るには
今回更新のためPUTメソッドを用いたいのですが、formタグはGETかPOSTメソッドしか対応していないため、少し工夫してJavaScriptからPUTメソッドを送信します。
scriptタグ内を見ていきましょう。
edit.ejs
const submitForm = (event) => {
event.preventDefault();
const url = "/api/task/";
const form = event.target;
const formData = new FormData(form);
この部分ではsubmitForm関数を定義し、event引数を受け取っています。
event.preventDefault();
はformタグでsubmitがされた際のデフォルトの動作を無効化し、formタグから直接リクエストが送られないようにしています。
url
はのちで使用するための定数です。送信先を定義しています。
const form
ではイベントに含まれているtargetを一度定数に入れています。
const formData = new FormData(form);
でformタグの中身を取り出しています。
edit.ejs
fetch(url, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
fetch
関数はJavaScriptからリクエストを送信します。先ほど定義したurlに、メソッドはPUTを指定しています。
headers
ではこのリクエストに含まれるbodyがjson形式であることを宣言しています。
edit.ejs
body: JSON.stringify({
title: formData.get("title"),
detail: formData.get("detail"),
}),
})
bodyにはtitleとdetailを持つオブジェクトを生成し、そのままでは送信できないためJSON.strigify
関数を使ってstring型に変換しています。
formData変数には先ほど定義したイベントからのformタグのデータが入っておりformData.get("name")で値を呼び出しています。
edit.ejs
.then((response) => {
alert("更新しました");
location.href = "/";
})
.catch((error) => {
alert("更新に失敗しました")
});
};
.then
は非同期関数において成功した場合に呼ばれる関数です。
alert関数はダイアログを用いてブラウザ上にメッセージを表示する関数で、今回は更新に成功したメッセージを表示しています。
location.hrefはブラウザを遷移させます。この場合は'/'に遷移します。
.catch
はリクエストに失敗した場合に呼ばれます。
動作確認を行う
ここまでで更新ができるようになりました。
ThunderClientからbodyにtitleとdetailを持つPUTリクエストを送信してみます。
更新しましたと表示されましたでしょうか?
またブラウザを操作し、既存のタスクがGUIからも編集できるか確認してみてください。
実際に更新できたか詳細簡易表示の機能を使い確かめます。
更新は成功していますでしょうか?

Lesson 14
Chapter 6
TODO削除処理を実装する
このチャプターではTODOアプリのタスク削除処理を実装していきます。
main.jsから見ていきましょう。
main.js
app.delete('/api/task/:id', async (req, res) => {
await controller.destroy(req.params.id);
res.send('削除しました');
});
/api/task/:idにDELETEメソッドでアクセスがあった際にこの関数が回ります。controller.jsのdestroy関数をidを引数として呼び出し、 削除完了後に削除しましたとメッセージを送っています。
Controllerを実装
controller.jsを見ていきます。
controller.js
export const destroy = async (id) => {
const data = await new Promise((resolve) => {
con.query(
'UPDATE task SET by_using = false WHERE id = ?;',
[id],
(err, result, fields) => {
if (err) throw err;
resolve(result);
}
);
});
return data;
};
従前の通り関数を定義しています。このSQLの意味としてはtaskテーブルをidで検索し、
ヒットしたレコードのby_usingをfalseにしています。
タスク一覧を表示するチャプターでby_using = true
というWHERE句をつかいました。また、タスクを作成する時に必ずby_usingはtrueが入るように設計しています。それをfalseとすることで、一覧取得から削除されたように動作することが可能です。
by_using
by_usingカラムを用いることには複数のメリットがあります。
今回はTODOアプリですが、これが例えばチャットアプリだった場合、ユーザーが不適切な投稿をした後に削除処理をするかもしれません。物理的に削除してしまうと証拠が残らず、行政や裁判所から情報の提出を求められた時に対応が難しくなってしまいます。
また、アカウントのテーブルであれば退会したユーザーを削除してしまうとどのようなユーザーがいたのか後から確認できないなどの不都合が生じます。
またデータベースからレコードを物理的に削除することがないため、誤って削除した場合でも復旧することができます。
もし本当にデータベース上から抹消したい場合は、下記のSQLを実行することで削除することができます。
SQL
DELETE FROM task WHERE id = 'id';
動作確認を行う
動作確認をします。
ThunderClientからDeleteメソッドによるリクエストを送信してみます。
削除しましたと表示されましたでしょうか?
またブラウザを操作し、既存のタスクがGUIからも削除できるか確認してみてください。
実際に削除が成功したか、タスク一覧画面から確認してみてください。
