※当方駆け出しエンジニアのため、間違っていることも多々あると思いますので、ご了承ください。また、間違いに気付いた方はご一報いただけると幸いです。
##Node.js Express MongoDBを用いたアプリ開発
受託開発でNode.js Express MongoDBを用いたアプリ開発を行うことになりました。
開発の中で得られた知見を、支障のない範囲で記録していきたいと思います。
アプリの内容はさておき、チームで開発するため、まずはDockerを用いて開発環境を構築を行います。
###構築する環境
- docker-composeを用いて、アプリコンテナ、mongodbコンテナを作成する。
- アプリコンテナとmongodbコンテナの接続テスト(mongoose)を用いて、テストモデルにデータ保存。
- mongo compassからデータの挿入を確認
- mongodbに認証を設ける。作成するユーザーの権限は"root","read","owner"
- データベースのパスワード等は環境変数で管理(gitignoreにてgitの管理から外す。)
※注意点
<アプリ名> <パスワード>となっているところは、各自で適当なものを記入してください。
そのまま記載するとエラーとなります。
今回は、パスワードは全て共通としています。
###初期インストールするパッケージ一覧
"bcrypt"
"body-parser"
"connect-flash"
"cookie-parser"
"debug"
"ejs"
"express"
"express-ejs-layouts"
"express-generator"
"express-session"
"express-validator"
"http-errors"
"http-status-codes"
"method-override"
"mongoose"
"morgan"
"nodemon"
"passport"
"passport-local-mongoose"
#下準備
###最初に用意するファイル群
.
├── .env
├── .gitignore
├── data
│ └── db //空ディレクトリ
├── docker-compose.yml
├── docker_app
│ └── Dockerfile
├── secret_file
│ ├── db.env
│ └── db_init
│ └──mongo_init_user.js
└── src
├── controllers
│ └── initTestsController.js
├── models
│ └── init_test.js
└── package.json
MONGO_INITDB_ROOT_USERNAME=root
MONGO_INITDB_ROOT_PASSWORD=<パスワード>
MONGO_INITDB_DATABASE=<DB名>
node_modules/
data/
.env
secret_file/
version: '3'
services:
app:
build: ./docker_app
container_name: <アプリ名>_app_cnt
ports:
- "8080:3000"
restart: always
working_dir: /app
tty: true
volumes:
- /etc/passwd:/etc/passwd:ro
- /etc/group:/etc/group:ro
- ./src:/app
env_file:
- ./secret_file/db.env
command: bash
networks:
- <アプリ名>-network
depends_on:
- mongo
mongo:
image: mongo:latest
container_name: <アプリ名>_db_cnt
ports:
- "27018:27017"
restart: always
environment:
MONGO_INITDB_ROOT_USERNAME: ${MONGO_INITDB_ROOT_USERNAME}
MONGO_INITDB_ROOT_PASSWORD: ${MONGO_INITDB_ROOT_PASSWORD}
MONGO_INITDB_DATABASE: ${MONGO_INITDB_DATABASE}
volumes:
- ./data/db:/data/db
- ./secret_file/db_init/:/docker-entrypoint-initdb.d
env_file:
- ./secret_file/db.env
command:
- mongod
networks:
- <アプリ名>-network
networks:
<アプリ名>-network:
external: true
FROM node:12
WORKDIR /app
RUN npm install
DB_USER=owner
DB_PASS=<パスワード>
DB_NAME=<アプリ名>_db
let users = [
{
user: "read",
pwd: "<パスワード>",
roles: [
{
role: "read",
db: "<アプリ名>_db"
}
]
},
{
user: "owner",
pwd: "<パスワード>",
roles: [
{
role: "dbOwner",
db: "<アプリ名>_db"
}
]
},
{
user: "readWriteUser",
pwd: "<パスワード>",
roles: [
{
role: "readWrite",
db: "<アプリ名>_db"
}
]
}
];
for (let i = 0, length = users.length; i < length; ++i) {
db.createUser(users[i]);
}
"use strict";
const InitTest = require('../models/init_test');
const test = () => {
let initTest = new InitTest({
name: "Taro",
age: 20
})
initTest.save((error, data) => {
if (error) {
console.log(error);
}
console.log(data);
})
}
module.exports = { test };
"use strict";
const mongoose = require("mongoose");
const initTestSchema = new mongoose.Schema({
name: String,
age: Number
});
module.exports = mongoose.model("InitTest", initTestSchema);
{}
#環境作成
###コンテナ作成
docker-compose build
下記の様な警告がでるが問題ない。
npm WARN saveError ENOENT: no such file or directory, open '/app/package.json'
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN enoent ENOENT: no such file or directory, open '/app/package.json'
npm WARN app No description
npm WARN app No repository field.
npm WARN app No README data
npm WARN app No license field.
#コンテナを一時的に起動コンテナ内に入る(--rmで停止後削除する。コンテナ起動後、bashに入る)
docker-compose run --rm app /bin/bash
# express-generatorでアプリケーションのひな形を生成
npx express-generator --view=ejs
下記の用に即abortingで拒絶されたら再度実行
destination is not empty, continue? [y/N]
aborting
二回目は質問で待ってくれるので y でエンター
destination is not empty, continue? [y/N] y
下記の様な各種ファイルが作成される。
create : public/
create : public/javascripts/
create : public/images/
create : public/stylesheets/
create : public/stylesheets/style.css
create : routes/
create : routes/index.js
create : routes/users.js
create : views/
create : views/error.ejs
create : views/index.ejs
create : app.js
create : package.json
create : bin/
create : bin/www
package.jsonに下記の内容を上書き
{
"name": "uniq_app",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "nodemon ./bin/www"
},
"dependencies": {
"bcrypt": "^5.0.0",
"body-parser": "^1.19.0",
"connect-flash": "^0.1.1",
"cookie-parser": "~1.4.4",
"debug": "~2.6.9",
"ejs": "^3.1.5",
"express": "~4.16.1",
"express-ejs-layouts": "^2.5.0",
"express-generator": "^4.16.1",
"express-session": "^1.17.1",
"express-validator": "^6.7.0",
"http-errors": "~1.6.3",
"http-status-codes": "^2.1.4",
"method-override": "^3.0.0",
"mongoose": "^5.11.9",
"morgan": "~1.9.1",
"nodemon": "^2.0.6",
"passport": "^0.4.1",
"passport-local-mongoose": "^6.0.1"
}
}
app.jsに下記の内容を上書き
const createError = require('http-errors');
const express = require('express');
const path = require('path');
const cookieParser = require('cookie-parser');
const logger = require('morgan');
const mongoose = require("mongoose");
const indexRouter = require('./routes/index');
const usersRouter = require('./routes/users');
const initTestController = require('./controllers/initTestsController');
const app = express();
mongoose.connect(
`mongodb://${process.env.DB_USER}:${process.env.DB_PASS}@mongo:27017/<アプリ名>_db`,
{ userNewParser: true }
);
mongoose.set("useCreateIndex", true);
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.get('/initTest', initTestController.test);/* 初期テストルーティング */
app.use('/', indexRouter);
app.use('/users', usersRouter);
// catch 404 and forward to error handler
app.use(function (req, res, next) {
next(createError(404));
});
// error handler
app.use(function (err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
module.exports = app;
パッケージのインストール
npm install
一度コンテナを抜ける
#コンテナを抜ける(この仮コンテナは削除される)
exit
コンテナを再起動
docker-compose up
下記クリックで接続確認
http://localhost:8080/
###mongooseとの接続確認
下記クリックでデータが作成されるか確認
http://localhost:8080/initTest
下記の様なログが出力されれば成功(docker-compose upしたターミナルで)
uniq_app_cnt | _id: 5fec5a6213a2fd002d89acca,
uniq_app_cnt | name: 'initTestUser',
uniq_app_cnt | age: 20,
uniq_app_cnt | __v: 0
uniq_app_cnt | }
uniq_app_cnt | 【ログ】--接続成功--【ログ】
###mongodbの権限周り確認
ターミナルを別タブで開き、dbコンテナに入る
docker exec -it <アプリ名>_db_cnt bash
mongo
//mongodbに接続
show dbs
//何も表示されない。(認証でロックされていることを確認)
use admin
//adminデータベースへ接続
db.auth("root", "<パスワード>")
//1 と返れば認証成功
db.system.users.find().pretty()
//作成されたユーザー確認
以下の様な、データベースに対する各権限者が作成されていたらオッケー。
{
"_id" : "admin.root",
"roles" : [
{
"role" : "root",
"db" : "admin"
}
]
}
{
"_id" : "uniq_db.read",
"roles" : [
{
"role" : "read",
"db" : "<データベース名>_db"
}
]
}
{
"_id" : "uniq_db.owner",
"roles" : [
{
"role" : "dbOwner",
"db" : "<データベース名>_db"
}
]
}
{
"_id" : "uniq_db.readWriteUser",
"roles" : [
{
"role" : "readWrite",
"db" : "<データベース名>_db"
}
]
}
#mongo compassからの接続確認。