Node.jsの基本その３



#ファイルへのデータの書き出し
たとえばウェブアプリケーションがログを取る必要などがある場合、最も簡素な方法はテキストファイルに書き出すことです。Node.jsにはじめから組み込まれているfsというファイルシステムモジュールはとても使いやすいです。

```javascript:writeFile.js
const fs = require('fs')

const fileName = 'file.txt'
//check whether file exists or not
if(fs.existsSync(fileName)){
  const data = new Date().toString() + " log added\n"
  //fs.appendFile adds new text asynchronously
  fs.appendFile(fileName, data, (err) => {
    if(err){
      console.log(err)
    } else {
      console.log("write successful")
    }
  })
} else {
  const data = new Date().toString() + " the first log\n"
  //fs.writeFile writes data and also this creates file if it doesn't exist
  fs.writeFile(fileName, data, (err) => {
    if(err){
      console.log(err)
    } else {
      console.log("file create successful")
    }
  })
}
```

このコードを走らせるとfile.txtファイルが作成されそのなかにログが記入されたのが確認されます。そして再度走らせるたびに新しいログが追記されます。

##ファイルへのデータの読み取り

今度はファイルを読み込んでみましょう。この場合fs.readFileを用います。

```javascript
  const fileName = 'file.txt'
  fs.readFile(fileName, 'utf-8', (err, data) => {
    if(err){
      console.log(err)
    } else {
      console.log(data)
    }
  })
```

###応用編　～morganでログをつけましょう～

開発においても、運用においてもログをつけることは煩瑣でありながら何よりも大事な作業です。morganはログをつけるための非常に便利なミドルウェアで、どのようなウェブアプリでも欠かせません。この応用ではmorganを使ってコンソールとログファイルの両方にログをつけていきましょう。

まずmorganをインストールして、ディレクトリとログファイルを作成しましょう。

```console
npm install morgan
mkdir logs
cd logs
type server.log > server.log
```

次にログをつけるコードを書いていきましょう。

```javascript:server.js
const fs = require('fs')
const morgan = require('morgan')
const path = require('path')
const express = require('express')

const server = express()
const port = 3000
const filePath = path.join(__dirname, 'logs', 'server.log')

// morganはログの種類の定義を求めてきます。
// ここでは一般的なログをファイルに記入して
// 簡素な開発用ログをコマンドラインに書きましょう
server.use(morgan('common', {
  stream: fs.createWriteStream(filePath, {flags:"a"})
}))
server.use(morgan('dev'))

server.get('/', (req, res) => {
  res.send('Hello World!')
})

server.listen(port, () => {
  console.log('server listening on:' + port)
})
```
これでローカルホスト3000にgetリクエストがあるたびにその記録が記入されるようになりました。

##データベースの利用
ウェブアプリケーションが日々追加、削除、更新される情報を大量に扱うならデータベースの導入は欠かせないでしょう。
###Mongo DB
データベースには色々ありますが、ここはJavaScriptと親和性が高いMongoDBを使っていきましょう。
まずはMongoDBのGUIとShellをインストールしましょう。

[MongoDB Compass](https://www.mongodb.com/try/download/compass "MongoDB Compass")

[MongoDB Shell](https://www.mongodb.com/try/download/shell "MongoDB Shell")

###ローカル環境でのDBの作成
MongoDB Shellでデータベースとコレクションを作成します

```shell:mongosh
use testDB
db.createCollection("users")
```

無事作成されたか確認してみましょう
MongoDB Compassで

```http
mongodb://127.0.0.1:27017/testDB
```

usersという空のコレクションが確認できれば良いです。
次にmongoose というMongoDBを操作するためのモジュールをインストールしましょう

###mongoose
```console
npm install mongoose
```
次にコードを書いていきましょう。コード内のコメントでmongooseの使い方を説明します。
コードとしてjadeを用いたフォームから受け取った値でドキュメントを作成するものです。

```javascript:server.js
const express = require('express')
const mongoose = require('mongoose')
const bodyParser = require('body-parser')

const server = express()
const port = 3000
// データベースのパス
const dbPath = "mongodb://127.0.0.1:27017/testDB"
// 接続する関数（非同期）
mongoose.connect(dbPath, (err) => {
  if(!err){
    console.log("connected to database")
  } else {
    throw err
  }
})
// スキーマの宣言
// スキーマとは厳密なデータ定義がないMongoDBのデータを検索したり作成するための図面
const Schema = mongoose.Schema
// スキーマの定義はプロパティの名前とデータ型で行います
// また値が有効かどうかの検証を行うひつようがあり
// そのための関数をスキーマ内で定義しておきましょう
const validateLength = value => {
  return value && value.length
}
const validateNumber = value => {
  return value && parseInt(value)
}
const userSchema = new Schema({
  firstName:{
    type:String,
    validate:[validateLength, 'first name is required']
  },
  lastName:{
    type:String,
    validate:[validateLength, 'last name is required']
  },
  age:{
    type:Number,
    validate:[validateNumber, 'Not a valid number']
  },
})
// スキーマからモデルを定義します。
// モデルから検索や新しいドキュメントの作成などができる。
const userModel = mongoose.model('users', userSchema)

server.set('view engine', 'jade')
server.use(bodyParser.urlencoded({extended:true}))
server.use(bodyParser.json())

server.get('/', (req, res) => {
  res.render('index')
})
server.post('/addUser', (req, res) => {
  // ドキュメントの作成
  userModel.create(req.body.user).then(result => {
    res.render('postResult', {message:'post successful'})
  }).catch(reason => {
    if(reason){
      res.render('postResult', {message:'post failed'})
    }
  })
})

server.listen(port, () => {
  console.log('server listening on:' + port)
})
```

フォーム送信用のjadeファイル

```:index.jade
doctype html
html
  head
    title Jade Example
  body
    h1 Add User
    form(method='post', action='/addUser')
      fieldset
        legend add user
        label first name
        input(type="text" name='user[firstName]')
        label last name
        input(type='text' name='user[lastName]')
        label age
        input(type='number' name='user[age]')
        input(type='submit' value='Save')
```

作成の成否確認のためのjadeファイル

```:postResult.jade
doctype html
html
  head
    title #{message}
  body
    h1 #{message}
```

###ドキュメントの変更と削除
ついでにドキュメントの変更と削除もやってみましょう。まずはindex.jadeファイルを変更しましょう。

```:index.jade
doctype html
html
  head
    title Jade Example
  body
    h1 Add User
    form(method='post', action='/addUser')
      fieldset
        legend add user
        div
          label first name 
          input(type="text" name='user[firstName]')
        div
          label last name 
          input(type='text' name='user[lastName]')
        div
          label age 
          input(type='number' name='user[age]')
        div
          input(type='submit' value='Save')
    h1 Users
    - if(docs.length)
      ol
        each user in docs
          li #{user.firstName} #{user.lastName} #{user.age} 
          form(method='post' action="/deleteUser/#{user._id}?_method=DELETE")
            input(name='_method' value='DELETE' type="hidden")
            input(type='submit' value="delete")
          a(href="/editUser/#{user._id}") edit
```

次に新しく変更用の入力画面としてeditUser.jadeファイルを作成していきます。

```:editUser.jade
doctype html
html
  head
    title Jade Example
  body
    h1 Add User
    form(method='post', action='/addUser')
      fieldset
        legend add user
        div
          label first name 
          input(type="text" name='user[firstName]')
        div
          label last name 
          input(type='text' name='user[lastName]')
        div
          label age 
          input(type='number' name='user[age]')
        div
          input(type='submit' value='Save')
    h1 Users
    - if(docs.length)
      ol
        each user in docs
          li #{user.firstName} #{user.lastName} #{user.age} 
          form(method='post' action="/deleteUser/#{user._id}?_method=DELETE")
            input(name='_method' value='DELETE' type="hidden")
            input(type='submit' value="delete")
          a(href="/editUser/#{user._id}") edit
```

そしてserver.jsに変更を加えましょう。

```javascript:server.js
const express = require('express')
const mongoose = require('mongoose')
const bodyParser = require('body-parser')

const server = express()
const port = 3000
const dbPath = "mongodb://127.0.0.1:27017/testDB"
mongoose.connect(dbPath, (err) => {
  if(!err){
    console.log("connected to database")
  } else {
    throw err
  }
})
const Schema = mongoose.Schema
const validateLength = value => {
  return value && value.length
}
const validateNumber = value => {
  return value && parseInt(value)
}
const userSchema = new Schema({
  firstName:{
    type:String,
    validate:[validateLength, 'first name is required']
  },
  lastName:{
    type:String,
    validate:[validateLength, 'last name is required']
  },
  age:{
    type:Number,
    validate:[validateNumber, 'Not a valid number']
  },
})
const userModel = mongoose.model('users', userSchema)

server.set('view engine', 'jade')
server.use(bodyParser.urlencoded({extended:true}))
server.use(bodyParser.json())

server.get('/', async (req, res) => {
  await userModel.find().exec().then(docs => {
    res.render('index', {docs:docs})
  })
})

server.post('/addUser', (req, res) => {
  userModel.create(req.body.user).then(result => {
    res.redirect('/')
  }).catch(reason => {
    if(reason){
      res.render('postResult', {message:reason})
    }
  })
})
// editUser.jadeへユーザー情報を送ります
server.get('/editUser/:id', (req, res) => {
  // findByIdはドキュメントをユニークIDで探します
  userModel.findById(req.params.id).exec().then(doc => {
    res.render('editUser', {user:doc})
  }).catch(reason => {
    if(reason){
      res.render('postResult', {message:'search failed'})
    }
  })
})
// 実際のドキュメント変更
server.post('/editUser/:id', (req, res) => {
  console.log(req.body)
// findByIdAndUpdateはユニークIDでドキュメントを発見し渡された値に更新します
  userModel.findByIdAndUpdate(req.params.id, req.body.user).then(
    res.redirect('/')
  ).catch(reason => {
    if(reason){
      res.render('postResult', {message:reason})
    }
  })
})
// ドキュメント削除用
server.post('/deleteUser/:id', (req, res) => {
  // findByIdAndDelete はユニークIDでドキュメントを探しそれを削除します
  userModel.findByIdAndDelete(req.params.id).then(
    res.redirect('/')
  ).catch(reason => {
    if(reason){
      res.render('postResult', {message:'post failed'})
    }
  })
})

server.listen(port, () => {
  console.log('server listening on:' + port)
})

```

###環境変数の設定と参照
最後に環境変数についてです。
たとえばウェブアプリが外部APIを用いる場合APIキーが必要になりますし、mongoDBをクラウドで扱う場合などではユーザー名とパスワードをパスに用いなければならず、それらを主要なプログラムにハードコードするのはとても危険です。
ぜひとも環境変数に組み込みましょう。

```console
SET MONGODB_USERNAME [YOUR_USER_NAME]
```
一度設定した環境変数は以下の記法で参照できます。

```javascript
const mongoUserName = process.env.MONGODB_USERNAME
```

##次回へ

その４へ続きます。
次はテストについてです。