其他章節請看:
報名系統 - [express]
最簡單的報名系統:
- 只有兩個頁面
- 人員資訊列表頁:展示已報名的人員資訊列表。裡面有一個
報名
按鈕,點選按鈕則會跳轉到報名頁 - 報名頁:用於報名。裡面是一個表單,可以輸入姓名和年齡,點選
儲存
,成功後會跳轉到人員資訊列表頁
本文主要分 3 部分:
- 使用 node 實現這個專案
- 介紹 express 相關知識
- 使用 express 重寫這個專案
Tip: 有將本文分成兩篇的打算,因為篇幅有點長;但最後還是決定寫在一起,因為更加緊湊。
node 實現
目錄如下:
- demo
- public // 存放靜態資源
- css
- global.css
- views // 存放模板
- add.html // 報名頁
- list.html // 列表頁
- index.js // 入口檔案
- package.json // PS: 自己安裝依賴包
global.css:
body{color:red;}
add.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<form action="/submit" method='get'>
<p><input type="text" name='name'></p>
<p><input type="text" name='age'></p>
<p><input type="submit" value='儲存'></p>
</form>
</body>
</html>
list.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="/public/css/global.css">
</head>
<body>
<p><a href="/add">報名</a></p>
<section>
{{each rows}}
<li>{{$value.name}} {{$value.age}}</li>
{{/each}}
</section>
</body>
</html>
index.js:
// 模擬資料庫
const DB = [
{name: 'ph', age: '18'},
{name: 'lj', age: '19'}
]
const http = require('http')
const fs = require('fs')
const template = require('art-template')
http.createServer(function(req, res){
const url = req.url
// URL模組的 WHATWG API。Constructor: new URL(input[, base])
// api: https://nodejs.org/dist/v12.18.1/docs/api/url.html#url_constructor_new_url_input_base
const urlObj = new URL(url, `https://${req.headers.host}`)
// 列表頁面 list.html
if(url === '/'){
fs.readFile('./views/list.html', (err, data) => {
if (err) throw err;
const ret = template.render(data.toString(), {
rows: DB
});
res.end(ret)
})
// 留言頁面 add.html
}else if(url.indexOf('/add') === 0){
fs.readFile('./views/add.html', (err, data) => {
if (err) throw err;
res.end(data)
})
// 提交留言
}else if(urlObj.pathname === '/submit'){
// 插入資料
const row = {}
row.name = urlObj.searchParams.get('name')
row.age = urlObj.searchParams.get('age')
DB.unshift(row);
// 臨時重定向
res.statusCode = '302'
res.setHeader('Location', '/');
res.end()
}else if(urlObj.pathname.endsWith('.css')){
fs.readFile('./' + url, (err, data) => {
if (err) throw err;
res.end(data)
})
}else{
res.end('404')
}
}).listen(3000)
package.json:
- 可以先在 demo 路徑下執行
npm init -y
來幫助我們生成 package.json 檔案 - 接著執行
npm install art-template
安裝外掛即可
{
...
"dependencies": {
"art-template": "^4.13.2"
}
}
執行程式:
$ cd demo
// 自行安裝依賴包: npm install
// 啟動服務 - 前文已介紹筆者使用 nodemon 來代替 node 啟動服務
$ nodemon index
瀏覽器訪問 http://localhost:3000/
,進入列表頁(list.html),頁面顯示:
報名
ph 18
lj 19
注:如果 node 控制檯報錯,則需要你根據錯誤提示修改一下,比如你把資料夾 views 一不小心寫成了 view。
點選報名
,進入報名頁面(add.html),顯示一個表單,輸入名字(pm)和年齡(22),點選儲存
,則會重定向到人員資訊列表頁,頁面顯示:
報名
pm 22
ph 18
lj 19
至此,這個簡單的專案就已經完成。
接下來用 express 框架重寫該專案之前,我們得先介紹一下 express 相關的知識。
express 基礎知識
筆者通過 express 中文網 來介紹 express。這類技術網站稱之為 cooking(烹飪) 網站。好比教我們如何烹飪,得先買菜(安裝),然後放油、放蔥薑蒜,爆炒1分鐘...,一步一步告訴我們怎麼做,相對比較簡單。
進入 express 中文網,導航的選單如下:
- 首頁
- 快速入門
- 安裝
- hello-world
- 基本路由
- 靜態檔案
- FAQ
- ...
- 指南
- 路由
- 開發中介軟體
- 使用模板引擎
- 整合資料庫
- API參考手冊
- ...
Tip: 主要介紹 express 重寫報名系統需要用到的知識點,更多細節請參考 express 官網。
首頁
Express - 基於 Node.js 平臺,快速、開放、極簡的 Web 開發框架。
快速入門 - 安裝
$ npm install express
快速入門 - hello-world
const express = require('express')
const app = express()
const port = 3000
app.get('/', (req, res) => {
res.send('Hello World!')
})
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
})
啟動服務,訪問 http://localhost:3000/
頁面會輸出 Hello World!
;如果訪問其他路徑(例如 http://localhost:3000/a
),則會以 404 響應。
快速入門 - 基本路由
路由是指確定應用程式如何響應客戶端對特定端點的請求,該特定端點是URI(或路徑)和特定的HTTP請求方法(GET,POST等)。
語法:app.METHOD(PATH, HANDLER)
以下定義了 4 個路由,請看示例:
app.get('/', function (req, res) {
res.send('Hello World!')
})
app.post('/', function (req, res) {
res.send('Got a POST request')
})
app.put('/user', function (req, res) {
res.send('Got a PUT request at /user')
})
app.delete('/user', function (req, res) {
res.send('Got a DELETE request at /user')
})
快速入門 - 靜態檔案
利用 Express 託管靜態檔案。
為了提供諸如影像、CSS 檔案和 JavaScript 檔案之類的靜態檔案,請使用 Express 中的 express.static 內建中介軟體函式。
語法:express.static(root, [options])
如果需要將 public 目錄下的圖片、CSS 檔案、JavaScript 檔案對外開放,下面兩種方式都可以。
方式1:
app.use(express.static('public'))
// 現在,你就可以訪問 public 目錄中的所有檔案了:
http://localhost:3000/images/kitten.jpg
http://localhost:3000/css/style.css
http://localhost:3000/js/app.js
http://localhost:3000/hello.html
方式2:
app.use('/static', express.static('public'))
// 現在,你就可以通過帶有 /static 字首地址來訪問 public 目錄中的檔案了。
http://localhost:3000/static/images/kitten.jpg
http://localhost:3000/static/css/style.css
Tip:提供給express.static函式的路徑是相對於您啟動節點程式的目錄的。 如果從另一個目錄執行Express App,則使用要提供服務的目錄的絕對路徑更為安全:
app.use('/static', express.static(path.join(__dirname, 'public')))
更多關於 __dirname,請看本文 path 模組
章節
快速入門 - FAQ
如何處理 404 響應?
app.use(function (req, res, next) {
res.status(404).send("Sorry can't find that!")
})
如何設定一個錯誤處理器?
app.use(function (err, req, res, next) {
console.error(err.stack)
res.status(500).send('Something broke!')
})
如何渲染純 HTML 檔案?
不需要!無需通過 res.render() 渲染 HTML。 你可以通過 res.sendFile() 直接對外輸出 HTML 檔案。 如果你需要對外提供的資原始檔很多,可以使用 express.static() 中介軟體。
指南 - 路由
路由路徑匹配 acd 和 abcd:
app.get('/ab?cd', function (req, res) {
res.send('ab?cd')
})
路由引數:
Route path: /users/:userId/books/:bookId
Request URL: http://localhost:3000/users/34/books/8989
req.params: { "userId": "34", "bookId": "8989" }
})
express 提供了一些響應方法,res.end()、res.redirect()、res.render()、res.sendStatus()...,可以將響應傳送到客戶端,並終止請求-響應週期。 如果沒有從路由處理程式中呼叫這些方法,則客戶端請求將被掛起。
指南 - 開發中介軟體
從接收請求,到傳送響應,我們可以加入各種中介軟體來做一些處理。中介軟體又可以傳給下一個中介軟體處理。下面我們定義了一個 myLogger 的中介軟體,請看示例:
var express = require('express')
var app = express()
var myLogger = function (req, res, next) {
console.log('LOGGED')
next() // {1}
}
// 使用中介軟體
app.use(myLogger)
app.get('/', function (req, res) {
res.send('Hello World!')
})
app.listen(3000)
每次請求,node 都會輸出 LOGGED
。如果將 next() (行{1})註釋,再次請求,頁面將一直轉圈圈,因為響應被掛起了。
指南 - 使用模板引擎
筆者使用的模板引擎是前文已使用過的 art-template。開啟 art-template 官網,點選 Express 選單就能看到該模板在 express 中使用的方法。請看:
npm install express-art-template
var express = require('express');
var app = express();
// view engine setup
app.engine('art', require('express-art-template')); // {20}
app.set('views', path.join(__dirname, 'views'));
// routes
app.get('/', function (req, res) {
res.render('index.art', { // {21}
user: {
name: 'aui',
tags: ['art', 'template', 'nodejs']
}
});
});
模板檔案預設是 .art,可以改成 .html,只需要將 art(行{20}和行{21}) 改為 html 即可。
指南 - 整合資料庫
MongoDB
Mongoose
Tip: 後續將會使用 Mongoose 依賴包來將 MongoDB 資料庫加入我們的專案。
API參考手冊
由於我的下載的 express 是 4.17.1,所以我參考的 API 是 4.x。
req.body - 包含在請求正文中提交的資料的鍵值對。 預設情況下,它是未定義的,並且在使用諸如 body-parser 和 multer 之類的 body-parsing 中介軟體時填充。請看示例:
var express = require('express')
var app = express()
app.use(express.json()) // for parsing application/json
app.use(express.urlencoded({ extended: true })) // for parsing application/x-www-form-urlencoded
app.post('/profile', function (req, res, next) {
console.log(req.body)
res.json(req.body)
})
express 重寫
在 node 實現的專案(demo)的基礎上,共 3 處變化: add.html、index.js 和 package.json。
1、add.html:method='get'
改為 method='post'
2、index.js:
const path = require('path')
const express = require('express')
const app = express()
// 填充 req.body
app.use(express.urlencoded({ extended: true })) // for parsing application/x-www-form-urlencoded
// view engine setup
app.engine('html', require('express-art-template'));
// 可以通過下面語句更改模板檢視的資料夾,預設是 views。
// app.set('views', path.join(__dirname, 'views'));
// 模擬資料庫
const DB = [
{name: 'ph', age: '18'},
{name: 'lj', age: '19'}
];
const port = 3000
// 將靜態資源對外開放
app.use('/public', express.static('public'))
app.get('/', function (req, res) {
res.render('list.html', {
rows: DB
});
});
app.get('/add', function (req, res) {
res.render('add.html', {
rows: DB
});
});
app.post('/submit', function (req, res) {
const row = {}
row.name = req.body.name
row.age = req.body.age
DB.unshift(row);
res.redirect('/')
});
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
})
// 處理 404 響應
app.use(function (req, res, next) {
res.status(404).send("404")
})
3、package.json(即依賴包的變化):
{
...
"dependencies": {
"express": "^4.17.1",
"express-art-template": "^1.0.1"
}
}
執行的效果和用node寫的一樣,但編碼更優雅。
path 模組
路徑模組提供了用於處理檔案和目錄路徑的實用程式。 可以使用以下命令訪問它:
const path = require('path');
path.basename() 方法返回路徑的最後一部分。尾部目錄分隔符將被忽略。請看示例:
> path.basename('/foo/bar/baz/asdf/quux.html');
quux.html
> path.basename('/foo/bar/baz/asdf/quux.html', '.html');
quux
> path.basename('/foo/bar/baz/asdf/');
asdf
path.dirname() 方法返回路徑的目錄名稱。尾部目錄分隔符將被忽略。請看示例:
> path.dirname('/foo/bar/baz/asdf/quux');
/foo/bar/baz/asdf
path.extname(path) 返回副檔名
> path.extname('index.html');
.html
> path.extname('index.coffee.md');
.md
> path.extname('index.');
.
> path.extname('index');
''
path.parse() 方法返回一個物件,該物件的屬性表示路徑的重要元素。請看示例:
> path.parse('/home/user/dir/file.txt');
{
root: '/',
dir: '/home/user/dir',
base: 'file.txt',
ext: '.txt',
name: 'file'
}
> path.parse('C:\\path\\dir\\file.txt');
{
root: 'C:\\',
dir: 'C:\\path\\dir',
base: 'file.txt',
ext: '.txt',
name: 'file'
}
path.join() 方法使用特定於平臺的分隔符作為分隔符,將所有給定的路徑段連線在一起,然後對結果路徑進行規範化。請看示例:
> path.join('/foo', 'bar', 'baz/asdf', 'quux');
\\foo\\bar\\baz\\asdf\\quux
> path.join('/foo', 'bar', 'baz/asdf', 'quux', '..');
\\foo\\bar\\baz\\asdf
> path.join('/foo', 'bar', 'baz/asdf', 'quux', './../..');
\\foo\\bar\\baz
> path.join('foo', {}, 'bar');
TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string. Received an instance of Object
path.isAbsolute() 是否是絕對路徑。請看示例:
> path.isAbsolute('/foo/bar');
true
> path.isAbsolute('qux/');
false
> path.isAbsolute('.');
false
__dirname
如果你將 express 專案放在 demo 目錄上一層執行 $ nodemon index
,在通過瀏覽器訪問 http://localhost:3000/
,頁面會出現報錯資訊:Error: Failed to lookup view "list.html" in views directory "D:\實驗樓\node-study\views"
。
在檔案裡面用相對路徑是不靠譜的。相對於執行 node 的目錄,node 就是這麼設計。
每個模組都有 __dirname,表示該檔案的目錄,是一個絕對路徑,還有 __filename。請看示例:
Running node example.js from /Users/mjr
console.log(__filename);
// Prints: /Users/mjr/example.js
console.log(__dirname);
// Prints: /Users/mjr
我們可以通過 path.join(__dirname, 'xxx')
來修復上面的問題。將 index.js 改為下面的程式碼即可:
const path = require('path')
const express = require('express')
const app = express()
app.use(express.urlencoded({ extended: true })) // for parsing application/x-www-form-urlencoded
// view engine setup
app.engine('html', require('express-art-template'));
// 可以通過下面語句更改模板檢視的資料夾,預設是 views
// app.set('views', path.join(__dirname, 'views'));
// 模擬資料庫
const DB = [
{name: 'ph', age: '18'},
{name: 'lj', age: '19'}
];
const port = 3000
// 將靜態資源對外開放
app.use('/public', express.static(path.join(__dirname, 'public')))
app.get('/', function (req, res) {
res.render(path.join(__dirname, 'views', 'list.html'), {
rows: DB
});
});
app.get('/add', function (req, res) {
res.render(path.join(__dirname, 'views', 'add.html'), {
rows: DB
});
});
app.post('/submit', function (req, res) {
const row = {}
row.name = req.body.name
row.age = req.body.age
DB.unshift(row);
res.redirect('/')
});
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
})
// 處理 404 響應
app.use(function (req, res, next) {
res.status(404).send("404")
})
其他章節請看: