Project review — Expense tracker

專案簡介

Yunya Hsu
7 min readJun 21, 2022

使用者可以:

  1. 用 email 註冊帳號
  2. 只有「登入狀態」的使用者可以看到內容,且只能看到自己建立的資料;若未登入,一律被導向登入頁
  3. 在首頁可以瀏覽所有支出清單及總金額
  4. 在首頁時可以根據「類別」篩選支出;且選擇類別時,總金額的計算只包括「被篩選出來的支出總和」
  5. 新增、修改、刪除支出(一次編輯一筆)
Home Page

你為何會選擇這個專案?

此專案是由我自己從零開始打造,主要功能包含:建立伺服器、設計各個路由及顯示頁面、連接資料庫、對資料進行 CRUD、進行使用者驗證等,無論是哪一個功能,都需要對專案需求及技術有相當程度的了解才有辦法完成。因此選擇此專案展示。

你使用了什麼技術?

Server 部分

  • 以 Node.js 及 Express 打造 Server
  • 依循 RESTful 風格設計路由

資料庫部分

  • 使用 MongoDB 資料庫
  • 使用 mongoose 與 MongoDB 連線,並進行資料的增/修/查/改

資料驗證部分

  • 使用 passport.js 作為登入驗證及製作 session
  • 使用 bcrypt 為使用者密碼加密後再儲存 MongoDB

其他部分

  • 使用 Bootstrap 5 框架做整體設計、並套用 FontAwesome 作為 icon
  • 使用 express-handlebars 作為模板設計 (partial templates)
  • 遵循 MVC 架構
  • 使用 Heroku 將專案部署至線上
  • 使用 git 做版本控管

哪部分你相對能掌握?哪裡花了最多時間?

相對能掌握

  • 使用 Bootstrap 5 框架及 FontAwesome
  • 使用 Node.js 及 Express 建立 server
  • 使用 express-handlebars partial templates 來設計頁面並加快開發進度

花了最多時間

  • 資料庫設計:本專案需要 3 個 data model,分別為 expense (每一筆支出)、user (使用者)、category (分類),一開始三個 collections 都只放很基礎的資料,但實作到後來發現,當我們要操作 C/R/U/D 的時候,原本的資料需求不夠,因此在專案的中後期才做調整。
    例如:在首頁,原本我規劃在拿到 expense list 後才把 icon 一筆筆加入顯示,但後來發現這樣可能會花太多時間,應該在「建立」資料的階段就把 icon 一併放入 MongoDB 比較好。
    這些經驗讓我了解「資料庫設計」及「資料關聯」的重要性,其實目前這版本的資料庫設計也還不夠完善,也不利於未來增減功能(也許開放讓使用者自己增減 category?)下次我會在開始製作專案之前,花更多的時間去思考、並和團隊討論資料庫的規劃。
  • Promise 用法及非同步處理:這個專案中,「與資料庫串接」的部分特別需要注意非同步處理,否則很容易出現順序錯亂的問題,因此在處理這部分的程式碼的時候需要花很多心力去搞懂先後順序。
    本專案使用的是 promise 語法來處理,但說實話個人覺得易讀性還不是最高,希望未來可以改用 async/ await 來做調整。

過程中碰到什麼困難?又如何克服?(例如:查找網路文件)

無法在「編輯」頁面中,顯示該筆資料「原本的類別」

因為「類別」是使用下面這種結構來顯示,不像其他的 <input> 可以增加 value 屬性來帶入輸入值,所以要調整起來比較複雜。一開始我沒有管這個 bug ,到了案子很後期才來改,沒想到花了一整個早上才改好囧。

<select>
<option></option>
<option></option>
</select>

整個部分最困難的地方在於:要怎麼讓 express-handlebars 顯示為 selected

handlebars 原生的判斷式很陽春,沒辦法去做 if a > b 則 run function A 之類的步驟,若要自己做也可以,在 handlebars 裡面有開放一個叫做 helper 的功能,可以客製化自己需求。但總覺得相同的困擾應該不止我有,若已經有現成套件不需要自造輪子,因此我最後找到 handlebars-helpers 這個套件。

handlebars-helper 共有 188 個 helper,我最後使用 {{#compare}} ,使用說明如下,翻譯成白話文的意思就是:如果 compare 後面的條件式成立,則顯示;反之則不顯示。

Render a block when a comparison of the first and third arguments returns true. The second argument is the arithemetic operator to use.

所以我的用法是:

{{#compare 此項category的id 等於 該筆資料原本被選的category id}}
selected
{{/compare}}

本來以為事情已經解決,然而實作上又發現另一個困難!因為 category 共有五個,我是採用 express-handlebars 原生的 {{#each}} {{/each}} 寫法來生成這五個 category 的 <option>;若要使用上面的邏輯,這會變成一個巢狀結構,也就是:

{{#each category}}
{{#compare this.id 等於 該筆資料原本被選的category id}}
selected
{{/compare}}
{{/each}}

然而「該筆資料原本被選的category id」卻沒辦法在 {{#each}} {{/each}} 下面被讀到,也就是說這個 {{#compare 此項category的 id 等於 該筆資料原本被選的category id}} 判斷式永遠都是 false。

但因為案子已經到了後期,我不想再做太大的調整,因此最後用了一個有點笨的方法:從資料庫拿到 category 清單 → 把「原本被選的 category id」 加到 category 裡面 → 再把 category 傳給 express-handlebars 使用。

如此一來,就可以在 {{#each}} 迴圈裡面做 {{#compare}} 判斷。實作的程式碼摘出如下:

{{#each category}}<option value={{this._id}} {{#compare this._id "==" this.isSelectedId}}selected{{/compare}}>
{{this.name}}
</option>
{{/each}}

過程中你有對哪個技術有特別深刻的學習?

  1. Passport 的應用:原本對 cookie/ session/ passport 的流程不算特別熟悉,但本次跟著有特別查找了以下幾篇文章:

2. connect-flash 的應用:特別推薦以下這個教學影片(雖然印度口音很重 XD),但看一次就馬上了解 connect-flash 的使用方法了呢~~

--

--