Node.js+Express — 製作CRUD簡易待辦清單(上)

Sean Yeh
Web Design Zone
Published in
19 min readApr 20, 2020

想要快速學習一種語言或者是一種框架,最快的方式沒有比寫一個 TODO 待辦清單應用程式來得適當了。

一個TODO 待辦清單應用程式包含了基本的CRUD功能在內。CRUD代表增(Create)刪(Delete)改(Update)查(Read)四個主要功能的縮寫。學會了CRUD之後,就可以將所學的語言使用在很多地方了。

因此,這一篇與下一篇就是要練習寫一個簡易版的TODO 待辦清單應用程式。預期從這個簡易版待辦清單應用程式,可以學到下面知識:

  • 使用網頁瀏覽器將Request 傳送到伺服器。
  • 使用Express建立伺服器服務,並且監聽來自網頁瀏覽器的Request訊息。
  • 透過增刪改查功能(CRUD)更新資料庫內容。

步驟

這裏列出應用程式大致上開發的步驟:

  • 建立環境:採用Node.js與Express.js框架
  • 製作頁面:用EJS樣版引擎製作HTML頁面
  • 建立與連接資料庫:採用firebase做為資料庫
  • C新增資料
  • R讀取、U修改、D刪除資料

我們就依照上面的計畫,一個一個的來實作看看。

建立環境

要建立一個TODO 待辦清單應用程式,先從建立伺服器服務開始。

首先,需要安裝的套件為:express。在安裝套件之前,我們先進行npm環境設定。(以上假定大家已經在電腦中安裝了Node.js)

初始化專案

先在電腦中建立一個資料夾(例如:CRUD資料夾),並在VS Code打開該資料夾並且在command line工具列輸入下面npm init指令。依照步驟一步一步安裝,之後會在我們的工作資料夾裡面新增一個package.json的檔案。

再來,輸入下面指令,安裝express套件:

$ npm install express

安裝完後,到名為node_modules開資料中,檢查看看裡面是否有一個express資料夾存在。未來所有透過npm安裝的套件,都會存在這個資料夾裡。

複習一下,我們來觀察package.json檔,會發現多了下面這一段,express就是我們剛剛安裝的套件。我們可以把package.json檔當作是一個「貨物清單」,裡面列出本專案所安裝的套件與每個套件的版本,方便我們管理各個套件。

"dependencies": {"express": "^4.17.1"}

建立入口檔案

套件安裝好之後,新增一個檔案,名稱為app.js。並在app.js裡面,先載入express套件

let express = require('express')

接著我們 (A).初始一個 express服務:app,(B).加上首頁路由,並且©.監聽 3000 port。

let app = express()app.get('/', function (req, res) {res.send("Hello, welcome to our app")})app.listen(3000)

初始化測試

在終端機Command Line 輸入node app 指令,啟動本地端伺服器。

接著打開瀏覽器(建議使用Google Chrome瀏覽器),在瀏覽器的網址列輸入下面網址:

localhost:3000/

如果網頁上出現「Hello, welcome to our app」的字樣,就表示伺服器已經成功建立。

製作HTML頁面

安裝套件

要使用EJS樣版引擎,需要安裝ejs、ejs-locals

$ npm install ejs --save
$ npm install ejs-locals --save

如果想要方便來回修改樣板,不想一直重新啟動伺服器,可以加裝nodemon套件進去,如此可以省下我們很多寶貴的時間。

$ npm install nodemon

我們也可以修改package.json檔案:在scripts裡面加上一行。”watch”: “nodemon app”,如下:

"scripts": {"watch": "nodemon app","test": "echo \"Error: no test specified\" && exit 1","start": "node server.js"},

在command line執行:

$ npm run watch

之後,只要有變動,伺服器都會自動重啟。我們每次修改程式後,只要重新整理瀏覽器,就可以看到程式的變化。

EJS樣版引擎套件載入與設定

先在express後面,載入ejs-locals。

let engine = require('ejs-locals');

接著,設定ejs為樣版引擎以及設定讀取的資料夾為根目錄的views資料夾。並且在根目錄建立一個views的資料夾。

app.engine('ejs', engine);
app.set('views', './views');
app.set('view engine', 'ejs');

建立EJS樣板

在views資料夾新增一個ejs檔案index.ejs,內容為簡單的html標籤(<H1>Hello, TODO</H1>)。

修改app.get路由

res.send("Hello, welcome to our app") 改成 res.render(‘index’)。

app.get('/', function (req, res) {  
res.render('index')
})

啟動app,如果網頁上出現「Hello」的字樣,就表示伺服器已經成功建立樣版引擎。我們進一步要把index.ejs調整成我們需要的頁面。因此,先加上form表單。並且載入CSS樣板Bootstrap 4 (為了簡化步驟,這裡使用CDN載入CSS)。以下是完成這個步驟後,index.ejs裡面<body>…</body>內會存在的原始碼。

<div class="container"><H1>Hello, TODO</H1><form><div class="d-flex align-items-center"><input autofocus autocomplete="off" class="form-control mr-3" type="text" style="flex: 1;"><button class="btn btn-primary">新增項目</button></div></form></div>

執行結果如下:

如果你看到的畫面與上面一樣,恭喜你完成了這一個階段。

不過,為了讓頁面更加完美,我們在調整一下上面的indes.ejs,加入一些css class,並且把之後要顯示的資料列表區的html標籤先做出來。

調整後的結果如下:

<body>…</body>內的原始碼如下:

<div class="container"><h1 class="display-4 text-center py-1">Hello, TODO</H1>
<!-- input area -->
<div class="jumbotron p-3 shadow-sm">
<form>
<div class="d-flex align-items-center">
<input autofocus autocomplete="off" class="form-control mr-3" type="text" style="flex: 1;">
<button class="btn btn-primary">新增項目</button>
</div>
</form>
</div>
<!-- // input area -->
<!-- list area -->
<ul class="list-group pb-5">
<li class="list-group-item list-group-item-action d-flex align-items-center justify-content-between">
<span class="item-text">Fake example item #1</span>
<div>
<button class="edit-me btn btn-secondary btn-sm mr-1">Edit</button>
<button class="delete-me btn btn-danger btn-sm">Delete</button>
</div>
</li>
<li class="list-group-item list-group-item-action d-flex align-items-center justify-content-between">
<span class="item-text">Fake example item #2</span>
<div>
<button class="edit-me btn btn-secondary btn-sm mr-1">Edit</button>
<button class="delete-me btn btn-danger btn-sm">Delete</button>
</div>
</li>
<li class="list-group-item list-group-item-action d-flex align-items-center justify-content-between">
<span class="item-text">Fake example item #3</span>
<div>
<button class="edit-me btn btn-secondary btn-sm mr-1">Edit</button>
<button class="delete-me btn btn-danger btn-sm">Delete</button>
</div>
</li>
</ul>
<!-- //list area -->
</div>

其他關於EJS樣版引擎可參考之前文章:EJS樣板引擎的使用方式

建立與連接資料庫

登入與啟用

我們在這裡使用Firebase Realtime database來作為這個專案的資料庫。首先我們要登入Firebase主控台並且啟用。

新建專案

依照步驟建立一個新專案,我們在這裡取名為Todoapp。

建立Database

接下來要建立Database。有兩個選項,請選擇RealtimeDatabase。

連接資料庫

這次我們不使用CDN的方式,要先安裝firebase模組

$ npm install firebase --save

安裝後在package.json裡面應該會出現一個firebase的dependencies。確認存在後,就可以在app.js載入firebase模組。

let firebase = require('firebase');

並進行 firebase 初始化,初始化的內容和網頁前端幾乎相同,宣告資料庫的網址就能操作操作 Realtime database。

firebase.initializeApp({
databaseURL: "https://xxxxxxxxxx.firebaseio.com"
});

完成上述步驟後,測試一下剛才的安裝。在app.js的首頁路由加入下面的程式碼,並查看 console裡面是否有訊息?

var db = firebase.database();
console.log(db)

加上去之後的路由如下:

app.get('/', function (req, res) {res.render('index');
var db = firebase.database();
console.log(db);
})

如果在console中出現訊息,就表示成功連接資料庫。其他關於Firebase的安裝說明,可參考之前文章:Firebase初探(1)- 安裝與設定

增刪改查(CRUD)

接下來我們要撰寫增刪改查(CRUD)的部分。TODO 待辦清單應用程式的使用情境大致如下,可以參考一下畫面:

情境說明

讀取:進入頁面時,應用程式會自行從資料庫中取出所有資料,並顯示在列表中。

新增:頁面中有一個input輸入框,讓使用者輸入待辦清單相關的文字,一旦輸入完畢按下輸入框旁邊的『新增項目』按鈕,就可以將文字輸入到資料庫中,並且同時顯示在下面的列表中。

修改:每個列表資料旁邊都有Edit(編輯)與Delete(刪除)按鈕。按下Edit(編輯)後,可以修改文字,並且存回資料庫。回存資料庫的同時,頁面不需重新整理,直接顯示修改後的文字。

刪除:按下Delete(刪除)後,該筆資料直接消失於畫面,並且相對應的資料也從資料庫中刪除。

以下針對這四個功能分別進行撰寫。

C新增資料

首先,修改indes.ejs裡面的<form>標籤,加上action與method屬性:

<form action="/create-item" method="POST">

接著,修改input標籤,在input標籤加上name屬性:

name=”item”

切換到app.js。

1.在express()的下方加上下面的middleware程式碼,這樣才能抓倒頁面資料:

app.use(express.urlencoded({extended: false}))

2.增加路由:因為我們希望按鈕按下去之後,會導向/create-item的路由。所以暫時先在app.js裡加上一個app.post()的create-item路由:

app.post('/create-item', function (req, res) {console.log(req.body.item)res.send("Thank for submit the form")})

測試目前結果:

在input輸入框中輸入文字,並且按下旁邊的『新增項目』按鈕。如果順利的話,你的網址會轉到 localhost:3000/create-item,並且在頁面上顯示『Thank for submit the form』。並且查看 command line,可以看到您剛剛在輸入框輸入的文字。

與資料庫連接

以上,我們都還是使用 console.log(req.body.item)來測試功能。接下來就要真的把資料「寫入」資料了。

(1).指定一個變數item,利用req.body.item的方式,將從input欄位取得的值存入這個變數。

var item = req.body.item;

(2).接下來我們用一個變數itemRef來設定Firebase資料庫的路徑。這裏我們把路徑設為todos。並且使用push來新增資料。使用Firebase的push方法會新增資料到指定的資料庫路徑中。這個方法會產生一組獨一無二的Key,並且將資料儲存在Key下面。

var itemRef = fireData.ref('todos').push();

(3).讓firebase的todos裡面產生一個item欄位,並且這個item欄位的資料要從前面item變數(var item = …)而來。

itemRef.set({"item": item});

(4).再用then()來確認是否存入資料庫中。then()要緊接著加在set()後面。我們使用once來取得資料庫的即時快照,再用snapshot.val()取出內容送到頁面。

.then(function () {db.ref('todos').once('value', function (snapshot) {res.send(snapshot.val())})})

修改路由

修改create-item路由。把步驟(1)~(4)的程式碼加入create-item路由。

app.post('/create-item', function (req, res) {var item = req.body.item;var itemRef = db.ref('todos').push();itemRef.set({"item": item}).then(function () {db.ref('todos').once('value', function (snapshot) {res.send(snapshot.val())})});})

測試

我們可以使用POSTMAN測試這個POST是否正確。

結果有出現,表示POST新資料應該沒問題。

此外,也可以把 res.send(snapshot.val()) 改成下面程式碼,顯示更為詳細的結果:

res.send({"success": true,"result": snapshot.val(),"message": "資料讀取成功"})

不過,這樣子做的話,新增資料後網頁會顯示結果,看起來也是很奇怪。

可以把res.send()改成 res.redirect(‘/’),之後 一但新增資料頁面就會自動轉址回到首頁了:

res.redirect('/')

R讀取資料

接下來,要把從資料庫取得的資料,顯示在網頁的頁面上(如下圖的『Fake example item #1』中,替換為資料庫中的資料)。

就像前面規劃的一樣:

讀取:進入頁面時,應用程式會自行從資料庫中取出所有資料,並顯示在列表中。

因此,我們先改寫首頁路由:

app.get('/', function (req, res) {db.ref('todos').once('value', function () {var data = snapshot.val();res.render('index', {"todolist": data});});});

改寫之後,當進入首頁時就會讀取到資料庫的資料。並且存在變數todolist裡面。

然後,我們要改寫下面這一段語法,讓資料顯示在這個區塊裡面:

<ul class="list-group pb-5">
<li>...</li>
<li>...</li>
<li>...</li>
</ul>

在EJS使用for迴圈

for迴圈,可以讓項目展示從資料庫中獲取的資料。我們也可以在indes.ejs的<ul>標籤中寫上for迴圈。

由於我們在EJS樣板中使用<% %>來寫程式。for迴圈要寫在<% %>裡面。也就是像下面這樣的寫法:

<% for(條件){%>執行項目<% } %>

其中<% for(條件){%>裡面的條件是什麼?

<% for(條件){%>應該是 for( list in todolist ),其中的todolist來自於app.js中 ”todolist”: data 得到的資料,data是firebase資料庫中todos裡面所有資料的快照。而list in todolist表示,這個個別的項目 list 必定屬於 todolist 中的某一筆資料。也就是 firebase 資料庫中 todos 裡面的某一筆資料。

那麼 for迴圈中的執行項目又是什麼?

我們知道資料庫裡面的資料如下:

資料庫中的資料

既然list屬於todolist中的某一筆資料,而每筆資料中都有一個item欄位(見上圖資料庫)。我們希望取得每一筆資料中item欄位的值。所以可以使用todolist[list].item取得,而我們要讓這個值被解析出來顯示在HTML頁面上,則需要把它放在<% %>裡面。綜上所述,執行項目 的內容應該是在<li>標籤裡面放置:<%- todolist[list].item %>。

依照這個方式,我們把<ul>標籤中的程式碼寫為:

<ul class="list-group pb-5"><% for(list in todolist) { %><li class="list-group-item list-group-item-action d-flex align-items-center justify-content-between">
<span class="item-text"><%- todolist[list].item %></span
<div>
<button class="edit-me btn btn-secondary btn-sm mr-1">Edit</button>
<button class="delete-me btn btn-danger btn-sm">Delete</button>
</div>
</li>
<% } %></ul>

執行後的首頁:畫面上已經出現資料庫中的資料了。

畫面上的顯示結果

繼續…

如果到目前為止都沒有問題的話,大家可以繼續進到下一個篇章。

--

--

Sean Yeh
Web Design Zone

# Taipei, Internet Digital Advertising,透過寫作讓我們回想過去、理解現在並思考未來。並樂於分享,這才是最大贏家。