[NodeJS Become A Full Stack Developer] — 菜雞必經之路 👉 實作一個 Todo List

Todo List

Introduction & 前言

這一篇文章會快速介紹一下筆者在上 六角學院 - NodeJS 前後端開發實戰 課程時,章節練習的小作業,透過 NodejsFirebase 寫一個菜鳥都必須要練習過的題目 Todo List

本次 NodejsTemplate 使用為 **PUG(Jade)**。


Summary & 摘要

Demo

有興趣跟筆者一起從後端窺探可以參考前幾篇文章 [NodeJS Become A Full Stack Developer] — 從0開始 NodeJS 小試身手 及 **[NodeJS Become A Full Stack Developer] — Express、Router 及 EJS 入門淺談**。

本次作業練習將會使用 ExpressFirebase 實作。


整體規劃

其實這次作業是希望我們做形象官網,但想了一下還是想從待辦清單下手,也可以串接 Firebase 的登入驗證,不需要在自己寫一個會員系統。

而這次路由也規劃拆成 routes -> controllers -> models**(MVC架構)**,一方面好維護,一方面檔案也不會那麼亂,比較清爽。

在前篇文章 [NodeJS Become A Full Stack Developer] — Express、Router 及 EJS 入門淺談 有提到如何使用 Express 快速建立一個專案環境,但資料夾架構怎麼放就是看個人了,這邊筆者拆成自己比較容易理解且好處理的架構。

大致架構

由上至下大概為:

  1. config -> 放第三方服務的設定

  2. controllers -> ModelsRouter 的橋樑

  3. middleware -> 中介層(可用來驗證…等等)

  4. models -> 與資料庫的橋樑

  5. node_modules -> 套件庫

  6. public -> 靜態資源放置區

  7. routes -> 路由

  8. views -> 靜態頁面

  9. env -> 環境變數

還有一些常見到的就不特別介紹了


大致功能

這次筆者做一個比較簡單的待辦事項,可以簡單新增編輯刪除,其實這些透過 Firebase 都可以很輕易地做到,而路由安排也是簡單的指定並渲染,其中有使用到 connect-flash,這是可以將一些資訊暫存在 session,好處是刷新後只會顯示一次,之後就會消失,不會再留存在我們的記憶體內,省空間!

使用方式也很簡單,路由需要用到的地方,只要使用以下方法即可:

1
2
3
4
5
// 這個方法會將 '錯誤資訊' 暫時存放再 session
req.flash('error', '錯誤資訊');

// 透過這個方法可將剛剛暫存的資料拿出來,但也將會消失在暫存中
req.flash('error');

模板題外話

此外這邊透過 pug 渲染,在之前前端使用 pug 時幾乎不會用到變數這功能,而這次接觸到了怎麼將變數渲染到頁面上,在後端透過下列方式將值放進變數裡:

1
2
3
4
5
6
// /controllers/index.js 111行
res.render('editTask', {
title: 'Edit Task',
error: req.flash('error'),
title_val: title,
});

接著在前端頁面使用下面的方式渲染出來:

1
2
3
4
5
6
7
8
//- /views/editTask.pug 36行
input#email.form-control(
type='text',
name="title",
placeholder="Title",
autocomplete,
value=`${title_val ? title_val : ''}`
)

而如果碰到陣列要渲染就透過下面的方式渲染:

1
2
3
4
5
6
7
//- /views/editTask.pug 13行
if error
each val,index in error
.alert.alert-danger.alert-dismissible.fade.show.animate__animated.animate__shakeX(
role="alert"
)
strong= val.msg

關於驗證

事前準備

這邊使用 express-validator-v6.6.1 作為驗證,一般驗證我們不止透過前端驗證,後端也需要驗證,而這邊使用這個套件也提供了很簡單的驗證方式。

1
2
// 安裝套件
$ npm install express-validator
1
2
3
// 引入套件 
const { check, body, cookie, header, param } = require('express-validator')
// 更多引入方法可查看 https://express-validator.github.io/docs/check-api.html

套件提供了幾種在後端驗證資料的讀取方式:

  1. check([field, message])
1
2
//- 驗證的欄位為 email
input(type="email", name="email")

這個方法會從 req.body、req.cookies、req.headers、req.param、req.query 去尋找這個名稱的欄位資料,如果有重複的名稱,則都會驗證!

  1. check 剩下其他的驗證函示

其他的驗證函示用法幾乎差不多,只是在尋找這個欄位的地方會不一樣,body 只會從 req.body 尋找、header 只會從 req.headers 尋找…等等

簡單的功能

其實我們最主要會用到的功能為 notEmpty(),檢查是否為空,而用法如下:

1
2
3
4
5
6
// /routes/index.js 24行
router.post('/editTask/:id', checkAuth.verifyAuth, [
check('title', 'Title is required').notEmpty(),
check('expiry', 'Expiry is required').notEmpty(),
check('content', 'Content is required').notEmpty(),
], indexController.postEditTask);

上方會尋找各個欄位名稱為 titleexpirycontent 的值,檢查是否為空,如果為空就返回提示文字,更多方法可查看 validator.js

回傳錯誤訊息

而我們如何將錯誤訊息回傳呢?在我們驗證錯誤之後,可以在驗證的檔案最上方引入 const { validationResult } = require('express-validator');,接著向下方的方式即可拿到錯誤資訊:

1
2
3
4
// /controllers/index.js 50行
postNewTask: (req, res) => {
const errors = validationResult(req);
}

這樣即可拿到錯誤資訊,如果是空的呢?就是沒有檢查到錯誤,接著透過上面提到的 connect-flash 將錯誤資訊放在暫存中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// /controllers/index.js 50行
postNewTask: (req, res) => {
const errors = validationResult(req);
const { title, expiry, content } = req.body;

if (!errors.isEmpty()) {
// 如果有錯誤
req.flash('error', errors.errors);
req.flash('title_val', title);
req.flash('expiry_val', expiry);
req.flash('content_val', content);
res.redirect('/newTask');
} else if(errors.isEmpty()) {
// 如果沒錯誤 Do something...
}

上方可以看到筆者也在 req.body 拿出了相關資訊,因為我們可能會在某一個欄位輸入錯誤,但其他欄位可能是正確的,這時候就必須將原本的值帶回,提升使用者體驗!

錯誤提示


中介層

這次的練習也透過 express-session 將登入權杖存在 session 並且前端無法透過開發者工具取得,另外也把登入後 Firebase 的相關資訊存在 session,方法如下:

1
2
// app.js 5行
var session = require('express-session');

確保有引入套件後在使用下面的方法拿取 Token:

1
2
3
4
5
6
7
8
9
10
11
// app.js 24行
app.use(session({
secret: 'todo list',
resave: true,
rolling: true,
saveUninitialized: true,
cookie: {
httpOnly: true,
maxAge: 10*10*10000 // 過期時間
}
}))

接著在登入完成後將相關的資訊存在 session:

1
2
3
4
5
// /controllers/user.js 32行
req.session.auth = {
uid: response.user.uid,
email: response.user.email
}

然後再寫一支中介層檔案,內容如下:

1
2
3
4
5
6
7
8
9
10
// /middleware/auth.js 1行
module.exports = {
verifyAuth: (req, res, next) => {
if(!req.sessionID || !req.session.auth || !req.session.auth.uid || !req.session.auth.email) {
res.redirect('users/login');
} else {
next();
}
}
}

這樣一來就能在想要驗證的路由上使用了,先在要使用的地方引入中介層檔案 const checkAuth = require('../middleware/auth');,接著即可使用:

中介層

如果有通過驗證才會 next() 其他會倒回登入頁面!


範例

按照慣例這邊附上實作好的 Demo:

GitHub: 傳送門

Demo: 傳送門

因為是使用 Heroku 免費方案,如果太久沒有人打開網頁,打開需要點時間喚醒,請見諒


Conclusion & 結論

本篇其實沒有什麼技術成分在,但純分享一下自己小作業做出來的成果,我想還有許多需要改進,**六角學院 - NodeJS 前後端開發實戰** 課程也還沒完成,這邊要在繼續惡補了!


參考網站