---
# System prepended metadata

title: Node.js
tags: [backend, web]

---

Node.js
===
###### tags: `web` `backend`

# Node.js
尚未筆記
1. ejs
2. body-parser
3. redirect
4. express + firebase
5. cookie + session
6. email csurf env

## 網站
Node.js : https://nodejs.org/en/
npm : https://www.npmjs.com/
npm 為 nodejs 的套件管理工具，安裝 nodejs 時預設安裝
## 常用指令
查看版本
```javascript=
node -v
npm -v
```
開始
```javascript=
npm init
```
```
package.json

{
  "name": "hbdoy",
  "version": "1.0.0",
  "description": "test",
  "main": "test.js",
  "scripts": {
    "gogo": "node test.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "hbdoy",
  "license": "ISC",
  "dependencies": {
    "jquery": "^3.2.1",
    "router": "^1.3.0"
  }
}
```
自訂 & 執行 script
```
"gogo": "node test.js"

npm run gogo
```
版本號
```
"router": "^1.3.0"

1: 主要版本號(重大更新)
3: 次要版本號(小功能更新)
0: bug更新

^: 只更新次要版本號以及bug，不貿然更新主要版本號
~: 只更新bug
```
執行 JS
```javascript=
node myJsCode.js
```
進入可以直接輸入js執行介面(類似chrome的console)
```javascript=
node
// 離開: ctrl+c 兩次
```
安裝其他 library
```javascript=
// 要 save 才會把使用的 library 名稱存到  package.json
npm install jquery --save

// save 是 node 應用程式上線時會用到的相依套件，若只是要在開發時用來測試的套件，則加上 -dev
npm install test --save-dev

// C:/user/[使用者名稱]/AppData/Roming/npm/node_modules
// 這不會寫入 package.json，而是本機上所有專案都可以使用
npm install test -g
```
解除安裝
```javascript=
npm uninstall jquery [-g]
```
顯示安裝的套件
```javascript=
npm list
```
查看有沒有過期的 library
```javascript=
npm outdated
```
把使用到的library裝回來（讀取於 package.json 中的 dependencies）
```javascript=
npm install
```
### 模組化之間的 data 傳遞
main.js
```javascript=
var content = require('./child');
var a = 1;
console.log(a);
// 如果是物件的話就 content.b就好
console.log(content); // 2
```
child.js
```javascript=
// 商業邏輯可以寫在這裡,只傳出運算後的值
var b = 2;

// 也可以傳出物件
module.exports = b;
```

### Debug
node 內建 debug 模式，能夠逐步執行，也可以跳到中斷點(debugger)。

test.js
```javascript=
var a = 1;
var b = 2;
debugger;
var c = 3;
debugger;
```

在終端機輸入
```javascript=
node debug test.js
```
就會進入 debug 模式，並停在第一行，如果要執行下一行就打 ``n`` (next)，
如果要跳到最近的 debugger 點就打 ``c`` (cont)，
當想要輸入一些指令時(ex: console)，就打 ``repl``，就會進入類似 chrome 中 console 的模式。
#### 結合 chrome debug
若是想更圖形化、直覺一點的 debug，可以在終端機輸入
```javascript=
node --inspect --debug-brk test.js
```
就會得到一串指令，只要把這段指令丟到瀏覽器執行，就會自動開啟該檔案的 debug 工具。

## Other Library
### browser & server
browser | request（ex：get）=> <=　response（ex：html） | webserver（ex：apache）

瀏覽器發出 request ，需要有 webserver（httpserver） 才能夠攔截到，並做出回應。
還有若是請求一個php http://hello/index.php
server 端無果沒有安裝 php 的模組則會回傳 index.php 的原始碼回來

Nodejs 不需要有 webserver 就能夠對 request 做出回應

### Node.js HTTP Server
server 啟動後，訪問網址時，NodeJs 就能夠做出回應。
```javascript=
var http = require('http');
var hostname = '127.0.0.1';
var port = 7774;
http.createServer(function(request, response){
    response.writeHead(200);
    response.write('Hello World');
    response.end();
}).listen(port, hostname);
```
### HTTP Server & File System
```javascript=
var http = require('http');
var port = 7774;
var hostname = '127.0.0.1';
var fs = require('fs');
http.createServer(function (request, response){
    response.writeHead(200, {'Content-Type': 'text/html'});
    fs.readFile('index.html', function(error, content){
        response.write(content);
        response.write('Hello World');
        response.end();
    });
}).listen(port, hostname);

```
### request library
讓 NodeJS 能夠發出一個 request

```
npm install request --save
```
```javascript=
var request = require('request');
request.get(
    {
        url:'https://bot.moli.rocks/ncnu-staff-contact/俞',
    }, function(err, httpResponse, body){
        console.log(`err: ${err}`);
        console.log(`httpRes: ${httpResponse}`);
        console.log(`body: ${body}`);
    });
```
#### request用於表單
流程為 user 訪問網址 > NodeJS 發出一個 request 到指定網址，然後再把得到的 response 渲染到 browser。

```javascript=
var http = require('http');
var port = 7774;
var hostname = '127.0.0.1';
var request = require('request');
http.createServer(function(req, res){
    res.writeHead(200, {'Content-Type': 'text/html'});
    request.post('http://ycchen.im.ncnu.edu.tw/join.php', {
    form:{
        name: 'LeeRay',
        sex: 'male',
        isHandsome: 'Yes'
    }}, function(err, httpResponse, body){
        res.write(body);
        res.end();
    });
}).listen(port, hostname);
```
### express
一個方便做路由的 web 框架
```
npm install express --save
```

``helloWorld.js`` :
```javascript=
var express = require('express');
var app = express();
var app2 = express();

app.get('/', function(req, res){
    res.send('Hello Get!');
});
app.get('/users', function(req, res){
    res.send('Hello users!');
});
app.post('/', function(req, res){
    res.send('Hello Post!');
});

app2.get('/', function(req, res){
    res.send('Hello 3001_Get!');
});

app.listen(3000, function(){
    console.log('http://localhost:3000');
});
app2.listen(3001, function(){
    console.log('http://localhost:3001');
});
```
run
```
node helloWorld.js
```
``> localhost:3000/users``

#### Router
透過 module 做大分類的路由，此例中只要是到 ``/test`` 下，就是交由 ``users.js`` 做進一步的路由。

像是學校網站可能會在大分類有 ``/teacher`` 和 ``/student``，然後再轉交給各自的路由進行下一步的動作。

``module_test.js`` :
```javascript=
var express =  require('express');
var users = require('./users');

var app = express();
// xxx.use 是在指定 URI 時，把路由交給指定 js 做近一步功能
app.use('/test', users);

app.listen(3001, function(){
    console.log('http://localhost:3001');
});
```
``users.js`` :
```javascript=
var express =  require('express');
var router = express.Router();

// xxx.get 是對指定 method、URL 做動作
router.get('/', function(req, res){
    res.send('Get 1,2');
});
router.get('/1', function(req, res){
    res.send('Get 1');
});
router.get('/2', function(req, res){
    res.send('Get 2');
});

module.exports = router;
```
run :
```
node module_test.js
```
```
> localhost:3001/test
> localhost:3001/test/1
> localhost:3001/test/2
```

#### middleware
1. 可以用來驗證程式是否要繼續往下執行
```javascript=
var express = require('express');
var app = express();

app.get('/',function(req, res){
    res.send('<html><head></head><body><h1>Hello World</h1></body></html>')
})

// /user 被訪問時，會先判斷 next 是否執行，才會繼續往下走，否則會卡在 console 那一行
app.use(function(req, res, next){
    console.log('有人進來了');
    next();
})

app.get('/user',function(req, res){
    res.send('<html><head></head><body><h1>我是認證過的user</h1></body></html>')
})

// 監聽 port
var port = process.env.PORT || 3000;
app.listen(port);
```
也可以寫成
```javascript=
var login = function(req, res, next){
    console.log('有人進來了');
    next();
};
app.use(login);
```

如果想要針對某個路由設定驗證就好，可以這樣寫
```javascript=
var login = function(req, res, next){
    console.log('有人進來了');
    next();
};
app.get('/', login, function(req, res){
    res.send('<html><head></head><body><h1>Hello World</h1></body></html>')
})
```

2. 404頁面、500頁面
:::info
兩段程式碼都是放在最下面，當所有路由規則都不符合就會顯示404。
而多了一個 err 參數的，則是當程式發生錯誤(ex: 執行未定義函數)，就會執行。
:::
```javascript=

app.use(function(req, res, next){
    res.status(404).send("抱歉，您的頁面找不到");
})

app.use(function(err, req, res, next){
    res.status(500).send("程式發生異常錯誤，請稍後再試");
})
```

#### 載入靜態檔案
如果在 router 中要使用靜態檔案，像是圖片，需要先指定所有存放靜態檔案的位置，否則會抓取不到。

``public/images``
```javascript=
var express = require('express');
var app = express();
//增加靜態檔案的路徑
app.use(express.static('public'))

app.get('/',function(req,res){
    res.send('<html><head></head><body><img src="/images/logo.png"</body></html>')
})

// 監聽 port
var port = process.env.PORT || 3000;
app.listen(port);
```


### express-generator
express-generator 可快速建構一個模板，類似於 MVC（model、view、control）架構，不用再手動建立。
```
npm install express-generator -g
```

`` express -h`` 可以查看各參數用法

快速產生
```
express name
cd name
npm install
npm start

或是

mkdir name
cd name
express -f
npm install
npm start

> localhost:3000
```

#### 從網址列中取值
``name/routes/index.js``
```javascript=
var express = require('express');
var router = express.Router();

/* GET home page. */
router.get('/', function(req, res, next) {
  res.render('index', { title: 'Express' });
});

/* /: 冒號後面表示可以接任意字串 */
router.get('/:name', function(req, res, next) {
  // 用 .params 取 URI 的值
  var myName = req.params.name;
  res.send('HaHaHa Tomato '　+ myName);
});

module.exports = router;
```

``res.render('index', { title: 'Express' })`` 中的 render 可以回傳 view 中的指定頁面，而 title 為變數名稱。

``> localhost:3000/leeray``

:::info
取路徑名稱、參數
```
app.get('/:name', function(req, res) {
  // www.xxx.com/Tom?limit=10

  // 用 params 取 URI 的值
  var myName = req.params.name; // Tom

  // 用 query 取參數
  var myQuery = req.query; // {limit: 10}
  var limit = req.query.limit; // 10
});
```
:::

#### 自訂
在上一個範例中，不管 ``localhost:3000/xxx`` 後面打的是什麼，都會顯示 ``HaHaHa Tomato xxx``，但是可以到 ``app.js`` 中新增路由規則，則此名稱將會是一個新的路由，而不會被視為 ``:name``。

到資料夾中找到 ``app.js``，所有初步規則都設定在這邊。
在裡面加上自訂義的新路由規則，記得新增的規則要放在 index 上面。
```javascript=
var classes = require('./routes/classes');

// 記得 /classes 要放在 / 上面，否則不管網址打什麼還是會被視為 index
app.use('/classes', classes);
app.use('/', index);
```

``name/routes/classes.js``
```javascript=
var express = require('express');
var router = express.Router();

/* GET users listing. */
router.get('/', function(req, res, next) {
  res.send('CLASSSSSSSSSSSSSSSES');
});

router.get('/:class_id', function(req, res, next) {
  var class_id = req.params.class_id;
  var myData = {
    name: 'LaaRay'
  };
  res.send('This is class '　+ class_id + JSON.stringify(myData));
});

// 再分一個路由下去
var eggs = require('./eggs/eggs.js');
router.use('/:class_id/eggs', eggs);

module.exports = router;
```
```
// CLASSSSSSSSSSSSSSSES
> localhost:3000/classes
> localhost:3000/classes/xxx
```

當然也可以再分下一層路由，也就是 ``localhost:3000/classes/xxx/eggs``

要注意的是因為 ``/:uid`` 有使用到上一層也就是 ``name/routes/classes.js`` 中的 ``class_id`` 變數，所以要設定 ``mergeParams: true``，讓 ``egg.js`` 能夠使用上層變數。

``name/routes/eggs/eggs.js``
```javascript=
var express = require('express');
// mergeParams: true 讓上層變數可以使用
var router = express.Router({mergeParams: true});

/* GET users listing. */
router.get('/', function(req, res, next) {
  res.send('Some eggs');
});

router.get('/:uid', function(req, res, next) {
  var class_id = req.params.class_id;
  var uid = req.params.uid;
  res.send("This is " + class_id + " has " + uid + ' eggs');
});

module.exports = router;
```
```
// Some eggs
> http://localhost:3000/classes/xxx/eggs/

// This is xxx has 123 eggs
> http://localhost:3000/classes/xxx/eggs/123
```

# Firebase
創建專案後，可以到 firebase 首頁點選「將 Firebase 加入您的網路應用程式」，得到下面啟用程式碼
```javascript=

<script src="https://www.gstatic.com/firebasejs/4.9.0/firebase.js"></script>
<script>
  // Initialize Firebase
  var config = {
    apiKey: "xxxx",
    authDomain: "xxxx.firebaseapp.com",
    databaseURL: "https://xxxx.firebaseio.com",
    projectId: "xxxx",
    storageBucket: "xxxx.appspot.com",
    messagingSenderId: "xxxx"
  };
  firebase.initializeApp(config);
</script>
```

:::info
預設寫入權限需要登入驗證，如果只是練習可以修改規則設定
```
{
  "rules": {
    // ".read": "auth != null",
    // ".write": "auth != null"
    ".read": true,
    ".write": true
  }
}
```
:::

## 寫入
### 選擇路徑
``ref()``：尋找資料庫路徑
```javascript=
ref('todos/content');
```

``child()``：用法和 ref() 差不多
```javascript=
ref('todos').child('content');
```
### set
``set()``：新增資料

```javascript=
// 沒有指定路徑代表根目錄
firebase.database().ref().set('hello world');

// 指定路徑
firebase.database().ref('student1/name').set('John');
```
:::info
firebase 中的資料都為物件形式
:::
### push
set 會把路徑下的資料覆寫，push 則是新增額外一筆，並自動產生對應 key。
```javascript=
var todos = firebase.database().ref('todos');
todos.push({content: '記得寫功課'});
```

## 讀取
### once
只會從 firebase 抓取一次，不會即時更新
```javascript=
// 同樣也可以指定路徑，否則就是讀取根路徑下的所有資料
firebase.database().ref().once('value', function(snapshot){
    console.log(snapshot.val());
});

// 讀取 myName 下的名字
var myName = firebase.database().ref('myName');
myName.once('value', function(snapshot){
    $('#name').html(snapshot.val());
});
```
### on
資料庫中的資料變動會即時更新到網頁上
```javascript=
var myName = firebase.database().ref('myName');
myName.on('value', function(snapshot){
    $('#name').html(snapshot.val());
});
```
#### 顯示 firebase 資料到網頁上
```htmlmixed=
<pre id="content"></pre>
```
```javascript=
firebase.database().ref().on('value', function(snapshot){
    // stringify 倒數兩個參數是縮排
    document.getElementById('content').textContent = JSON.stringify(snapshot.val(), null, 3);
});
```

## 刪除
### remove
```javascript=
var todos = firebase.database().ref('todos');
todos.child('content').remove();
```

## ToDoList
```htmlmixed=
<input id="txt" type="text" placeholder="請輸入內容...">
<input type="button" id="send" value="送出">
<ul id="list"></ul>
```
```javascript=
var txt = document.getElementById('txt');
var send = document.getElementById('send');
var list = document.getElementById('list')

// todos
var todos = firebase.database().ref('todos');

// 按送出按鈕，可以寫入到資料庫
send.addEventListener('click',function(e){
    console.log(txt.value);
    todos.push({content: txt.value});
})

// 顯示內容出來
todos.on('value',function(snapshot){
    var str = '';
    var data = snapshot.val();
    for(var item in data){
        str+='<li data-key="'+ item +'">'+data[item].content+'</li>';    
    }
    list.innerHTML = str;
})

// 刪除邏輯
list.addEventListener('click',function(e){
    
    if(e.target.nodeName=="LI"){
        var key = e.target.dataset.key;
        todos.child(key).remove();
    }
})
```

## 排序
### orderByChild
若使用 orderByChild 要用 ``forEach`` 撈出值
```javascript=
var peopleRef = firebase.database().ref('people');

peopleRef.orderByChild('age').once('value', function(snapshot){
    snapshot.forEach(function(item){
        // 撈出的是 item 的值，如果想看該值的 key 就直接 .key 就好
        console.log(item.key);
        console.log(item.val());
    })
})
```

## 區間
### startAt、endAt、equalTo
```javascript=
var peopleRef = firebase.database().ref('people');

peopleRef.orderByChild('weight').equalTo(2500).once('value',function(snapshot){
// peopleRef.orderByChild('weight').startAt(2500).endAt(3500).once('value',function(snapshot){
    snapshot.forEach(function(item){
        console.log(item.key);
        console.log(item.val());
    })
})
```

## 限制比數
### limit
取出第一/最後一筆資料
```javascript=
var peopleRef = firebase.database().ref('people');

peopleRef.orderByChild('weight').startAt(2500).limitToFirst(1).once('value',function(snapshot){
// peopleRef.orderByChild('weight').startAt(2500).limitToLast(1).once('value',function(snapshot){
    snapshot.forEach(function(item){
        console.log(item.key);
        console.log(item.val());
    })
})
```

# Restful API
URL : 網址 http://test.com/a/b/c
URI : 資源存放位置 /a/b/c
網址要可讀、簡潔、可預測

符合REST設計風格的 Web API 稱為 RESTful API

它從以下三個方面進行定義：
1. 直觀簡短的網址（URI）：http://example.com/resources/
2. 傳送的資源：JSON、XML
3. 對資源的操作：利用 http method（比如：POST、GET、PUT、DELETE）


### 網站連線方式
Browser URL Enter > Domain 透過 DNS 解析成 IP > http request > GET /a/b/c HTTP/1.1

Other Method/Action : ``Post`` ``Patch`` ``Put`` ``Delete``

### 實作練習
[規格表](https://hackmd.io/IYVgxgJgZgzGAMBaApmAjMRAWZAjAbIgBxTKH7wTBHD77ACcDyQA)

# ORM
> 物件關聯對映（英語：Object Relational Mapping，簡稱ORM），是一種程式設計技術，用於實現物件導向編程語言裡不同類型系統的資料之間的轉換。從效果上說，它其實是創建了一個可在編程語言裡使用的「虛擬物件資料庫」。

簡單來說就是用程式語言以操作物件的方式取代 SQL 指令，來對資料庫進行操作。

## sequelize
[搭配 express 的 orm 套件](https://github.com/sequelize/express-example)

### 用法
```
npm install express-generator -g
# 這邊創建 ejs
express -e name
cd name && npm install
npm start
```

這邊搭配的資料庫是 sqlite
```
# install ORM , CLI and SQLite dialect
npm install --save sequelize sequelize-cli sqlite3

# generate models
node_modules/.bin/sequelize init
```

:::danger
超級大坑：在 windows 下，``node_modules/.bin/sequelize init`` 會顯示 ``'node_modules' 不是內部或外部命令、可執行的程式或批次檔``，這是因為在 windows 下，路徑要用 ``\`` 而不是 ``/``。
:::

輸入完之後就會看到多三個資料夾
1. config（設定 DB 連線資料）
2. models（負責操作 DB 的 js）
3. migrations（紀錄所有操作 DB 的動作）

因為這邊要搭配的 DB 是 sqlite，所以要到 ``config\config.json`` 中把 dialect 的 mysql 改成 sqlite，然後就可以建立 DB 了。
```
node_modules/.bin/sequelize db:migrate
```

## 範例
這邊要來寫一個記帳功能，能夠 CRUD。

table 項目有
1. title（ex：買珍珠奶茶）
2. type（ex：drink）
3. cost（ex：50）

### create
先寫一個能夠新增資料進 DB 的功能
``name\models\account.js``
```javascript=
"use strict";

module.exports = function(sequelize, DataTypes){
    var Account = sequelize.define('Account', {
        title: DataTypes.STRING,
        type: DataTypes.STRING,
        cost: DataTypes.STRING
    });
    return Account;
}
```

對應首頁的 ``name/routes/index.js``， ejs 為模板語法，可以傳自訂參數。

``name/routes/index.js``
```javascript=
var express = require('express');
var router = express.Router();

/* GET home page. */
router.get('/', function(req, res, next) {
  res.render('index', { title: '記帳囉' });
});

module.exports = router;
```

首頁外觀直接套 bootstrap 模板，並讓新增消費按鈕連到 ``accounts/create`` 網址。
``name\views\index.ejs``
```javascript=
<!DOCTYPE html>
<html>
<head>
  <title>
    <%= title %>
  </title>
  <link rel='stylesheet' href='/stylesheets/style.css' />
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css" integrity="sha384-PsH8R72JQ3SOdhVi3uxftmaW6Vc51MKb0q5P2rRUpPvrszuE4W1povHYgTpBfshb"
    crossorigin="anonymous">
</head>
<body>
  <nav class="navbar navbar-expand-md navbar-dark bg-dark">
    <div class="container">
    	<a class="navbar-brand" href="/">記帳囉</a>
    	<div class="ml-auto my-2 my-lg-0">
          <a href="/accounts/create">
            <button class="btn btn-outline-success my-2 my-sm-0">新增消費</button>
          </a>
        </div>
    </div>
  </nav>
  <main role="main">
    <div class="jumbotron">
      <div class="container">
        <h1 class="display-3">記下生活中的花費</h1>
        <p>做自己錢包的主人</p>
        <p>
          <a class="btn btn-primary btn-lg" href="#" role="button">Learn more &raquo;</a>
        </p>
      </div>
    </div>
    <div class="container">
      <div class="row">
      </div>
    </div>
  </main>
  <footer class="container">
    <hr>
    <p>&copy; Company 2017</p>
  </footer>
</body>
</html>
```

新增消費明細的頁面
``name\views\create_account.ejs``
```javascript=
<!DOCTYPE html>
<html>
<head>
    <title>記帳囉</title>
    <link rel='stylesheet' href='/stylesheets/style.css' />
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css" integrity="sha384-PsH8R72JQ3SOdhVi3uxftmaW6Vc51MKb0q5P2rRUpPvrszuE4W1povHYgTpBfshb"
        crossorigin="anonymous">
</head>
<body>
  <nav class="navbar navbar-expand-md navbar-dark bg-dark">
    <div class="container">
    	<a class="navbar-brand" href="/">記帳囉</a>
    	<div class="ml-auto my-2 my-lg-0">
          <a href="/accounts/create">
            <button class="btn btn-outline-success my-2 my-sm-0">新增消費</button>
          </a>
        </div>
    </div>
  </nav>
    <div class="container mt-3">
        <div class="row justify-content-center">
            <div class="col-md-6">
                <h2>新增消費</h2>
                <form method="POST" action="/accounts/create">
                    <input class="mb-3 form-control" type="text" name="title" placeholder="請輸入消費名稱">
                    <select class="mb-3 form-control" name="type">
                        <option value="eat">食</option>
                        <option value="cloth">衣</option>
                        <option value="home">住</option>
                        <option value="traffic">行</option>
                        <option value="play">育樂</option>
                    </select>
                    <input class="mb-3 form-control" type="number" name="cost" placeholder="請輸入消費金額">
                    <div class="text-right">
                        <button class="btn btn-primary" type="submit">送出</button>
                    </div>
                </form>
            </div>
        </div>
        <hr>
    </div>
    <footer class="container">
        <p>&copy; Company 2017</p>
    </footer>
</body>
</html>
```

在上面新增明細的頁面中，form 會 post 資料到 /accounts/create 的網址，所以要在 control 中做控制。

記得到 ``app.js`` 中新增 ``accounts`` 的路由規則。

``name\routes\accounts.js``
```javascript=
var express = require('express');
var router = express.Router();
var models = require('../models');

/* 正常訪問時顯示新增消費的頁面 */
router.get('/create', function(req, res, next) {
  res.render('create_account');
});

/* 接收表單 POST 來的資料並寫進資料庫 */
router.post('/create', function(req, res, next) {
  console.log(req.body);
  models.Account.create({
    title: req.body.title,
    type: req.body.type,
    cost: req.body.cost
  }).then(function(){
    /* 回首頁 */
    res.redirect('/');
  });
});

module.exports = router;
```

寫好之後還需要修改兩個地方，一個是在 ``name\bin\www`` 中新增一個自動與 DB 同步的 function。

``name\bin\www``
```javascript=
/* 要調用 models 中的功能 */
var models = require('../models');

var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);

var server = http.createServer(app);

// sync() will create all table if they doesn't exist in database
models.sequelize.sync().then(function () {
  server.listen(port);
  server.on('error', onError);
  server.on('listening', onListening);
});
```

另一個是在 windows 環境下，記得修改 ``name\models\index.js`` 中的反斜線。

``name\models\index.js``
```javascript=
// 對應的 config.json 為 DB 連線資料
var config = require(__dirname + '/../config/config.json')[env];
```

都修改好之後就新增資料試試看，送出表單時，就可以詳細過程了
```
{ title: '吃便當', type: 'eat', cost: '80' }
Executing (default): INSERT INTO `Accounts` (`id`,`title`,`type`,`cost`,`createdAt`,`updatedAt`) VALUES (NULL,'吃便當','eat','80','2017
-10-29 19:12:17.226 +00:00','2017-10-29 19:12:17.226 +00:00');
POST /accounts/create 302 68.697 ms - 46
```

### read
可以 insert data 之後，就要把 DB 中的資料撈到 ``index.ejs`` 顯示。

因為訪問 ``localhost:3000`` 時，是先交由 routes 中的 ``index.js`` 來做路由判斷進一步動作，所以這邊要做的是撈完資料後回傳，再讓 view 來渲染。

``name/routes/index.js``
```javascript=
var express = require('express');
var router = express.Router();
var models = require('../models');

/* GET home page. */
router.get('/', function(req, res, next) {
  /* 撈出 DB 所有資料 */
  models.Account.findAll().then(function(accounts){
    /* 可以看到撈出的資料 */
    console.log(accounts);
    res.render('index', {
      title: '記帳囉',
      accounts: accounts
    });
  });
});

module.exports = router;
```

接著在主頁中將接收的資料透過模板語法渲染出來，有點像 php 的用法，只是若要顯示變數的 value 時，需要多加一個等號 ``<%= 變數 %>``。

``name/view/index.ejs``
```javascript=
  <main role="main">
    <div class="jumbotron">
      ...
    </div>
    <div class="container">
      <div class="row">
        <div class="col-12">
          <table class="table table-hover">
            <tr>
              <th>明細</th>
              <th>類型</th>
              <th>金額</th>
              <th>操作</th>
            </tr>
            <% for(var i = 0; i < accounts.length; i++){ %>
              <tr>
                <td><%= accounts[i].title %></td>
                <td><%= accounts[i].type %></td>
                <td><%= accounts[i].cost %></td>
                <td></td>
              </tr>
            <% } %>
          </table>
        </div>
      </div>
    </div>
  </main>
```

### update
先修改首頁

``name/view/index.ejs``
```javascript=
<div class="container">
      <div class="row">
        <div class="col-12">
          <table class="table table-hover">
            <tr>
              <th>消費內容</th>
              <th>類型</th>
              <th>金額</th>
              <th>更改</th>
              <th>刪除</th>
            </tr>
            <% for(var i = 0; i < accounts.length; i++){ %>
              <tr>
                <td><a href="accounts/<%= accounts[i].id %>"><%= accounts[i].title %></a></td>
                <td><%= accounts[i].type %></td>
                <td><%= accounts[i].cost %></td>
                <td>
                  <a href="accounts/<%= accounts[i].id %>/update" class="btn btn-warning">更新</a>
                </td>
                <td>
                  <!-- 刪除採取 post 較為安全一點 -->
                  <form method="post" action="accounts/<%= accounts[i].id %>/delete">
                    <button class="btn btn-danger" type="submit">刪除</button>
                  </form>
                </td>
              </tr>
            <% } %>
          </table>
        </div>
      </div>
    </div>
```

接著在 ``name/view/`` 中新增一個修改的頁面，和原本輸入的頁面 ``name/view/create_account.ejs`` 一樣，只是要帶入原始值而已。

``name/view/update_account.ejs``
```javascript=
<div class="container mt-3">
    <div class="row justify-content-center">
        <div class="col-md-6">
            <h2>新增消費</h2>
            <form method="POST" action="/accounts/<%= account.id %>/update">
                <input value="<%= account.title %>" class="mb-3 form-control" type="text" name="title" placeholder="請輸入消費名稱">
                <select class="mb-3 form-control" name="type">
                    <option <%= account.type === 'eat'? 'selected' : '' %> value="eat">食</option>
                    <option <%= account.type === 'cloth'? 'selected' : '' %> value="cloth">衣</option>
                    <option <%= account.type === 'home'? 'selected' : '' %> value="home">住</option>
                    <option <%= account.type === 'traffic'? 'selected' : '' %> value="traffic">行</option>
                    <option <%= account.type === 'play'? 'selected' : '' %> value="play">育樂</option>
                </select>
                <input value="<%= account.cost %>" class="mb-3 form-control" type="number" name="cost" placeholder="請輸入消費金額">
                <div class="text-right">
                    <button class="btn btn-primary" type="submit">送出</button>
                </div>
            </form>
        </div>
    </div>
</div>
```

接著在 ``name/routes/accounts.js`` 中新增 update 動作

``name/routes/accounts.js``
```javascript=
/* update頁面 */
router.get('/:account_id/update', function(req, res, next) {
  console.log(req.params);
  models.Account.findOne({
    where: {
      id: req.params.account_id
    }
  }).then(function(account){
    res.render('update_account', {account: account});
  });
});

/* update DB 中的某筆資料 */
router.post('/:account_id/update', function(req, res, next) {
  models.Account.findOne({
    where: {
        id: req.params.account_id
    }
  }).then(function(account){
    account.update({
      title: req.body.title,
      type: req.body.type,
      cost: req.body.cost
    });
  }).then(function(){
    res.redirect('/');
  });
});
```

:::info
params 取 URL 中對應參數的值
query 取 GET 時 URL 中的參數（?p=test）
body 取表單 POST 過來的值
:::

### delete
剛剛在 update 中已經在主頁加上刪除的按鈕了，這邊只剩寫入邏輯操作就好。

``name/routes/accounts.js``
```javascript=
/* delete DB 中的某筆資料 */
router.post('/:account_id/delete', function(req, res, next) {
  models.Account.destroy({
    where: {
      id: req.params.account_id
    }
  }).then(function(){
    res.redirect('/');
  });
});
```

### 優化
新增一個單獨顯示某筆消費的頁面

``name/routers/accounts.js``
記得要放在最下面，不然會和上面的 ``router.get('/create'...)`` 有衝突
```javascript=
/* 某筆資料單獨頁面 */
router.get('/:account_id/', function(req, res, next) {
  models.Account.findOne({
    where: {
      id: req.params.account_id
    }
  }).then(function(account){
    res.render('single_account', {account: account});
  });
});
```

接著撰寫顯示的頁面，並加上麵包屑

``name/view/single_account.ejs``
```javascript=
<!DOCTYPE html>
<html>
<head>
    <title>記帳囉</title>
    <link rel='stylesheet' href='/stylesheets/style.css' />
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css" integrity="sha384-PsH8R72JQ3SOdhVi3uxftmaW6Vc51MKb0q5P2rRUpPvrszuE4W1povHYgTpBfshb"
        crossorigin="anonymous">
</head>
<body>
    <nav class="navbar navbar-expand-md navbar-dark bg-dark">
        <div class="container">
            <a class="navbar-brand" href="/">記帳囉</a>
            <div class="ml-auto my-2 my-lg-0">
                <a href="/accounts/create">
                    <button class="btn btn-outline-success my-2 my-sm-0">新增消費</button>
                </a>
            </div>
        </div>
    </nav>
    <div class="container mt-3">
        <!-- 麵包屑 -->
        <nav aria-label="breadcrumb" role="navigation">
            <ol class="breadcrumb">
                <li class="breadcrumb-item">
                    <a href="/">Home</a>
                </li>
                <li class="breadcrumb-item active" aria-current="page"><%= account.title %></li>
            </ol>
        </nav>
        <div class="row">
            <div class="col-12">
                <table class="table table-hover">
                    <tr>
                        <th>消費內容</th>
                        <th>類型</th>
                        <th>金額</th>
                    </tr>
                    <tr>
                        <td>
                            <%= account.title %>
                        </td>
                        <td>
                            <%= account.type %>
                        </td>
                        <td>
                            <%= account.cost %>
                        </td>
                    </tr>
                </table>
            </div>
        </div>
    </div>
    <footer class="container mt-5">
        <hr>
        <p>&copy; Company 2017</p>
    </footer>
</body>
</html>
```

### 成品
![](https://i.imgur.com/t2v92Px.png)

![](https://i.imgur.com/bJM1Sgj.png)

![](https://i.imgur.com/FtQSNEQ.png)

### 登入功能
可以使用　[passport 套件](http://www.passportjs.org/)
