Node.js+Express — 製作CRUD簡易待辦清單(上)
想要快速學習一種語言或者是一種框架,最快的方式沒有比寫一個 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>
執行後的首頁:畫面上已經出現資料庫中的資料了。
繼續…
如果到目前為止都沒有問題的話,大家可以繼續進到下一個篇章。