
什麼是版本控制? 來源
簡言之,就是將一個檔案所有歷史紀錄的版本都保存起來,以便日後參考。
但這種管理方式會碰到哪些問題?
- 檔案一多、時間久了之後不易管理
- 多人協作易發生衝突,無法快速分辨檔案差異
簡言之,就是將一個檔案所有歷史紀錄的版本都保存起來,以便日後參考。
npm install express
//引入express
var express = require('express');
//建立一個 Express 伺服器
var app = express();
//設定靜態檔案所在目錄
app.use(express.static(__dirname + '/publc'));
app.listen(12345);
//處理首頁連線要求
app.get('/', function(req, res) {
//傳送字串給瀏覽器
res.send('This is a GET METHOD');
res.end();
});
//POST方法來處理路由
app.post('/post', function(req, res) {
res.send('This is a POST METHOD');
res.end();
})
大多數的連線都是以GET方法來要求葉面,而網頁上的表單傳送,則會使用到POST方法。我們可以針對需求,以get()或post()設定不同的路由處理函數。
這個處理函數扮演著處理連線要求的重要腳色,它會被帶入兩個參數:
>>request-連線要求的資訊和方法
>>response-回應連線的資訊和方法
function(req, res) {
// 處理連線要求,並回應客戶端
}
安裝Jade模板引擎模組
npm install jade
//設定模板放置目錄
app.set('views', __dirname + '/views');
//設定模板引擎
app.set ('view engine', 'jade');
//設定首頁,導入(render)到/views/mypage裡
app.get('/', function(req, res) {
res.render('mypage');
//傳遞參數msg1給mypage
res.render('mypage', { msg1: 'Hello Template' })
}
a標籤HTML用法
<a href="http://www.mandice.com/">Heloo</a>
a標籤使用Jade
a(href='http://www.mandice.com/') Hello
id和class屬性
Jade仿用CSS用法 #開頭代表id屬性、 .開頭代表class屬性
div #myid.my_div
帶入參數
在Jade裡可以使用 #{參數名稱}的方式,去使用res.render()所傳進來的參數內容。
div #{msg1}
基本邏輯表示
使用if做判斷
- if (msg1 == 'Hello Template')
div Hello Template
- else
div Notjing
使用 for Each 方法
- for (var i=0; i<=10; i++ )
div #{i}
跟Jade比較起來,EJS語法跟原來HTML相近
HTML
<html>
<head>
<title>Hello EJS</title>
</head>
<body>
<h1>Hello EJS</h1>
</body>
<html>
EJS 重述上頁面
發現語法都差不多
<html>
<head>
<title>Hello EJS</title>
</head>
<body>
<h1>Hello EJS</h1>
</body>
<html>
安裝EJS引擎
npm install ejs
使用變數
res.render('index', {
title: 'To-Do List',
});
放入模板中
<h1><%= title %></h1>
<p>Welcome to <%= title %></p>
基本語法
EJS使用 <% %>所包夾而成的特殊標籤,來表示動態內容,上述例子中的msg1變數,可以用 <%=msg1 %>來帶入變數內容。
if的判斷
<% if(msg1 == 'Hi EJS') { %>
<h1> YES </h1>
<% } else { %>
<h1> NO </h1>
<% } %>
使用for-loop迴圈,將每一個陣列一一印出
<ul>
<% for (var i=o; i<list.length; i++) { %>
<li><%= list[i] %></li>
<% } %>
</ul>
QueryString的使用方式,是在網頁路徑之後接一個?符號隔開,然後連接上所要傳遞的字串,如以下例子。
/mydir/hello?name=Fried&country=Taipei
express程式接值
app.get('/mydir/hello', function(req, res) {
console.log(req.query.name);
console.log(req.query.country);
});
get缺點:你雖然可以直接在瀏覽器上網址列輸入想送出的資料,測試容易且快速。但是,由於網址路徑是有長度限制的,往往不能使用QueryString傳太過複雜或太大的資料。
對一般網站開發者來說,使用POST方法的方式,多半是使用HTML的表單form,當使用者按下送出按鈕submit,瀏覽器就會用POST的方法,把表單各個欄位內容放置在body中,送到伺服器上。
為了能讓Express讀取並處理POST方法的body資料區塊,你要先安裝body-parser模組。
npm install body-parser
安裝完就要設定啟用body解析器,由於範例將會用HTML表單form來傳送資料,所以要設定urlencoded來支援HTML表單:
var bodyParser = require('body-parser');
//設定bodyParser支援application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({
//不用在bodyParser處理Query String
extended: false;
}));
建立一個node.js
// node.js 內建 http 相關 module
const http = require('http')
// createServer() 要傳入的參數是 function
const server = http.createServer(handler)
// 兩個參數分別是 request 和 response,這裡使用命名慣例寫法
function handler(req, res) {
console.log(req.url) // 印出 req 網址
res.write('Hello World!') // 指定 respone 回傳內容
res.end() // 結束這個 response
}
// 常見為 80 port,測試時使用 5001 port 就不易發生衝突
server.listen(5001)
輸入 https://localhost:5001
我們可以加上轉址功能
const http = require('http')
const server = http.createServer(handler)
function handler(req, res) {
console.log(req.url) // 印出 req 網址
if (req.url === '/hello') {
// 參數分別是 request 的 status code 和內容格式,告訴瀏覽器如何解析網頁
res.writeHead(200, { // 200: 請求成功
'Content-Type': 'text/html'
})
res.write('<h1>hello!</h1>') // 也可以加上 HTML 標籤
} else if (req.url === '/bye') {
res.write('bye!')
} else {
res.write('Invalid url')
}
res.end() // 結束這個 response
}
server.listen(5001)
npm install express --save
用express 實作一個Hello World
編輯一個index.js
// 引入 library
const express = require('express');
// express 引入的是一個 function
const app = express();
// 建立一個不易產生衝突的 port 用來測試
const port = 5001;
// 如何處理不同的 request,參數分別為 url 和要執行的 function
app.get('/', (req, res) => {
res.send('hello world!')
})
app.get('/bye', (req, res) => {
res.send('bye!')
})
// 運行這個 port,參數分別為 port 和要執行的 function
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
})
執行 node index.js ,網站就執行了。
MVC(Model–view–controller):是一種應用程式架構,透過將程式碼拆成分成模型(Model)、視圖(View)和控制器(Controller)三個部分,並透過路由系統,建立整個應用程式的設計模式。
在 MVC 架構中,request 流程大致如下:
1.發出的 request 會由 Controller 來處理
2.接著 Controller 會和 Model 拿取 data
3.Controller 再把拿到的資料給 View,由 View 提供的 template
4.最後 Controller 再結合 data 和 template,回傳 respon
透過 Express 提供的 template engines 來實作 View
1.安裝ejs
npm install ejs
EJS 語法是透過<% %>符號,和 PHP 語法其實很類似,語法又可分為三種:
<% JavaScript 程式碼 %>
<%- %> 會經過解析然後印出來,用於引入 HTML 內容
<%= %> 會直接印出原始碼,用於輸出資料,避免被解析成語法,可視為一種 XSS 防禦
2.設定index.js
// 設定 view engine
app.set('view engine', 'ejs')
3.預設目錄會是 /views,因此需要新建一個資料夾 views,並在資料夾中建立一個 hello.ejs 檔
4.在 hello.ejs 檔中輸入簡單的程式碼進行測試,例如:<h1>Hello</h1>
5.接著調整 index.js 程式碼,告訴 express 去 render views 目錄底下叫做 hello 的檔案:
const express = require('express');
const app = express();
const port = 5001;
// 設定 view engine
app.set('view engine', 'ejs')
app.get('/', (req, res) => {
res.send('index')
})
app.get('/hello', (req, res) => {
// 叫 express 去 render views 底下叫做 hello 的檔案,副檔名可省略
res.render('hello')
})
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
})
網站變成有hello的page(node去讀取views目錄裡的hello page)
1.首先在 index.js 建立 todos,並設定 app.get() 傳入資料:
const express = require('express');
const app = express();
const port = 5001;
// 設定 view engine
app.set('view engine', 'ejs')
// 建立 todos data
const todos = [
'first todo', 'second todo', 'third todo'
]
app.get('/todos', (req, res) => {
// 第二個參數可傳入資料
res.render('todos', {
todos // todos: todos 一樣的話可省略寫法
})
})
app.get('/hello', (req, res) => {
res.render('hello')
})
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
})
2.接著編輯 todos.ejs 檔的內容,也就是 todos 的 view 部分。要輸出內容的語法是 <%= code %>,而不是用 console.log(),或是 PHP 的 echo:
<h1>Todos</h1>
<ul>
<% for(let i = 0; i < todos.length; i++) { %>
<li><%= todos[i]%></li> // 加上等於代表後面的東西要輸出
<% } %>
</ul>
3.在瀏覽器運行,這樣能根據之前設立的 data 輸出 todos:
4.接著回到 index.js 檔,用同樣的方式,根據不同 id 來拿取對應的 todo:
// 加上 :id 代表不確定的參數
app.get('/todos/:id', (req, res) => {
// params: 可拿到網址列上指定的參數
const id = req.params.id
const todo = todos[id]
res.render('todo', {
todo
})
})
5.建立 todo.ejs 檔,也就是 todo 的 view 部分:
<h1>Todo</h1>
<h2><%= todo %></h2>
6.透過網址列上的 id,能夠讀取相對應的 todo:
接下來要試著重構程式碼,也就是實作 MVC 架構中的 Model 和 Controller 部分。
Model:用來管理 todos 的資料
1.回到 express 目錄,新增一個 models 資料夾,並在裡面建立 todo.js 檔
2.在 todo.js 檔案,建立 todoModel,提供存取資料的方法(function),例如 get 或 add 等 method:
const todos = [
'first todo', 'second todo', 'third todo'
]
// 建立一個 todoModel 物件,裡面放存取資料的方法(function)
const todoModel = {
getAll: () => {
return todos
},
get: id => {
return todos[id]
}
}
module.exports = todoModel
Controller:控制器
1.同樣在 express 目錄,新增一個 controllers 資料夾,並在裡面建立 todo.js 檔
2.接著重構程式碼:
*從 model 引入資料
*建立物件,並透過方法(function)來存取資料,這裡會和一開始中 *index.js 的 app.get() 寫法類似再交由 view engine 進行 render
// 先從 model 引入 todos 資料
const todoModel = require(../models/todo)
// 建立一個 todoController 物件,透過方法來存取 model 的資料
const todoController = {
// 傳入參數 req, res
getAll: (req, res) => {
const todos = todoModel.getAll()
res.render('todos', {
todos
})
},
get: (req, res) => {
const id = req.params.id
const todo = todoModel.get(id)
res.render('todo', {
todo
})
}
}
module.exports = todoController
3.回到根目錄的 index.js 檔,修改路由,透過引入 controller 的 todo.js,程式碼就可以更簡潔:
const express = require('express');
const app = express();
const port = 5001;
// 引入 controller
const todoController = require('./controllers/todo')
app.set('view engine', 'ejs')
const todos = [
'first todo', 'second todo', 'third todo'
]
// 可直接使用 controller 的方法拿取資料和進行 render
app.get('/todos', todoController.getAll)
app.get('/todos/:id',
這樣就完成了有 MCV 架構的程式:
*express 目錄的 index.js:提供路由
*views 目錄的 todo.ejs 和 todos.ejs:提供模版
*models 目錄的 todo.js:提供資料
*controllers 目錄的 todo.js:結合 model 和 view,根據路由回傳Response
在瞭解到基本的 Express 架構之後,再來我們要試著把 todo 資料存在資料庫。這是因為在實際專案中,後端會把資料存放在資料庫,因此我們要來學習如何透過 Node.js 和 MySQL 溝通。
Step1. 安裝 MySQL
在使用 Node.js 操作 MySQL 資料庫時,必須先安裝 MySQL 模組。搜尋 node.js mysql 會找到 GitHub 有個叫做 mysqljs 的 Library,執行安裝指令:
npm install mysql
Step2. 新增 app 資料庫 & todos 資料表
我們可以用phpmyadmin來新增資料庫與資料表
Step3. 串接 MySQL 資料庫
確認本地端已經安裝資料庫並正常啟動,接著就可以新增一個 db.js 檔來進行連線,程式碼可參考範例:
// 引入 mysql 模組
var mysql = require('mysql');
// 建立連線
var connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: 'root',
database: 'app'
});
connection.connect();
// 使用 callback 來接收訊息: 連線成功就印出 todos 所有欄位
connection.query('SELECT * from todos', function (error, results, fields) {
if (error) throw error;
console.log(results);
});
connection.end();
Step4. 重構程式碼
1.將 db.js 簡化,獨立成串聯資料庫時需要的資料,方便其他部分要連線時引入:
var mysql = require('mysql');
var connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: 'root',
database: 'app'
});
module.exports = connection;
2.接著在 index.js 引入 db,也就是 mysql 模組以及連線資料,加上 db.connect() 指令來連線:
const express = require('express');
// 引入 db 資料庫: mysql 模組 & 連線資料
const db = require('./db')
const app = express();
const port = 5001;
const todoController = require('./controllers/todo')
app.set('view engine', 'ejs')
app.get('/todos', todoController.getAll)
app.get('/todos/:id', todoController.get)
app.listen(port, () => {
// 連線資料庫
db.connect()
console.log(`Example app listening at http://localhost:${port}`)
})
3.再來是修改 Models,MVC 架構的好處就是能像這樣明確分工:
在使用 SQL 指令時須注意,字串拼接可能會有 SQL injection 的風險,可透過 Preparing Queries 來避免,方法和 Prepared Statements 其實很類似。
// 引入 db,也就是 connection
const db = require('../db')
const todoModel = {
// 這裡要用 callback 來拿取資料
getAll: (cb) => {
db.query(
'SELECT * FROM todos', (err, results) => {
if (err) return cb(err);
// cb: 第一個參數為是否有錯誤,沒有的話就是 null,第二個才是結果
cb(null, results)
});
},
get: (id, cb) => {
db.query(
'SELECT * FROM todos WHERE id = ?', [id], (err, results) => {
if (err) return cb(err);
cb(null, results)
});
}
}
module.exports = todoModel
4.因為 Models 從同步改成非同步操作,也要修改 Controllers 的部分:
// 先從 model 引入 todos 資料
const todoModel = require('../models/todo')
const todoController = {
getAll: (req, res) => {
// 改成 callback 非同步操作
todoModel.getAll((err, results) => {
// 如果有 err 就印出錯誤訊息
if (err) return console.log(err);
// 不然就把 todos 傳給 view
res.render('todos', {
todos: results
})
})
},
get: (req, res) => {
const id = req.params.id
todoModel.get(id, (err, results) => {
if (err) return console.log(err);
res.render('todos', {
// 注意回傳的結果 array,必須取 results[0] 才會是一個 todo
todos: results[0]
})
})
}
}
module.exports = todoController
5.再來是修改 Views 部分,Todos 部分有兩種寫法:
第一種:分開寫
<h1>Todos</h1>
<ul>
<% for(let i = 0; i < todos.length; i++) { %>
<li><%= todos[i].id %>: <%= todos[i].content %></li>
<% } %>
</ul>
第二種:寫在一起,用字串拼接方式
<h1>Todos</h1>
<ul>
<% for(let i = 0; i < todos.length; i++) { %>
<li><%= todos[i].id + ': ' + todos[i].content %></li>
<% } %>
</ul>
執行 node index.js 之後,回到瀏覽器確認程式是否有成功運行:
本範例port為5002,作者說:不知道如何原因5001被占走,只好執行5002
接著是 Todo,會發現輸出結果是 Object。這是因為 <%= %> 語法會直接印出字串,當我們想要把一個 Object 轉成字串時,就會發生下列情形:
只要將 Todo 部分修改成輸出 todo.content:
<h1>Todo</h1>
<h2><%= todo.content %></h2>
結果就會是相對應的 todo:
如果對資料庫操作 CURD,重整頁面也會動態更新:
學到目前為止,透過上面這些範例,我們其實已經能寫出一些簡單的網頁程式了,並且有 MVC 架構,能夠簡化程式碼且便於維護。