はじめに
Dcokerの使い方を学んだので、Docker上でNode.jsアプリケーションを作ってみました。
非常に多くの記事やサイトを参考にして作ったので、自分用のまとめ的な側面もあります。
作成したのは簡単なToDoリストアプリです。
初心者なので色々おかしな点があると思いますが、気づいたらコメントで教えていただけるとありがたいです。
完成イメージ
CRUD機能を搭載した簡単なToDoリストを作ります。
完成イメージは以下の通りです。
![ToDoリストアプリの完成形.png]()
一番上のテキストボックスにタスクを入力し、右にある「add!」ボタンを押すことでタスクを追加できます。(Create)
その下には、既にデータベースに保存されているタスクが表示されます。(Read)
これらのタスクは、チェックボックスやテキストボックスの値を書き換えることで、自動的に更新されます。(Update)
また、タスク右の「delete」ボタンを押すことで、タスクを削除できます。(Delete)
※テキストボックスの値は、Enterキーを押すかフォーカスが外れたときに入力が確定します。
主な参考サイト
基本的には以下のサイトを参考にしていますが、一部自分なりにアレンジしています。
使用したソフトウェア等
DockerホストのPCはWindows10を使用しました。WSL2でUbuntuを実行し、Ubuntu上で作業を行いました。
- Windows10 バージョン 2004
- Ubuntu 20.04.2 LTS (Focal Fossa)
- Docker 20.10.2
- Node.js 14.16.0
- express 4.16.1
- sequelize 6.5.0
- sequelize-cli 6.2.0
- VueCLI 4.5.0
- Vue.js 3.0.0
- MySQL 8.0.23
システムの全体構成
システムは、3つのコンテナで構成されています。
db
コンテナ(MySQLイメージを使用)
- ToDoリストアプリのデータを保管するデータベースサーバーです。
vue
コンテナ(Node.jsイメージを使用)
- ユーザーからのリクエストを受けるサーバーです。今回はフロントエンドにVue.jsを使うため、Vue CLIをインストールします。
api
コンテナ(Node.jsイメージを使用)
- MySQLサーバーを操作するAPIを提供するサーバーです。
vue
コンテナは本サーバーが提供するAPIを利用してデータベースを操作します。Sequelizeをインストールします。
![Docker①.jpg]()
![Docker②.jpg]()
プロジェクトのディレクトリを作成
ディレクトリの構成は以下のようになります。
- todo_app
- docker-compose.yml
- .env
- api
- vue
- db
- logs
- mysql-error.log
- mysql-query.log
- mysql-slow.log
- conf
以下のコマンドを適当なディレクトリで実行してください。
mkdir-p todo_app/api
mkdir-p todo_app/vue
mkdir-p todo_app/db/logs
mkdir-p todo_app/db/conf
touch todo_app/docker-compose.yml
touch todo_app/.env
touch todo_app/db/logs/mysql-error.log
touch todo_app/db/logs/mysql-query.log
touch todo_app/db/logs/mysql-slow.log
touch todo_app/db/conf/my.cnf
docker-compose.ymlファイルを編集
todo_app/docker-compose.yml
ファイルを以下のように編集します。
todo_app/docker-compose.yml
version:'3'services:db:# 起動するイメージを指定image:mysql:8.0.23# 環境変数を設定environment:-MYSQL_ROOT_HOST=${DB_ROOT_HOST}-MYSQL_DATABASE=${DB_NAME}-MYSQL_USER=${DB_USER}-MYSQL_PASSWORD=${DB_PASS}-MYSQL_ROOT_PASSWORD=${DB_PASS}-TZ=${TZ}# ホスト側のポート:コンテナのポートports:-'3306:3306'# ボリュームバインドvolumes:-./db/conf:/etc/mysql/conf.d/:ro-mysqldata:/var/lib/mysql-./db/logs:/var/log/mysql#使用するネットワークnetworks:-backendapi:image:node:14.16.0-busterenvironment:-MYSQL_SERVER=db-MYSQL_USER=${DB_USER}-MYSQL_PASSWORD=${DB_PASS}-MYSQL_DATABASE=${DB_NAME}-TZ=${TZ}-CHOKIDAR_USEPOLLING=true#コンテナを起動させ続けるよう設定tty:trueports:-'3000:3000'# ソースコードを格納するフォルダをマウント#(ホスト側の./apiをコンテナの/appにマウント)volumes:-./api:/app# 起動時のカレントフォルダを指定working_dir:/app# 起動後に実行するコマンドを指定command:npm run devnetworks:-backend#依存関係(apiコンテナより先にdbコンテナが起動するように設定)depends_on:-dbvue:image:node:14.16.0-busterenvironment:-CHOKIDAR_USEPOLLING=truetty:trueports:-'8080:8080'volumes:-./vue:/appworking_dir:/appcommand:npm run servenetworks:-backenddepends_on:-apinetworks:backend:volumes:mysqldata:
db
、api
、vue
の3つのサービスを定義しています。
イメージimage:
イメージはMySQLとNode.jsの公式イメージをそのまま使用しています。(今回はDockerfileを使いません。)Node.jsイメージはnode:14.16.0-buster
を使用しています。なお、buster
というのはDebianのVer.10のことです。ちなみにDebianのバージョン名は映画「トイ・ストーリー」の登場キャラクターが元になっているそうです。バージョン14.16.0
は記事執筆時点での最新のLTS版を使用しています。
環境変数environment:
db
コンテナとapi
コンテナには、MySQLを使用(MySQLに接続)するための環境変数を設定していますが、後述する.env
ファイルで定義したものを取り込むようにしています。直接docker-compose.ymlファイルに記述しなかったのは、データベースの接続情報をソースに含めるのはよろしくないからです。Githubに公開するときは.gitignore
に.env
ファイルを追加し、ソース管理から除外しましょう。
api
コンテナとvue
コンテナの環境変数には- CHOKIDAR_USEPOLLING=true
を設定しています。これを書かないと、apiコンテナに入れるソースコードの変更を検知して自動でアプリを再起動するライブラリ「nodemon」が正常に動作しません。一応vue
コンテナにも書きました。
参考:Docker 環境で nodemon が watch してくれない問題と対処方法 - Qiita
ポートports:
ユーザーからリクエストを受けるvue
コンテナの8080番ポートと、ホスト側の8080番ポートを対応させています。
残りの2つのコンテナはコンテナ間のみで通信できればいいので、Dockerホストにマッピングする必要はないですが、api
コンテナもホストから動作を確認したいので設定しています。api
コンテナの3000番ポートと、ホスト側の3000番ポートを対応させています。(なお、db
コンテナも対応付けていますが、これは意味ないです。)
ボリュームvolumes:
db
コンテナでは、ホスト側のtodo_app/db/conf
ディレクトリをコンテナの/etc/mysql/conf.d/
ディレクトリにバインドマウントして、MySQLのデフォルト設定を記述したmy.cnf
ファイル(後述)を上書きしています。
また、MySQLのデータはコンテナが削除されてもデータが消えないように、mysqldata
というボリュームを作成し、コンテナの/var/lib/mysql
ディレクトリにボリュームマウントしています。(Windows環境ではバインドマウントだと上手くいかないそうです。)
さらに、MySQLのログデータを格納しているディレクトリもバインドマウントすることで、ホストからログを確認できるようにしています。
api
コンテナでは、ホスト側のtodo_app/api
をapiコンテナの/app
にバインドマウントすることで、アプリケーションのファイル群をホスト側から編集可能にしています。
vue
コンテナでも同様です。(ホスト側のtodo_app/vue
をvueコンテナの/app
にバインドマウント)
コマンドcommand:
api
コンテナおよびvue
コンテナでは、起動時に実行するコマンドを設定しています。しかし、現時点ではこれらのコマンドを動作させるために必要なライブラリがインストールされていないため、コンテナを起動してもすぐに終了してしまいます。
ネットワークnetworks:
コンテナ名でコンテナ間の通信を行うために、backend
という名前のDockerネットワークを作り、db
コンテナ、api
コンテナ、vue
コンテナで設定しています。
依存関係depends_on:
依存関係を設定しました。db
→api
→vue
コンテナの順で起動します。停止するときは逆順になります。vue
コンテナ(Todoリストアプリ)はapi
コンテナの提供するAPIを使用しないとデータのCRUD(作成・取得・更新・削除)ができません。つまり、vue
コンテナはapi
コンテナに依存していると言えます。そのapi
コンテナも、データをdb
コンテナに取りにいかなければなりません。api
コンテナはdb
コンテナに依存していると言えます。
my.cnfの編集
todo_app/db/conf/my.cnf
を以下のように編集します。
todo_app/db/conf/my.cnf
# MySQLサーバーへの設定
[mysqld]
# 文字コード/照合順序の設定
character-set-server = utf8mb4collation-server = utf8mb4_bin# タイムゾーンの設定
default-time-zone = SYSTEMlog_timestamps = SYSTEM# デフォルト認証プラグインの設定
default-authentication-plugin = mysql_native_password# エラーログの設定
log-error = /var/log/mysql/mysql-error.log# スロークエリログの設定
slow_query_log = 1slow_query_log_file = /var/log/mysql/mysql-slow.loglong_query_time = 5.0log_queries_not_using_indexes = 0# 実行ログの設定
general_log = 1general_log_file = /var/log/mysql/mysql-query.log# mysqlオプションの設定
[mysql]
# 文字コードの設定
default-character-set = utf8mb4# mysqlクライアントツールの設定
[client]
# 文字コードの設定
default-character-set = utf8mb4
ここでは、文字コードの設定や、ログの出力先設定などを行っています。
また、デフォルトの認証プラグインを変更しています。
なお、DockerホストがWindowsの場合、todo_app/db/conf/my.cnf
は読み取り専用にしておきます。
参考:[Docker+Windows]mysqlのdockerイメージがmy.cnfのマウントのエラーで起動しない時の対処法 - Qiita
![my.cnfを読み取り専用にする①.png]()
![my.cnfを読み取り専用にする②.png]()
.envファイルの編集
todo.app/.env
ファイルを以下のように編集します。
docker-compose.ymlでMySQLの環境変数を設定しますが、このファイルから具体的な値を取得しています。
参考:docker-composeのenv_fileと.envファイルの違い - Qiita
todo_app/.env
DB_ROOT_HOST=%
DB_NAME=todo
DB_USER=username
DB_PASS=mypassword
TZ=Asia/Tokyo
apiコンテナを起動してコンテナ内でExpress.jsアプリケーションを作成する
api
コンテナを起動して、express.jsアプリケーションの雛型を作成していきます。
docker-compose.ymlが置いてあるディレクトリ(todo_app/
)で次のコマンドを実行します。
#docker-compose.ymlが置いてあるディレクトリに移動cd todo_app
#apiコンテナを一時的に起動$ docker-compose run --rm--no-deps api /bin/bash
このコマンドは、api
コンテナを実行(run
)し、シェルを起動する(/bin/bash
)という意味になります。
--rm
は、実行後に自動でコンテナを削除するオプションです。
--no-deps
は、リンクしたサービスを起動しないようにするオプションです。docker-compose.ymlでコンテナの依存関係を定義したため、本来ならばapi
コンテナが起動する前にdb
コンテナが立ち上がるはずですが、このオプションを設定することでapi
コンテナのみ起動するようにしています。
lsコマンドで/app
ディレクトリ内に何もないことを確認しておきましょう。
====ここからapiコンテナ内 ====# lsコマンドで、/appディレクトリ(=ホスト側のtodo_app/api)内にまだ何もないことを確認ls-al
total 4
drwxrwxrwx 1 node node 4096 Feb 15 11:48 .
drwxr-xr-x 1 root root 4096 Feb 15 11:46 ..
私の環境ではnpmのバージョンを上げておかないとライブラリのインストールが失敗してしまうことがあったので、npmをアップデートしておきます。(今後も何度かこの操作を行うのでDockerfileに書いておくべきだったかもしれません…。)
==== apiコンテナ内 ====# npmのバージョンを確認
npm -v
6.14.11
# npmのアップデート
npm install-g npm
# npmがアップデートされたことを確認
npm -v
7.6.1
npm install
でexpress-generatorをインストールして実行します。
(なぜかnpx express-generator
では上手くいきませんでした。npmのバージョンを上げなければうまくいくのですが…)
==== apiコンテナ内 ====# express-generatorをインストールして実行
npm install-g express-generator
express .
nodemonをインストール。ソースコードを変更したときに、自動でサーバーを再起動してくれる便利なツールです。開発環境でのみ使用するので-D
(--save-dev
)オプションを付けておきます。
==== apiコンテナ内 ====# nodemonをインストール
npm install-D nodemon
exitでコンテナを抜けます。
==== apiコンテナ内 ====# exitでコンテナを抜けるexit====ここまでapiコンテナ内 ====
todo_app/api/package.json
を編集して、"scripts"
に"dev"
を追加します。
todo_app/api/package.json
{"name":"app","version":"0.0.0","private":true,"scripts":{"dev":"nodemon ./bin/www","start":"node ./bin/www"},"dependencies":{"cookie-parser":"~1.4.4","debug":"~2.6.9","express":"~4.16.1","http-errors":"~1.6.3","jade":"~1.11.0","morgan":"~1.9.1","mysql2":"^2.2.5","sequelize":"^6.5.0","sequelize-cli":"^6.2.0"},"devDependencies":{"nodemon":"^2.0.7"}}
以下のコマンドで3つのコンテナをまとめて起動します。
起動後、apiコンテナはnpm run dev
が自動的に実行されます。(docker-compose.ymlで定義しました。)
http://localhost:3000/に接続して、Expressの初期画面が表示されることを確認します。
![Expressの初期画面.png]()
ここで、docker-compose ps
コマンドでコンテナの起動状況を確認しておきます。
vueコンテナだけ状態(State
)がExit
になっています。(停止状態になっています。)
![vueコンテナだけExitになっている.png]()
これは、vueコンテナを起動時に実行されるコマンド(npm run serve
)の実行に失敗したためです。後ほどvueコンテナに入りVue CLIをインストールすることで、このコマンドを実行できるようになります。
dbコンテナを起動して正常に設定されていることを確認
起動済みのdbコンテナに入り、MySQLにログインする。
以下のコマンドを実行し、dbコンテナのシェルを起動してMySQLにログインします。
$ docker-compose exec db /bin/bash
====ここからdbコンテナ内 ====
mysql -u root -p
MySQLログイン時のパスワードは、.env
ファイルで指定したものを入力してください。(今回はmypassword
)
MySQLの文字コードを確認
MySQLにログイン出来たら、念のため文字コードの設定を確認します。
==== dbコンテナ内 ====
mysql> show variables like 'char%';
![MySQL.png]()
todo_app/db/conf/my.cnf
で指定した通りの設定になっていればOKです。
データベースを確認
データベースの状態を確認します。
==== dbコンテナ内 ====
mysql> show databases;
![データベースの確認.png]()
環境変数で指定したtodo
というデータベースが存在しているのがわかります。
todo
データベースの中身はどうなっているでしょうか?
==== dbコンテナ内 ====
mysql> use todo;
mysql> show tables;
![データベース切り替え&テーブル確認.png]()
todo
データベースの中身は空です。(テーブルは1つも存在しません。)
テーブルの作成は手作業でもできますが、今回はapiコンテナからSequelizeというライブラリのDBマイグレーション機能を使用して行います。
quit
でMySQLを抜け、exit
でdbコンテナも抜けてください。
==== dbコンテナ内 ====
mysql> quit
exit====ここまでdbコンテナ内 ====
apiコンテナ内で、sequelizeとその依存パッケージをインストール
今度はapiサーバーを設定していきます。
apiコンテナのシェルを立ち上げ、npmのバージョンをアップデートしておきます。
$ docker-compose exec api /bin/bash
==== apiコンテナ内 ====# npmのバージョンを確認
npm -v
6.14.11
# npmのアップデート
npm install-g npm
# npmがアップデートされたことを確認
npm -v
7.6.1
まず、APIコンテナ内でSequelizeとその依存パッケージをインストールします。
==== apiコンテナ内 ====
npm install mysql2 sequelize sequelize-cli
次に、sequelize-cliを使用してSequelizeの初期化を行います。
==== apiコンテナ内 ====
npx sequelize-cli init
これによって、apiフォルダ内にconfig
、migrations
、models
、seeders
の4つのディレクトリが作成されます。
次に、以下のコマンドでモデルクラスを生成します。
==== apiコンテナ内 ====
npx sequelize-cli model:generate --name Task --attributes name:string,done:boolean
上記の例では、string型のname
と、boolean型のdone
という2つのプロパティを持つ、Task
というモデルクラスを作成しています。
カラム名 | 型 | 説明 |
---|
name | string | タスク名 |
done | boolean | タスクが終了したか否か |
実行すると、todo_app/api/models
ディレクトリに、task.js
ファイルが出来ているのがわかります。
todo_app/api/models/task.js
'use strict';const{Model}=require('sequelize');module.exports=(sequelize,DataTypes)=>{classTaskextendsModel{/**
* Helper method for defining associations.
* This method is not a part of Sequelize lifecycle.
* The `models/index` file will call this method automatically.
*/staticassociate(models){// define association here}};Task.init({name:DataTypes.STRING,done:DataTypes.BOOLEAN},{sequelize,modelName:'Task',});returnTask;};
また、同時にtodo_app/api/migrations
ディレクトリに、{日時}-create-task.js
というファイルも出来ています。
todo_app/api/migrations/[yyyyMMddHHmmss]-create-task.js
'use strict';module.exports={up:async(queryInterface,Sequelize)=>{awaitqueryInterface.createTable('Tasks',{id:{allowNull:false,autoIncrement:true,primaryKey:true,type:Sequelize.INTEGER},name:{type:Sequelize.STRING},done:{type:Sequelize.BOOLEAN},createdAt:{allowNull:false,type:Sequelize.DATE},updatedAt:{allowNull:false,type:Sequelize.DATE}});},down:async(queryInterface,Sequelize)=>{awaitqueryInterface.dropTable('Tasks');}};
この時点のフォルダ構成は次の通りです。
![フォルダ構成.png]()
sequelize-cliでDBマイグレーションを実行
次は、DBマイグレーションを実行してtodo
データベースにテーブルを作成します。
しかし、その前にDB接続情報を正しく設定する必要があります。
先程npx sequelize-cli init
でSequelizeの初期化を行った時に、todo_app/api/config
ディレクトリにconfig.json
というJSONファイルが出来ているはずですが、それをconfig.js
にしてJavascriptファイルにします。
そして内容を次のように書き換えます。
これは、docker-compose.ymlで設定した環境変数の値をそのままDB接続情報として使うためです。
todo_app/api/config/config.js
module.exports={development:{username:process.env.MYSQL_USER,password:process.env.MYSQL_PASSWORD,database:process.env.MYSQL_DATABASE,host:process.env.MYSQL_SERVER,dialect:'mysql',},test:{username:process.env.MYSQL_USER,password:process.env.MYSQL_PASSWORD,database:process.env.MYSQL_DATABASE,host:process.env.MYSQL_SERVER,dialect:'mysql',},production:{username:process.env.MYSQL_USER,password:process.env.MYSQL_PASSWORD,database:process.env.MYSQL_DATABASE,host:process.env.MYSQL_SERVER,dialect:'mysql',},};
また、todo_app/api/models/index.js
ファイルを開き、config.json
となっている部分をconfig.js
に変更します。
![index.jsの修正.png]()
この状態で、以下のコマンドを実行するとSequelizeによるDBマイグレーションが実行されます。
つまり、何もなかったtodo
データベースにTasks
テーブルが作成されます。
==== apiコンテナ内 ====
npx sequelize-cli db:migrate
最後に、exit
でapiコンテナを抜けましょう。
==== apiコンテナ内 ====exit====ここまでapiコンテナ内 ====
Tasksテーブルが作成されたことを確認
todo
データベースにTasks
テーブルが作成されたことを確認しておきます。
以下のコマンドでdbコンテナのシェルを実行してください。
docker-compose exec db /bin/bash
先程と同様に、MySQLにログイン後、todo
データベースに切り替え、テーブルを見てみます。
====ここからdbコンテナ内 ====
mysql -u root -p
mysql> use todo
mysql> show tables;
![Tasksテーブルの確認.png]()
結果、Tasks
テーブルが出来ていることが確認できました。なお、初回はマイグレーション管理用にSequelizeMeta
テーブルも作られます。
ではMySQLとdbコンテナを抜け、ホストに戻ります。
==== dbコンテナ内 ====
mysql> quit
exit====ここまでdbコンテナ内 ====
vueコンテナの準備
ここで、ユーザーからのリクエストを受け取るvueコンテナの準備をしていきます。以前も触れましたが、vueコンテナは起動時に実行されるはずのnpm run serve
コマンドが実行できなかったため、停止しています。(docker-compose ps
でもう一度確認してみましょう。)
ここでは、Vue CLIをインストールし、npm run serve
コマンドを使えるようにしていきます。
vueコンテナは停止しているため、docker-compose exec
は使えません。ここでは、docker-compose run
を使ってvueコンテナを一時的に起動します。
docker-compose run --rm--no-deps vue /bin/bash
例によってnpmのバージョンをアップデートしておきます。
==== vueコンテナ内 ====# npmのバージョンを確認
npm -v
6.14.11
# npmのアップデート
npm install-g npm
# npmがアップデートされたことを確認
npm -v
7.6.1
npmのアップデート後、Vue CLIをインストールします。
==== vueコンテナ内 ====
npm install-g @vue/cli
インストール完了後、現在のディレクトリ.
を対象にvue create
を行います。
==== vueコンテナ内 ====
vue create .
以下の画像を参考に進めていきます。
![vue create①.png]()
↑デフォルトで参照しているレジストリでは接続が遅く、「こっち(https://registry.npm.taobao.org
)の方が早いからこっちを使おうよ!」と聞いているようですが、よくわからないのでNo(n
)を選択しました。ちなみにtaobaoというのは中国のサーバーらしいです。
![vue create②.png]()
↑カレントディレクトリに作っていいのか聞いています。Yes(Y
)で大丈夫です。
![vue create③.png]()
↑プリセットを選択します。Default (Vue 3 Preview) ([Vue 3] babel, eslint)
を選びました。
![vue create④.png]()
↑パッケージマネージャーを選択します。NPM
を選びました。
その後、待っていればvue create
が終わります。(私の環境だと結構時間がかかりました…)
次に、apiコンテナ(APIサーバー)からデータの取得や更新を行うために、axiosをインストールします。
==== vueコンテナ内 ====
npm install axios
インストールが完了したら、exit
でvueコンテナを抜けます。
==== vueコンテナ内 ====exit====ここまでvueコンテナ内 ====
そして、docker-compose down
で全てのコンテナを停止&削除した後、docker-compose up -d
で再び起動し直します。
docker-compose down
docker-compose up -d
ここで、docker-compose ps
で各コンテナのステータスを確認してみると、今度はvueコンテナも稼働状態になっていることがわかります。Vue CLiをインストールしたことで、npm run serve
コマンドが実行できるようになりました。
![vueコンテナもUpになっている.png]()
http://localhost:8080/に接続し、Vue.jsの初期画面が表示されることを確認します。
![Vue.jsの初期画面.png]()
APIの実装
ここからは、apiコンテナ内のプログラムを編集し、ToDoリストアプリのCRUDのAPIを実装していきます。
まず、todo_app/api/routes/index.js
に2行追加します。
/task
ルートの処理をtaskRoutes.js
に投げるようにします。
todo_app/api/routes/index.js
varexpress=require('express');varrouter=express.Router();//この2行を追加するconsttaskRoutes=require("./taskRoutes");router.use("/task",taskRoutes);/* GET home page. */router.get('/',function(req,res,next){res.render('index',{title:'Express'});});module.exports=router;
次に、todo_app/api/routes/
に、ファイルtaskRoutes.js
を作成して、以下のように編集します。
todo_app/api/routes/taskRoutes.js
"use strict";constrouter=require("express").Router(),taskController=require("../controllers/taskController");router.get("/",taskController.read);router.post("/",taskController.create);router.put("/:id",taskController.update);router.delete("/:id",taskController.delete);module.exports=router;
ここでは、4つのhttpメソッド(get、post、put、delete)毎に違う処理を割り当てています。
処理の内容は、todo_app/api/controllers/taskController.js
内に記述していきます。
todo_app/api/
に、controllers
という名前のディレクトリを作成し、その中にtaskController.js
を作成します。
todo_app/api/controllers/taskController.js
は、以下のように編集します。
todo_app/api/controllers/taskController.js
"use strict";constdb=require("../models/index");module.exports={read:async(req,res,next)=>{try{constresult=awaitdb.Task.findAll();res.send(result);}catch(err){res.status(500).send(err);}},create:async(req,res,next)=>{try{constresult=awaitdb.Task.create({name:req.body.name,done:false});res.send(result);}catch(err){res.status(500).send(err);}},update:async(req,res,next)=>{try{constresult=awaitdb.Task.update({name:req.body.name,done:req.body.done},{where:{id:req.params.id}});res.send(result);}catch(err){res.status(500).send(err);}},delete:async(req,res,next)=>{try{constresult=awaitdb.Task.destroy({where:{id:req.params.id}});res.send({result:result});}catch(err){res.status(500).send(err);}}}
これで、APIコンテナの実装は完了です。
Vue.jsの実装
最後に、フロントエンドの実装を行います。
デフォルトで存在するtodo_app/vue/src/components/HelloWorld.vue
というコンポーネントを以下のように書き換えます。
todo_app/vue/src/components/HelloWorld.vue
<template><divclass="hello"><form><inputtype="text"style="display: none"/><inputv-model="currentTask"type="text"/><inputtype="button"value="add!"@click="taskCreate"/></form><tablealign="center"border="0"><tr><th>done</th><th>task</th><th>delete</th></tr><trv-for="(task, index) in tasks":key="task.id"><td><inputtype="checkbox"v-model="task.done"@change="taskUpdate(task.id, task.name, task.done)"/></td><td><inputtype="text"v-model="task.name"@change="taskUpdate(task.id, task.name, task.done)"/></td><td><inputtype="button"value="delete"@click="taskDelete(task.id, index)"/></td></tr></table></div></template><script>importaxiosfrom"axios";exportdefault{name:"HelloWorld",data:()=>({tasks:[],currentTask:"",}),created:asyncfunction(){try{constresult=awaitaxios.get("/task/");this.tasks=result.data;}catch(err){alert(JSON.stringify(err));}},methods:{taskCreate:asyncfunction(){try{constresult=awaitaxios.post("/task/",{name:this.currentTask,});this.tasks.push(result.data);this.currentTask="";}catch(err){alert(JSON.stringify(err));}},taskDelete:asyncfunction(id,index){try{awaitaxios.delete("/task/"+id);this.currentTask="";this.tasks.splice(index,1);}catch(err){alert(JSON.stringify(err));}},taskUpdate:asyncfunction(id,val,done){try{awaitaxios.put("/task/"+id,{name:val,done:done,});this.currentTask="";}catch(err){alert(JSON.stringify(err));}},},};</script><!-- Add "scoped" attribute to limit CSS to this component only --><stylescoped>h3{margin:40px00;}ul{list-style-type:none;padding:0;}li{margin:010px;}a{color:#42b983;}.table{height:100%;text-align:center;}</style>
次に、todo_app/vue/
にファイルvue.config.js
を作成し、以下のように編集します。
参考:Vue.jsとAPIサーバとのaxiosでCORSに引っかかった時のProxyを使った回避方法 - Qiita
todo_app/vue/vue.config.js
module.exports={devServer:{proxy:'http://api:3000'}};
編集し終わったら、ここで各コンテナを再起動しましょう。
docker-compose down
docker-compose up -d
これで、ToDoリストアプリが完成しているはずです。http://localhost:8080/にアクセスして操作してみましょう。
![ToDoリストアプリの完成形.png]()
出来ました!
参考書籍
主な参考サイト