如何用 Node.JS 開發一個留言板
YC JHUO 5/29/2021 Node.JS
這篇來介紹單純用 Node JS,不依靠其他框架(如 Express)來建構一個留言板
這個留言板共二個頁面:首頁&留言頁
下圖順序為:首頁 -> Post 頁 -> (提交留言後)回首頁
# 目錄結構
首先,專案名稱為 bulletin 這個資料夾下有下列四項:
- public:資料夾內放置 css, image, js 等網站會用到的資源、圖片等
- views:放置各個頁面的 html (方便管理)
- app.js:業務邏輯
- package.json:專案設定 & 用到的各種套件
bulletin
└── public
└── css
└── main.css
├── image
└── js
└── views
├── index.html
├── post.html
└── 404.html
├── app.js
└── package.json
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# 建立 package.json
首先利用 npm 來建立 package.json 及安裝 art-template 套件
npm init -y
# 安裝 art-template
npm install art-template
1
2
3
4
2
3
4
# 業務邏輯
# app.js
var http = require('http')
var fs = require('fs')
var url = require('url')
var template = require('art-template')
// 每次重開服務時,預先保存的留言(可省略)
var comments = [
{
name: 'User 1',
message: 'Hello',
dateTime: '2021-5-27 18:20:54'
},
{
name: 'User 2',
message: 'There',
dateTime: '2021-5-27 18:20:54'
}
]
http.createServer(function (req, res) {
var parseObj = url.parse(req.url, true)
// 用 url.parse 將路徑解析為對象;第二個參數是將查詢的字串轉為對象
// 在讀取首頁時,parseObj 為:
// Url { protocol: null, slashes: null, auth: null, host: null, port: null, hostname: null, hash: null, search: null, query: [Object: null prototype] {}, pathname: '/', path: '/', href: '/' }
// 在 Post 頁,Submit 後 parseObj 為:
// Url { protocol: null, slashes: null, auth: null, host: null, port: null, hostname: null, hash: null, search: '?name=Leon&message=Say+something', query: [Object: null prototype] { name: 'Leon', message: 'Say something' }, pathname: '/submit', path: '/submit?name=Leon&message=Say+something', href: '/submit?name=Leon&message=Say+something' }
// 可看出不同的地方為:search, query, pathname, path, href,而等等我們就要用這個 query 來取得我們 submit 的值
// 擷取網址列的路徑 (會忽略 ? 之後的內容)
// eg. http://localhost:3000/post?name=Leon&message=Say+something
// 只會抓到 /post
var pathname = parseObj.pathname
// 路由邏輯
// Homepage
if (pathname === '/') {
// data = ./views/index.html
fs.readFile('./views/index.html', function (err, data) {
if (err) {
return res.end('Loading index page failed.')
}
// 因為 data 是二進制,所以須轉為 string
var htmlStr = template.render(data.toString(), { comments: comments })
res.end(htmlStr)
})
}
// Post page
else if (pathname === '/post') {
// data = ./views/post.html
fs.readFile('./views/post.html', function (err, data) {
if (err) {
return res.end('Loading post page failed.')
}
res.end(data)
})
}
// Opening access to folder public (若不開放 user 存取則可省略)
// eg. http://localhost:3000/public/js/main.js
else if (pathname.indexOf('/public/') === 0) {
fs.readFile('.' + pathname, function (err, data) {
if (err) {
return res.end('Loading public folder failed.')
}
res.end(data)
})
}
// Click submit
else if (pathname === '/submit') {
// 取得 submit 的 data
// comment = query: [Object: null prototype] { name: 'Leon', message: 'Say something' }
var comment = parseObj.query
// 取得目前日期時間 (用於紀錄 submit 的時間)
// today = 2021-05-28T18:40:03.622Z
var today = new Date();
var date = today.getFullYear() + '-' + (today.getMonth() + 1 ) + '-' + today.getDate();
var time = today.getHours() + ":" + today.getMinutes() + ":" + today.getSeconds();
var dateTime = date + ' ' + time;
// dateTime = '2021-5-28 21:33:55'
comment.dateTime = dateTime
// 將 comment 放到 comments 內 (unshift 代表新 submit 的內容出現在列表最上面)
comments.unshift(comment)
// 設定狀態碼為 302 (網頁重定向),用於網頁自動跳轉
res.statusCode = 302
// 跳轉至首頁
res.setHeader('Location', '/')
res.end()
}
// 設定 page 404
else {
fs.readFile('./views/404.html', function (err, data) {
if (err) {
return res.end('404 Not Found.')
}
res.end(data)
})
}
})
.listen(3000, function () {
console.log('running...')
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# 建立 views
# index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Bulletin</title>
<link rel="stylesheet" href="../public/css/main.css">
<!-- Import bootstrap -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous">
</head>
<body>
<div class="header container">
<div class="page-header">
<h1>Bulletin</h1>
<a class="btn btn-success" href="/post">Post</a>
</div>
</div>
<div class="comments container">
<ul class="list-group">
<!-- art-template -->
{{each comments}}
<li class="list-group-item">{{ $value.name }}:{{ $value.message }} <span class="pull-right">{{ $value.dateTime }}</span></li>
{{/each}}
</ul>
</div>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# post.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Bulletin</title>
<!-- Import bootstrap -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous">
</head>
<body>
<div class="header container">
<div class="page-header">
<h1><a href="/">Homepage</a></h1>
</div>
</div>
<div class="comments container">
<!--
submit 表單時需有 name 屬性
action 為 submit 後會跳轉的 url 地址
-->
<form action="/submit" method="get">
<div class="form-group">
<label for="input_name">Name</label>
<input type="text" class="form-control" id="input_name" name="name" placeholder="Type your name here" required minlength="2" maxlength="10">
</div>
<div class="form-group">
<label for="textarea_message">Comments</label>
<textarea class="form-control" id="textarea_message" name="message" cols="30" rows="10" required minlength="5" maxlength="20"></textarea>
</div>
<button type="submit" class="btn btn-default">Submit</button>
</form>
</div>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# 404.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Bulletin</title>
<body>
<p>The page cannot be found.</p>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# Public 資料夾
# main.css
.pull-right {
float: right;
}
1
2
3
2
3
# package.json
{
"name": "bulletin",
"version": "1.0.0",
"description": "",
"main": "app.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"art-template": "^4.13.2"
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
歡迎點擊追蹤: