概要
この記事は「【マイスター・ギルド】本物のAdvent Calendar 2022」9日目の記事です。
まぁかくかくしかじかありまして、JavaScriptの練習としてAPIを叩いて返ってきた値(JSON)を画面に表示してみよう、そのための環境を作ろうってなったんです。
JavaScriptの練習だからフロントはもちろん使うけど、API側もNodeで書けばより理解も深まるだろうということでExpressなるフレームワークがあると知ってそれを使って環境構築をしてみました。
最近nginxの勉強もしたのでプロキシ設定を使った構成の練習台にもちょうどよかったです。
環境を作ろう
準備
こんな感じのディレクトリ構成にします。今回はデスクトップに作成しました。
js-practice
├─ backend
├─ docker
│ ├─ nginx
│ └─ node
└─ frontend
nginxの設定
nginxで設定するところは静的ファイルの参照場所とリバースプロキシです。
まずはこの記事を参考にnginxのdefault.conf
ファイルを取り出します(実は自分の記事)。
以下コマンドでnginxのコンテナを立ち上げてコンテナ内に入ります。
docker run --rm -v /$PWD/docker/nginx:/tmp -it nginx bin/bash
マウントした/tmp
にdefault.conf
ファイルをコピーしてホスト側に取り出します。
cp /etc/nginx/conf.d/default.conf /tmp/
exit
これでdocker/nginx
配下にdefault.conf
が入りました。
で、中身を以下のように書き換えます。
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html/public;
index index.html index.htm;
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
location /api/ {
proxy_pass http://node:9000;
}
}
root /usr/share/nginx/html/public;
の部分がデフォルトで参照されるディレクトリですね。
proxy_pass http://node:9000;
の部分がプロキシの設定です。
またプロキシする場合、バックエンドへのリクエストはnginxから行われることになるためブラウザのリクエストに含まれるヘッダー情報は失われてしまいます。
ブラウザからのヘッダー情報をバックエンドでも参照する場合はproxy_set_header
を使って設定します。
今回は特に設定しませんがブラウザからのヘッダー情報を使用する場合は気をつけてください。
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
Expressの設定
ExpressではHello,Express!
をJSON形式で返すAPIを作ってみます。
ちなみにExpressのことはよく知りません😊
まずはbackendディレクトリに移動してpackage.json
を用意してからExpressをインストールします。
cd backend
npm init -y
npm install express
インストールできたらapp.js
というファイルを作成してAPIを作ってみます。
const express = require('express');
const app = express();
const port = 9000;
app.get('/api/v1/', (req, res) => {
setTimeout(() => {
res.json({
message: 'Hello,Express!'
});
}, 500);
});
app.listen(port, () => {
console.log(`listening on port: ${port}`);
});
Expressの公式を少しいじって500ms後にJSON形式で{message: Hello,Express!}
を返します。
(setTimeout
関数を使っているのはAPIらしくレスポンスが遅れてくるようにしてみただけで必要な処理ではありません)
ここで一つ問題があって今後app.js
を書き換えるたびにサーバー再起動が必要になります。
サーバー再起動するためにnodeのプロセスをkillしたいのですがnodeコンテナはこれが一筋縄でいかず結局Dockerコンテナの再起動をするしかないようです。
これでは不便なのでapp.js
に変更があればそれを検知して再起動してくれるnodemon
を入れます。
npm install -D nodemon
mainで読み込むファイルはapp.js
なので
nodemon
を動かすスクリプトと一緒にpackage.json
を以下のように変更します。
___略___
"main": "app.js",
"scripts": {
"start": "nodemon /app"
},
___略___
Dockerの設定
Dockerの設定ではnginxのWebサーバーとnodeのAPIサーバーをそれぞれ立てます。
まずはそれぞれのDockerfile
を作成します。
FROM nginx:latest
FROM node:16-alpine
WORKDIR /app
COPY ./backend ./
EXPOSE 9000
CMD ["npm", "start"]
nginxはDockerfile作らなくてもdocker-compose.yml
に収まるんですが、個人的な好みでDockerfile
に書き出してます。
次にroot配下にdocker-compose.yml
をつらつらと書きます。
version: '3.9'
services:
nginx:
container_name: nginx
build:
context: .
dockerfile: ./docker/nginx/Dockerfile
ports:
- 80:80
volumes:
- ./frontend/public:/usr/share/nginx/html/public
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
node:
container_name: node
build:
context: .
dockerfile: ./docker/node/Dockerfile
ports:
- 9000:9000
volumes:
- ./backend:/app
tty: true
フロントエンドの設定
フロントエンドと言いつつそれほど大層なものは作らずとりあえずindex.html
を準備します。
frontend/public
ディレクトリの中にindex.html
を作成します。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<h1>Hello,World!</h1>
</body>
</html>
ディレクトリ構成確認
ここまでの環境構築でこんな形になってればOKです。
js-practice
├─ backend
│ ├─ node_modules
│ ├─ app.js
│ ├─ package-lock.json
│ └─ package.json
├─ docker
│ ├─ nginx
│ │ ├─ default.conf
│ │ └─ Dockerfile
│ └─ node
│ └─ Dockerfile
├─ frontend
│ └─ public
│ └─ index.html
└─ docker-compose.yml
動かしてみよう
では準備ができたので、動かしてみましょう。
js-practice
ディレクトリ直下に移動してから満を持してdocker-compose up
。
docker-compose up
静的ファイルの確認
http://localhostにアクセスしてみて下記画面ならOK、nginxに静的ファイルが正しく配置されています。
APIの確認
http:localhost:9000/api/v1/にアクセスしてみて下記画面ならOK、ExpressのAPIサーバーが正しく立ち上がっています。
リバースプロキシの確認
http:localhost/api/v1/にアクセスしてみて下記画面ならOK、nginxの設定により/api/
のリクエストがAPIサーバーへ正しくプロキシしています。
APIの結果を取得しよう
APIを叩く準備
フロントエンドからAPIをコールして結果を表示させてみます。
まずはaxios
を入れたいのでフロント側でもpackage.json
を作ります。
cd frontend
npm init -y
npm install axios
あとaxios
をimportして使うのにwebpackに代わってlaravel-mixを使います。
npm install -D laravel-mix
frontend
ディレクトリ直下にwebpack.mix.js
を作成して以下のようにします。
const mix = require('laravel-mix');
mix.disableNotifications(); // windows環境だと通知が邪魔なので通知OFF
mix.js('src/js/main.js', 'public/js/main.js');
src/js/main.js
をコンパイルしてpublic/js/main.js
に出力しなさいということです。
(src/js/main.js
は後で作ります)
フロント側のpackage.json
のscript
も編集します。
___略___
"scripts": {
"build": "mix",
"watch": "mix watch"
},
___略___
これでひとまず準備はOK。
ちなみにlaravel-mix
の使い方はこの記事でも書いたので参考までに(また自分の記事)。
APIを叩いてみる
ボタンを押したらgetリクエストのAPIを叩いて、レスポンスを表示するということをやってみます。
レスポンスはbackend/app.js
で定義したJSONですね。
{
message: "Hello,Express!"
}
index.html
を更新してsrc/js/main.js
をfrontend
ディレクトリ配下に新たに作成します。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<h1 id="message">Hello,World!</h1>
<button id="btn">Message取得</button>
<script src="./js/main.js"></script>
</body>
</html>
import axios from "axios";
const msg = document.getElementById('message');
const btn = document.getElementById('btn');
const getMessage = async () => {
const response = await axios.get('/api/v1/');
return response.data.message;
}
btn.addEventListener('click', async () => {
await getMessage()
.then(res => msg.textContent = res)
.catch(err => console.log(err));
});
そしてfrontend/src/js/main.js
をコンパイルします。
npm run watch
http://localhostはこんな画面になりました。
レスポンスを受け取ってその内容に切り替わりました〜。
まとめ
ちょっとしたきっかけがあって、この環境を作ることになりましたがdocker内のnginxのリバプロの設定でhttp://localhost:9000
ではうまくいかないことやnodemon
やexpress
といったツールやフレームワークを知ることができました。(host側のlocalhostとdocker側のlocalhostは違いますよということらしいです)
location /api/ {
# OK
proxy_pass http://node:9000;
# NG
# proxy_pass http://localhost:9000;
}
小さな学びがいくつかあったのでよかったです。
これを作った後にフロントをReactに変えてみたりもしました。
環境構築もこなれてきた感があります(嘘)。
今回はシンプルなリクエストを送ってレスポンスを受け取るだけですが、DBを追加してみたりすればよりいい感じのアプリが作れるようになると思います。
フロントとバックのJavaScriptを勉強するための簡易な環境構築でした。
最後にディレクトリ構成はこんな感じになります。
js-practice
├─ backend
│ ├─ node_modules
│ ├─ app.js
│ ├─ package-lock.json
│ └─ package.json
├─ docker
│ ├─ nginx
│ │ ├─ default.conf
│ │ └─ Dockerfile
│ └─ node
│ └─ Dockerfile
├─ frontend
│ ├─ node_modules
│ ├─ public
│ │ ├─ js/main.js
│ │ └─ index.html
│ ├─ src
│ │ └─ js/main.js
│ ├─ mix-manifest.json
│ ├─ package-lock.json
│ ├─ package.json
│ └─ webpack.mix.js
└─ docker-compose.yml