Simple Twitter Project: building a CRUD web app with Node.js, Express and MySQL

楊雅婷(Yang Ya-Ting)
Yang Yating
Published in
13 min readJan 6, 2021

Alpha Camp全端課程終於進入最終階段:「復刻twitter」。此專案是所有Alpha Camp畢業生的共同回憶,也是AC學生第一個四人協作的專案。

團體協作是轉職路上比較難可以練習的情境,一方面是身邊轉換跑道的朋友並不多,而要找到同樣轉職且願意一起做專案的又是少數,感謝AC提供機會,讓我可以認識Jim, Ahno, Jonny,一起完成twitter專案。

什麼是twitter專案?

前端GitHub:https://github.com/r05323045/twitter-api-2020-frontend
後端GitHub:https://github.com/iamy8000/twitter-api-2020
Demo頁面:https://r05323045.github.io/twitter-api-2020-frontend/#/signin
測試帳號:帳號為@user2, 密碼為12345678

我們的任務是需要透過全端或前後分離的協作方式,在兩週內架構出具有完整CRUD功能的twitter頁面,使用者可以:

  • 登入、登出、註冊新帳號
  • 編輯自己的 account、name、email 和 password
  • 編輯自己的自我介紹、個人頭像與封面
  • 新增、按讚、留言、閱讀推文
  • 追蹤、取消追蹤其他使用者
  • 看到追蹤人數前十名的使用者
  • 查看其他使用者的個人頁面
  • 在公開聊天室與所有在線使用者聊天
  • 與特定使用者進行一對一的私人訊息

為什麼採前後分離的開發模式?我扮演什麼角色?

從學期一到三多數的專案都是一人團隊開發所有功能,也都是以全端角度去開發,一個人可以一直狂衝、埋頭苦幹,git commit可以按照自己習慣方式命名、coding style也依照自己的方式去刻,不需要顧慮其他人,一個人自由自在。因為目前專案規模很小,可以一個人cover全部,不過當專案架構變大,需要有夥伴一起協作開發時,協作能力變得非常重要。

因此在學期三第一次接觸到前後分離的開發模式,就決定在AC的最後一項專案要跳拖原本熟悉的全端開發模式,轉為需要共同協作的前後分離模式,藉此開始熟悉團隊協作的開發。

我們四人一組,前後端各二,我和Ahno負責後端API開發,主要使用Node.js+Express.js,Jim與Johnny則使用Vue框架開發前端畫面,而Jim原本也是參與全端的課程,不過他自己有在Udemy自修過Vue框架,因此在此專案就投身於前端開發了。Jim前後都有學、也都有實作過,所以溝通上他知道後端路由會怎麼串、前端會怎麼拿,可以直接用後端語言與我和Ahno討論,溝通效率提升不少,是前後端重要的溝通橋樑。

挑選協作工具,讓所有人都on the same page

好的協作工具可以幫助團體順暢的一起工作,也可一目瞭然的明瞭專案開發內容、各自負責項目,更可以追蹤開發進度,回歸工具的挑選,因為開發期短且Google Sheet是最快可以上手的平台,因此便沒有再另外使用slack或troll等。

與隊友溝通主要都還是line,即時的討論遇到的bug、路由規格等,有遇到卡超過三個小時的問題會丟Line尋求隊友協助,隊友也願意一起把問題丟出來討論,創造雙向的溝通氛圍,我很appriciate隊友的態度。

專案啟動的第一天,Ahno速度很快的建了Google Sheet表、Jim整理了一份PPT檔案,統合UIUX畫面與對應的API,協助大家對焦接下來所需的路由開發和畫面元件。日後的開發,我們都透過此表,很清楚的看到各自開發的進度與狀況,也可以透過備註讓其他開發者知道此路由/功能需注意的事項,等於是這個專案的checklist。

感謝Ahno
感謝Jim

後端如何分工?

後端開發主要分兩個部分:共同環境的架設+路由開發。共同環境的架設包含所有會共用的設定,例如:passport設定、model的建立、套件的安裝等等,路由開發則是根據controlller以及model切分,因此上表中的橘色部分會由我開發(部分的userController以及followController),藍色則交由Ahno。

Ahno很快的把後端大環境建立好(seeder, 第三方元件等),包含統整後端的API路由在Google Sheet上,這大概都在一個晚上發生完畢,效率超級高!我再來接手把使用者登入的passport完成。各自PR到GitHub後,沒問題就合併至master去,再各自pull下來所有專案初始化所需的設定,接著就開始各自路由的開發了。

PR和git pull都沒遇到很嚴重的程式碼衝突,可能是因為我們都很怕會線圖大爆炸,所以在上傳前、或是動手更改共同設定前,都會先在line與隊友討論,隊友也可以先在本地做相應的調整,避免PR產生衝突,算是本次開發比較幸運的部分。

另外因為隊友的開發時間都不太一定,我習慣半夜工作,Ahno主要都是在白天進行,為了可以及時協助隊友處理PR,意外發現GitHub有出IOS的App,而且介面非常好用(激推)!因此就算手邊沒有電腦,也可以透過手機即時approve PR並且merge到master上。

開發了哪些功能?

時程規劃上,後端Day-4前需將所有路由功能開發完成,我負責的路由功能與使用者的登入驗證、註冊、取得使用者資料(從資料庫撈使用者名稱、email、頭貼、帳號、粉絲清單、追蹤名單等基本資料)、更新使用者資料,以及使用者追蹤&退追蹤其他使用者功能,透過Sequelize ORM來操作MySQL資料庫。後端開發相較於前端單純許多,功能的寫法可以參考餐廳論壇,即使如此,在開發上也遇到的不少挑戰。

挑戰1:本地跑測試時,多條路由報錯 Cannot read property “()”of null/undefined

遇到的問題:路由在postman測試功能一切正常,一樣的流程在跑本地測試時卻噴錯!測試檔的測試邏輯為,先在database建立一個使用者以及一組要測試的資料(例如:建立一篇貼文、建立一則留言等等),接著跑該功能對應的API,透過controller撈到目標資料後,再與一開始建立的資料做對比,而測試時報錯的地方,都剛好在最後將資料庫資料傳入測試檔的地方,因此判斷報錯是因為資料沒有成功傳進去,所以就會顯示undefined或null。

解決問題的流程

  1. 在每個節點放console.log():假如說路由的傳輸途徑是A→B→C,那我就在每個action後都放console.log,看資料傳到哪邊就消失、沒繼續傳下去,找到關鍵的那行後,就前後推敲,看是哪邊的設定出了問題,可能是回傳的資料包裝不對,或者是model少定義了什麼。
  2. 將回傳的data印出來:測試的最後一步是對照資料庫與一開始存進去的資料是否相同,因此回傳的格式也存在一些眉角。有時候資料其實沒有消失在程式內,而是測試檔指定的資料格式與回傳不符合,例如:測試檔需要將res.body[0]與原先資料做比對,如果在res.body多包了一層[ ],那res.body[0]就無法取到正確的內容,諸如此類需要多注意的細節也是造成測試沒過的原因。雖然事後回想都是很簡單的地方,但過程中卻也花費兩三個小時在找,一個坑都是一次的學習。

挑戰2:passport需使用函式包裝,跑測試才能通過身份驗證

遇到的問題:登入驗證功能使用passport+token,一樣在postman測試時都沒問題,偏偏在本地跑npm run test時,又再度噴錯誤:Error: expected 200 “OK”, got 401 “Unauthorized”。

解決問題的流程:

在line詢問其他同學的同時,Google關鍵字打「node.js mocha 測試 passport」,找到應該是AC學長的文章,雖然內容主要分享如何寫測試檔,不過也找到學長對於passport身份驗證的解方。與隊友討論後,綜合其他組的後端同學分享(感謝kelly!),將passport用函式封裝起來,如此一來測試時才能複寫並模擬通過驗證。

雖然調整不多,但摸索出需要用函式封裝這步,也是花了好多時間。一開始是自己埋頭苦找,後來花了兩三個小時還是解決不了,就果斷尋求隊友協助,大家一起集思廣益,設定一個停損點也是蠻重要的。

//before
const authenticated = passport.authenticate('jwt', { session: false })
//after
const authenticated = function (req, res, next) {
passport.authenticate("jwt", { session: false }, (err, user, info) => {
if (!user) {
return res
.status(401)
.json({ status: "error", message: "No auth token" })
}
req.user = user
return next()
})(req, res, next)
}

挑戰3:使用者登入時帳號需區分大小寫

遇到的問題:MySQL資料庫所使用的字符集是不區分大小寫的,因此使用者登入時輸入的帳號@user1以及@USer1對資料庫來說都是一樣的。我們希望登入時帳號驗證能區分大小寫,讓驗證過程更加嚴謹一些。

解決問題的流程

  1. 目前使用的字元集為何? MySQL資料庫所使用的字元集為utf8mb4_unicode_ci,ci結尾是不區分大小寫的。
  2. 改為utf8mb4_unicode_cs可能產生的影響? cs的確可以協助使用者登入時區分帳號大小寫,但考量之後若有想做搜尋使用者的功能,區分大小寫在搜尋上會不太友善,因此決定繼續使用原本的字元集。
  3. 在不改動字元集的前提下,查詢資料時,如何做到區分大小寫? 在stackOverflow參考許多不同語法,發現將查詢語句中欄位名稱前加上關鍵字「BINARY 」 ,就可區分大小寫。因此我將原本的User.findOne({ where: { account: account } }),使用SQL 語法改寫,因為是第一次接觸到在sequelize ORM寫SQL語法,因此有花了一點時間研究,一方面有參考了組員在寫admin時的寫法,以及網友在實作區分大小寫的語法,再搭配sequelize.where以及sequelize.literal,成功實現登入時會區分帳號大小寫。
  4. 心得: 在餐廳論壇中,字元集的設定主要參考AC建議,並沒有深究背後的原因,直到這次的事件,才去好好認識每個字元集的發展歷史,以及差異,也才知道了解一開始設定utf8mb4_unicode_ci背後的原因。在尋找適合語法時,也花了一段時間才找到「Binary」這個關鍵字,原本想像很單純的設定,卻也花了不少時間才找到答案,不過透過這過程,也才更了解資料庫編碼與字元的運作。

兩週密集溝通,初次團隊協作順利落幕

初次的團隊協作算是告了一段落,許久沒有協作的我,有種重回往日需要多方溝通推進項目的既視感,不過所幸遇到厲害又有責任感的隊友們,讓我從這次經驗學習到許多。

以終(測試)為始

很喜歡bernard校長在開學相見歡分享的一句話:以終為始。不論在做什麼是之前,先搞清楚目標,然後一步步回推需要做的action step,例如:在轉職時,先去研究夢幻公司的職位和JD,再往回推現在應該採取的行動,我覺得同樣的道理也可以套用在寫程式上。

這次後端的目標是開發路由,並且要通過本地測試。因此不論是在路由命名上或是開發功能時,都應該先確認測試檔的規格,知道要怎麼命名、controller內部要怎麼寫、要回傳什麼樣的資料等等,然後才是著手開發。或許會花比較多時間在planning,但比起一頭熱地去開發,然後才回頭debug,反而一開始就清楚遊戲規格或許也可節省開發時間。

協作時程抓得更鬆一些

作為團體協作的第一個專案,大家都很希望可以把專案做好,希望除了指定規格以外,還能夠加入自己想做的功能作為優化,同時AC也媒合一位資深前端工程師的助教來協助我們的開發,並且有一個小時的office hour可以與助教討論。因此時程上壓得比較緊,後端需在第一週的Day-4前開發完所有的API,D5–7則交由前端串畫面,因此照這個安排,D8就可以與助教進行office hour,討論專案可優化的部分,儘早收到助教feedback,留多一點時間給內部做調整、優化。

不過前端所需的時間比一開始預估的還要長一些,因此D8-D10畫面只串起來大約1/3,而第二週的後端其實除了做程式碼的優化、排除bug和研究挑戰功能的socket之外,都處在有點束手無策的狀態,想幫前端把進度撿起來,卻又幫不上忙。最後的解方是Jim請一天假,在死線當天以高鐵速度開發,趕在最後一刻交出去!

透過這次的經驗,深刻學習到時程真的不能抓太夢幻,或者是說,時程出來後,要隨時的與隊友確認開發狀況,以便做臨時的調整,倘若遇到開發進度delay的狀況,也要設一個停損點,若超過時間的話,就要立刻著手想替代方案。

感謝隊友互相carry

大家都沒有協作開發的經驗,因此這次專案都是邊摸索邊前進。第一次的線上meeting原本是想要做個簡單的破冰,沒想到自我介紹完之後,就直接開始討論大家最關心的議題:要怎麼分工(氣氛瞬間變沈重哈哈哈),而且相互沈默了超久。彼此都怕會寫不完,但又不知道要怎麼開始,更不要說是分工了,當下真的胃很痛~不過好險後來還是慢慢有討論出來,也順利切分工作,並且切的也還蠻乾淨(再次感謝Ahno的遠見!),所以在git push, git pull都沒有遇到太多的問題。我們也有討論branch要如何命名等等,盡量在下一步動作前,先達成彼此的共識。深刻體會到事前完整的溝通真的可以省掉後面很多不必要的git merge衝突,也感謝隊友都願意即時的回覆、有超強執行能力、高效率寫程式(ahno甚至比我早一天就把路由都開發完了!),讓後端的合作在開發基本盤功能上都可順順前行。

基本功能開發完後的假日,更相約在路易莎寫挑戰提功能,這部分想大力感謝我的隊友們,因為排班限制,所以我假日都只能參與到早上的部分,他們不僅兩天內摸索出socket的黑魔法,更成功做出公開聊天室功能!!Amazing!!

--

--

楊雅婷(Yang Ya-Ting)
Yang Yating

Information Management Grad Student @ University of Maryland, College Park|Full-Stack Engineer