如何用 Node.JS 開發一個留言板

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

# 建立 package.json

首先利用 npm 來建立 package.json 及安裝 art-template 套件

npm init -y

# 安裝 art-template
npm install art-template
1
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

# 建立 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

# 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

# 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

# Public 資料夾

# main.css

.pull-right {
    float: right;
}
1
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
Last Updated: 5/21/2023, 3:35:52 AM

歡迎點擊追蹤:

(adsbygoogle = window.adsbygoogle || []).push({});