# [BE201] 後端中階:Express 與 Sequelize
###### Date : 2021 Aug.03 - Aug.14
#### Express 簡介
- 先從不用框架開始
- node.js http sever
```javascript
const http = require('http')
const server = http.createServer(handler)
function handler(req, res) {
console.log(req.url)
if (req.url === '/hello') {
res.writeHead(200, {
'Content-Type': 'text/html'
})
res.write('hello!')
} else if (req.url === '/bye') {
res.write('bye~')
} else {
res.write('Invalid url!')
}
res.end()
}
server.listen(5001)
```
- 初探 Express
- 安裝 : `npm init` , `npm install express`
- 起手式 Ref : https://expressjs.com/en/starter/hello-world.html
```javascript
const express = require('express')
const app = express()
cosnt port = 5001
app.get('/', (req, res) => {
res.send('hi')
})
app.get('/hello', (req, res) => {
res.send('hello')
})
app.listen(port, () => {
console.log(`app listening on port ${port}`)
})
```
- Express 基本架構 與 MVC
- MVC ( Model, Controller, View )
> index.js ( express )
```javascript
const express = require('express')
const app = express()
const port = 5001
const todoController = require('./controllers/todo.js')
// set view engine
app.set('view engine', 'ejs')
app.get('/todos', todoController.getAll)
app.get('/todos/:id', todoController.get)
app.listen(port, () => {
console.log(`app listening on port ${port}`)
})
```
- View
- Template engine : https://expressjs.com/en/guide/using-template-engines.html
- EJS engine
> ./view/todo.js
```ejs
<h1>Todo</h1>
<h2><%= todo %></h2>
```
> ./view/todo.js
```javascript
<h1>Todo</h1>
<ul>
<% for(let i = 0; i < todos.length; i++) { %>
<li><%= todos[i] %></li>
<% } %>
</ul>
```
- Model
> ./models/todo.js
```javascript
const todos = ['todo1', 'todo2', 'todo3']
const todoModel = {
getAll: () => {
return todos
},
get: id => {
return todos[i]
}
}
module.exports = todoModel
```
- Controller
> ./controllers/todo.js
```javascript
const todoModel = require('../models/todo')
const todoController = {
getAll: () => {
const todos = todoModel.getAll()
res.render('todos', {
todos
})
},
get: id => {
const id = req.params.id
const todo = todoModel.get(id)
res.render('todo', {
todo
})
}
}
module.exports = todoController
```
- Node.js 與 MySQL 的串接
- node.js / mysql
- 安裝 : `npm mysql`
- 起手式
> db.js
```javascript
const mysql = require('mysql')
const connection = mysql.createConnetion({
host : 'localhost',
user : 'me',
password : 'secet',
database : 'my_db'
})
connection.connect()
connection.query('SELECT * FROM todos', (error, result) => {
if (error) throw error
console.log(results)
})
```
- 也可以 export 出去,其他的地方就可以用
- Model 下的用法 : 從資料庫取得資料
```javascript
const db = require('../db')
const todoModel = {
getAll: (cb) => {
db.query(
'SELECT * from todos', (err, results) => {
if (err) return cb(err);
cb(null, results)
});
},
get: (id, cb) => {
db.query(
'SELECT * from todos where id = ?', [id],
(err, results) => {
if (err) return cb(err);
cb(null, results)
}
);
},
add: (content, cb) => {
db.query(
'insert into todos(content) values(?)', [content],
(err, results) => {
if (err) return cb(err);
cb(null)
}
);
}
}
module.exports = todoModel
```
#### Middlewave
- 什麼是 Middleware
- Middleware 中介軟體
- Express 會經過一系列的 middleware 處理,如果不傳 next 就不會往下執行了
```javascript
app.use((req, res, next) => {
console.log('Time', new Date())
next()
}
```
- 大多功能是要靠 middlewave 去完成,但也有一些內鍵的如 : `req.query`
- 解析 Request : body-parser
- 安裝 : `npm install body-parser`
- 起手式 :
```javascript
const bodyParser = reuqire('body-parser')
app.use(bodyParser.urlencoded({ extend: false }))
app.use(bodyParser.json() )
```
- 加了之後才可以用 `req.body` 拿到 `POST` 過來的東西
- 管理 Session : Session middlewave
- 安裝 : `npm install express-session`
- 起手式 :
```javascript
const session = require('exprss-session')
app.use(session({
secret: 'keyboard cat',
resave: false,
saveUninintialized: true
}))
```
- 加了之後可以使用 `req.session` ,只有 require 有 session ,完全沒有 respones 的 session
- 顯示 Error : connect-flash
- 安裝 : `npm install connect-flash` ( 需先用 expross-session )
- 起手式 :
```javascript
const flash = require('connect-flash')
app.use(flash())
// 讀取
errorMessage: req.flash('errorMessage')
// 寫入
req.flash('errorMessage', '密碼錯誤')
```
- 客製化 middlewave
```javascript :
// 放入 locals ,有點像全域變數的感覺
// views 可以用任何來自 locals 的東西
app.use((req, res, next) => {
// 設置是設置在 res ,但是取得是在 req
res.locals.isLogin = req.session.isLogin || false
res.locals.errorMessage = req.flash('erroeMessage')
next()
})
```
- 做一個簡單的會員註冊系統
- bcrypt
- 安裝 : `npm install bcrypt`
- 起手式 :
```javascript
const bcrypt = require('bcrypt')
const saltRounds = 10
// hash
bcrypt.hash(password, saltRounds, (err, hash) => {
if (err) return req.flash('errMessage', err.toString())
// todo
})
// cpmpare
bcrypt.compare(password, hashed, (err, res) => {
if (err || !res) req.flash('errMessage', err.toString())
// todo
req.session.username = user.username
res.redirect('/')
})
```
- 做一個超簡易留言板
#### 完整留言板
- 思考產品全貌、規劃資料庫
- 功能
1. 新增留言
2. 編輯留言 : GET /delete_comment/3 => /
3. 刪除留言 : GET /update_comment/3 => form
- POST /update_comments/3 => /
- 資料結構
- id, username ( foriegn key ), nickname
- 實作刪除功能
- 需注意權限管理功能
- 實作編輯功能
- https://github.com/Lidemy/express-demo
- 美化頁面
- cdn 引入 bootstrap
- 在 express 中引入 static css
> express middlewae
```javascript
app.use(express.static('public'))
```
> pulic/style/style.ss
```html
<link rel="stylesheet" href="/styles/style.css" />
```
#### ORM 與 Sequelize
- 什麼是 ORM
- Object Relational Mapping
- 用物件來操作資料庫,但有些限制
- Sequelize 示範
- Ref : https://sequelize.org/
- 中文 : https://www.sequelize.com.cn/
- 安裝 : `npm install --save sequelize`
- 起手式 :
```javascript
const Sequelize = require('sequelize')
const sequelize = new Sequelize('mydb', 'root', 'root', {
host: 'localhost',
dialect: 'mysql'
})
```
- 定義方法
```javascript
const User = sequelize.define('user', {
firstName: {
type: Sequelize.STRING,
allowNull: false
},
lastName: {
type: Sequelize.STRING
}
}, {
// options
})
```
- 自動建立 table
```javascript
sequelize.sync().then( () => {
User.creat({
firstName: 'John',
lastName: 'Hancock'
})
}).then( () => {
console.log('created!')
}).catch(err => {
console.log('err:', err)
})
```
- Query all
```javascript
sequelize.sync().then(() => {
User.findAll().then( users => {
console.log("All user", JSON.stringify(users, null, 4))
})
})
```
- Query one
```javascript
sequelize.sync().then(() => {
User.findAll({
where: {
fistName: 'aaa'
}
}).then( users => {
console.log(users[0].id, users[0].firstName))
})
})
```
- Update
```javascript
sequelize.sync().then( () => {
User.findOne({
where: {
firstName: 'aaa'
}
}).then( users => {
users.update({
lastName: 'hahaha'
}).then( () => {
console.log('done')
})
})
})
```
- Delete
```javascript
sequelize.sync().then( () => {
User.findOne({
where: {
firstName: 'aaa'
}
}).then(user => {
user.destroy().then( () => {
console.log('done')
})
})
})
```
- 資料庫關聯
- 關連
```javascript
// 建立關連
User.hasMany(Comment)
sequelize.sync().then( () => {
User.findOne({
whhere: {
firstName: 'aaa'
},
include: Comment
// 在 Comment 中把 aaa 的留言都拿出來
}).then( user => {
console.log(user)
})
})
User.hasMany(Comment)
sequelize.sync().then( () => {
User.findOne({
where: {
firstName: 'aaa'
},
include: Comment
// 在 Comment 中把 aaa 的留言都拿出來
}).then( user => {
console.log(JSON.stringigy(user.comments, null, 4)
})
})
```
- 雙向關連
```javascript
User.hasMany(Comment)
Comment.belongsTo(User)
sequelize.sync().then( () => {
Comment.findOne({
where: {
content: 'hello'
},
include: User
// 在 Comment 中找出 hello 的使用者
}).then( comment => {
console.log(JSON.stringigy(comment.user.firstName, null, 4))
})
})
```
- Sequelize CLI 實戰
- 安裝 : `npm install --save sequelize-cli`
- init : `npx sequelize-cli init`
- config.json > development : 改成自己的資料庫設定
- 起手式 : `npx sequelize-cli model:generate --name User --attributes firstName:sting,lastName:string,email:string`
- model : 自動建立資料庫模型
- migrations : 儲存 database 的 table
- 執行 : `npx sequelize-cli db:migrate` ( 真的建立 table )
- 了有 migrations 就不需有 sync() 了
- 好處 :
1. 產生 medels, migrations
2. 更完整的資料夾結構
3. 設定檔、模組都弄好了 ( 還有 seeder 種子資料 )
- 改造留言板系統
- 使用 sequelize-cli 打造,Model 要大寫
- build
```javascript
// build data object
npx sequelize-cli model:generate --name User --attributes username:string,password:string,nickname:string
npx sequelize-cli model:generate --name Comment --attributes content:string
// migration
npx sequelize db:migrate
npx sequelize db:migrate:undo
```
#### 部署 : Nginx + PM2
- PM2 簡介
- 讓 APP 跑在背景執行
- 安裝 : `npm install pm2 -g`
- 起手式 :
- 看有哪些在背景跑 : `pm2 ls`
- 開始執行 : `pm2 start index.js`
- 看 log : `pm2 logs 0` ( 0 是 app 的 ID )
- 看詳細資訊 : `pm2 info 0`
- 暫停 : `pm2 stop 0`
- 重啟 : `pm2 restart 0`
- 刪除 : `pm2 delete 0`
- 防火牆設定 : ufw
- 看資訊 : `ufw status`
- 開啟 80 port : `ufw allow 80`
- Nginx
- 可以透過 Reverse Proxy Server ( 反向代理 ) 來管理 port restart
- 安裝 : nginx
- 防火牆 : `ufw allow 'Nginx HTTP'`
- 開啟 : `systemctl status nginx`
- 設定檔 : `etc/nginx`
#### 部署 : Heroku
- Heroku 簡介
- 主要是部署後端的專案
- 會自動 `npm install` 所以不用
- 只要 `npm run start` 跑的起來丟上去應該就 OK
- 環境變數
```javascript
// a.js
console.log(process.env.UNAME)
```
`UNAME=123 node a.js` => `123`
- export 可以存入環境變數
```bash
export UNAME=456
echo $UNAME # 456
node a.js # 456
```
- Heroku 的環境變數
- $PORT
```javascript
const port process.env.PORT || 3000
```
`PORT=5566 node app.js`
- 部署
- 先安裝 `herokr-cli`
- 選擇 node.js 版本
```javascript
// package.json
...
"engines": {
"node" : "12.x"
},
```
- Specifying a start script
```javascript
"scrpits": {
"start" : "node app.js"
},
```
`heroku local web` 確定專案可以跑起來
- 變成 git 的 repo
`git init`
```bash
heroku create # 到這邊為止都是靜態的,如果需要 db 可以參考以下
git remote -v # 查看遠端 heroku
git push heroku master # 推上去 heroku
heroku open # 開啟部署完的網址
```
- Ref :
- https://devcenter.heroku.com/articles/getting-started-with-nodejs?singlepage=true
- https://devcenter.heroku.com/articles/git
- 串接資料庫 ( clearDB )
- 接繼上一步到 `heroku create` 之後
- 新增 addons ( clearDB ) : `heroku addons:create cleardb:ignite`
- 在設設定檔中加上
```javascript
// config.json
production: {
// ...
"use_env_variable": "CLEARDB_DATABASE_URL"
}
```
- 在 package.json 加上
```javascript
"script": {
"db:migrate": "npx sequelize db:mrigate",
"start": "npm run db:migrate && node index.js"
}
```
- 再執行 git add / git commit / git push heroku master
- 如果有用 sequelize 的 db:migrate 要先使用
- 要記得去 heroku 遠端的資料庫找資訊 ( 帳號密碼那些 ) 才可以連上去看資料庫
- Ref : https://devcenter.heroku.com/articles/cleardb
---
##### 心得 2021 Aug.17
- 後端看來還跟我不熟悉,我可能是慢熟型的 (?)但比起用 php 來寫後端,node.js + express 真的很棒,好寫非常非常多,也許是用熟悉的 javascript 的語法寫比較快上手
- Sequelize 也是蠻特別的,可以用 javascript 的物件操作資料,對我來說蠻新的,用起來還不錯,可以不用寫 SQL Query,但也會有一些小問題,要學習新的語法、N + 1 problem、效能問題,但總地來說還不錯,還有 mirgate,部署起來也很方便
- 作業花了比較久去寫,覺得這周 loading 有點多,明明有很多東西可以延用之前的東西,但偏偏自己要作死自己搞 XD