2022 年 4 月 27 日

使用 Remix、Prisma 和 MongoDB 建構全端應用程式:CRUD、篩選和排序

閱讀時間 17 分鐘

歡迎來到本系列的第三篇文章,您將在此學習如何從頭開始使用 MongoDB、Prisma 和 Remix 建構全端應用程式!在本文中,您將建構應用程式的主要部分,該部分會顯示使用者的讚美牆,並允許他們向其他使用者發送讚美。

Build A Fullstack App with Remix, Prisma & MongoDB: CRUD, Filtering & Sorting

目錄

簡介

在本系列的上一部分中,您建構了應用程式的登入和註冊表單,並實作了基於 Session 的身分驗證。您也更新了 Prisma schema,以在 User 模型中加入新的嵌入式文件,用於儲存使用者的個人資料。

在本文中,您將建構應用程式的主要功能:讚美牆。每位使用者都會有一個其他使用者發送給他們的讚美牆。使用者也將能夠向其他使用者發送讚美。

此外,您將實作一些搜尋和篩選功能,以便更輕鬆地在讚美牆中找到讚美。

本專案的起點可在 GitHub 儲存庫的 part-2 分支中找到。如果您想查看本文的最終結果,請前往 part-3 分支。

開發環境

為了跟隨提供的範例,您需要...

注意:選用擴充功能為 Tailwind 和 Prisma 新增了一些非常棒的 IntelliSense 和語法突顯。

建構首頁路由

應用程式的主要部分將位於 /home 路由中。透過在 app/routes 資料夾中新增 home.tsx 檔案來設定該路由。

這個新檔案現在應該匯出一個名為 Home 的函式元件,以及一個 loader 函式,如果使用者未登入,則將其重新導向至登入畫面。

/home 路由將作為應用程式的主要頁面,而不是根 URL。

目前,app/routes/index.tsx 檔案(/ 路由)會呈現 React 元件。該路由應僅重新導向使用者:導向 /home/login 路由。設定一個資源路由來取代它,以達成該功能。

資源路由

資源路由是一種不會呈現元件的路由,但可以改為回應任何類型的回應。可以將其視為簡單的 API 端點。在您的 / 路由案例中,您會希望它傳回 redirect 回應,並帶有 302 狀態碼。

刪除現有的 app/routes/index.tsx 檔案,並將其替換為 index.ts 檔案,您將在其中定義資源路由

注意:檔案的副檔名已變更為 .ts,因為此路由永遠不會呈現元件。

上面的 loader 會在使用者訪問 / 路由時,先檢查使用者是否已登入。如果沒有有效的 Session,requireUserId 函式會重新導向至 /login

如果存在有效的 Session,則 loader 會傳回重新導向至 /home 頁面的 redirect

新增使用者列表面板

從建構一個元件開始您的首頁,該元件將在螢幕左側列出網站的使用者。

app/components 資料夾中建立一個名為 user-panel.tsx 的新檔案

這會建立側邊面板,其中將包含使用者列表。但是,該元件是靜態的,這表示它不會執行任何動作或以任何方式變化。

在透過新增使用者列表使此元件更動態之前,請將其匯入 app/routes/home.tsx 頁面,並將其呈現到頁面上。

上面的程式碼匯入了新元件和 Layout 元件,然後在版面配置中呈現新元件。

查詢所有使用者並排序結果

現在您需要實際在面板中顯示使用者列表。您應該已經有一個檔案用於存放使用者相關的函式:app/utils/user.server.ts

在該檔案中新增一個新函式,用於查詢資料庫中的任何使用者。此函式應接收一個 userId 參數,並依使用者名字以遞增順序排序結果

where 篩選器排除任何 iduserId 參數相符的文件。這將用於抓取每個 user除了目前已登入的使用者

注意:注意到依嵌入式文件中的欄位排序有多容易嗎?

app/routes/home.tsx 中,匯入該新函式並在 loader 中調用它。然後使用 Remix 的 json 輔助函式傳回使用者列表

注意:在 loader 函式中執行的任何程式碼都不會暴露給用戶端程式碼。您可以感謝 Remix 的這項絕佳功能!

如果您的資料庫中有任何使用者,並且在 loader 中輸出了 users 變數,您應該會看到所有使用者(除了您自己)的列表。

注意:整個 profile 嵌入式文件都作為巢狀物件擷取,而無需明確包含它。

您現在將擁有可用的資料。是時候用它做些事情了!

將使用者提供給使用者面板

UserPanel 元件中設定新的 users prop。

此處使用的 User 型別由 Prisma 產生,可透過 Prisma Client 取得。Remix 與 Prisma 配合得非常好,因為在全端框架中實現端對端型別安全非常容易。

注意:當整個技術堆疊中的型別隨著資料形狀的變更而保持同步時,就會發生端對端型別安全。

app/routes/home.tsx 中,您現在可以將使用者提供給 UserPanel 元件。匯入 Remix 提供的 useLoaderData Hook,它讓您可以存取從 loader 函式傳回的任何資料,並使用它來存取 users 資料

元件現在將擁有要使用的 users。現在需要顯示它們。

建構使用者顯示元件

列表項目暫時將顯示為一個圓圈,其中包含使用者名字和姓氏的首字母。

app/components 中建立一個名為 user-circle.tsx 的新檔案,並在其中新增以下元件

此元件使用 Prisma 產生的 Profile 型別,因為您將僅傳入來自 user 文件的 profile 資料。

它還具有一些可配置的選項,可讓您提供點擊動作並新增其他類別來自訂其樣式。

app/components/user-panel.tsx 中,匯入這個新元件,並為每個使用者呈現一個元件,而不是呈現 <p>Users go here</p>

太棒了!您的使用者現在將在首頁左側以漂亮的欄位呈現。此時側邊面板中唯一非功能性的部分是登出按鈕。

新增登出功能

app/routes 中新增另一個名為 logout.ts 的資源路由,該路由在調用時將執行登出動作

此路由處理兩個可能的動作:POST 和 GET

  • POST:這將觸發在本系列上一部分中編寫的 logout 函式。
  • GET:如果發出 GET 請求,使用者將被傳送到首頁。

app/components/user-panel.ts 中,在您的登出按鈕周圍新增一個 form,該表單在提交時將 Post 到此路由。

您的使用者現在可以登出應用程式了!與 POST 請求關聯的 Session 使用者將被登出,並且他們的 Session 將被銷毀。

新增發送讚美功能

當使用者點擊使用者列表中的使用者時,應該彈出一個 Modal,其中提供一個表單。提交此表單將在資料庫中儲存一個讚美。

此表單將具有以下功能

  • 顯示您要給予讚美的使用者。
  • 一個文字區域,您可以在其中填寫給使用者的訊息。
  • 樣式選項,可讓您選擇貼文的背景顏色和文字顏色。
  • 表情符號選擇器,您可以在其中將表情符號新增到貼文中。
  • 準確預覽您的貼文外觀。

更新 Prisma schema

您將儲存和顯示的幾個資料點尚未在您的 schema 中定義。以下是需要變更的列表

  1. 新增一個 Kudo 模型,其中包含一個嵌入式文件,用於儲存樣式自訂設定
  2. User 模型中新增 1:n 關聯,以定義使用者作為作者的讚美。也新增一個類似的關聯,以定義使用者作為接收者的讚美。
  3. 為表情符號、部門和顏色新增 enum,以定義可用的選項。

注意:在將 @default 應用於欄位之後,如果集合中的記錄沒有新的必要欄位,則下次讀取時將更新為包含具有預設值的該欄位。

目前您只需要更新這些。執行 npx prisma db push,這將自動重新產生 PrismaClient

巢狀路由

您將使用巢狀路由來建立將包含表單的 Modal。這將讓您可以設定一個子路由,該子路由將在您定義的 Outlet 上呈現到父路由。

當使用者導航至此巢狀路由時,Modal 將會呈現到螢幕上,而無需重新呈現整個頁面。

若要建立巢狀路由,請先在 app/routes 中新增一個名為 home 的新資料夾。

注意:該資料夾的命名很重要。由於您有一個 home.tsx 檔案,Remix 會將新 home 資料夾中的任何檔案識別為 /home 的子路由。

在新 app/routes/home 目錄中,建立一個名為 kudo.$userId.tsx 的新檔案。這將讓您可以像處理自己的路由一樣處理 Modal 元件。

此檔案名稱的 $userId 部分是路由參數,它充當您可以透過 URL 提供給應用程式的動態值。然後 Remix 會將該檔案名稱轉換為路由:/home/kudos/$userId,其中 $userId 可以是任何值。

在該新檔案中,匯出一個 loader 函式和一個呈現一些文字的 React 元件,以確保動態值正常運作

上面的程式碼執行了幾件事

  1. 它從 loader 函式中提取 params 欄位。
  2. 然後它抓取 userId 值。
  3. 最後,它使用 Remix 的 userLoaderData Hook 從 loader 函式中擷取資料,並將 userId 呈現到螢幕上。

由於這是巢狀路由,為了顯示它,您需要定義路由應在其父路由中輸出的位置。

使用 Remix 的 Outlet 元件來指定您希望子路由呈現為 app/routes/home.tsxLayout 元件的直接子元件

如果您前往 https://127.0.0.1:3000/home/kudo/123,您現在應該會在頁面最上方看到文字「User: 123」。如果您將 URL 中的值變更為 123 以外的值,您應該會看到該變更反映在螢幕上。

透過 ID 取得使用者

您的巢狀路由正在運作,但您仍然需要使用 userId 擷取使用者的資料。在 app/utils/user.server.ts 中建立一個新函式,該函式會根據使用者的 id 傳回單一使用者

上面的查詢在資料庫中找到具有給定 id 的唯一記錄。findUnique 函式可讓您使用唯一識別欄位或欄位值必須在資料庫中對該記錄唯一來篩選查詢。

下一步

  1. app/routes/home/kudo.$userId.tsx 匯出的 loader 中調用該函式。
  2. 使用 json 函式從該 loader 傳回結果。

接下來,您需要一種導航到具有有效 id 的巢狀路由的方法。

app/components/user-panel.tsx 中,您正在其中呈現使用者列表的檔案中,匯入 Remix 提供的 useNavigation Hook,並在點擊使用者時使用它導航到巢狀路由。

現在,當您的使用者點擊該面板中的另一個使用者時,他們將導航到具有該使用者資訊的子路由。

如果一切看起來都不錯,則下一步是建構將顯示表單的 Modal 元件。

開啟 Portal

若要建構此 Modal,您首先需要建構一個輔助元件,該元件會建立一個 Portal,可讓您在父元件的文件物件模型 (DOM) 分支外部的某個位置呈現子元件,同時仍允許父元件像管理直接子元件一樣管理它。

注意:此 Portal 非常重要,因為它可讓您在沒有任何繼承自父元件的樣式或定位的位置呈現 Modal,這可能會影響 Modal 的定位。

app/components 中建立一個名為 portal.tsx 的新檔案,其中包含以下內容

以下是此元件中正在發生的事情的說明

  1. 定義了一個函式,用於產生一個帶有 iddiv。然後將該元素附加到文件的 body
  2. 如果具有提供的 id 的元素尚不存在,則調用 createWrapper 函式以建立一個。
  3. Portal 元件卸載時,這將銷毀元素。
  4. 建立指向新產生的 div 的 Portal。

結果是,包裝在此 Portal 中的任何元素或元件都將呈現為 body 標籤的直接子元件,而不是在目前的 DOM 分支中作為其父元件的子元件。

試試看以查看它的實際運作情況。在 app/routes/home/kudos.$userId.tsx 中,匯入新的 Portal 元件,並使用它包裝傳回的元件

如果您導航到巢狀路由,您將看到一個 div,其 id"kudo-modal",現在呈現為 body 的直接子元件,而不是巢狀路由在 DOM 樹狀結構中呈現的位置。

建構 Modal 元件

現在您已經有一個安全的 Portal,開始建構 Modal 元件本身。此應用程式中將有兩個 Modal,因此以可重複使用的方式建構元件。

app/components/modal.tsx 建立一個新檔案。此檔案應匯出一個具有以下 Props 的元件

  • children:要在 Modal 中呈現的元素。
  • isOpen:一個旗標,用於判斷是否正在顯示 Modal。
  • ariaLabel(選用)要用作 Aria 標籤的字串。
  • className(選用)一個字串,可讓您將其他類別新增至 Modal 的內容。

新增以下程式碼以建立 Modal 元件

匯入了 Portal 元件,並包裝了整個 Modal,以確保它呈現於安全的位置。

然後使用各種 TailwindCSS 輔助程式,將 Modal 定義為螢幕上的固定元素,並帶有不透明的背景幕。

當點擊背景幕(Modal 本身以外的任何位置)時,使用者將導航到 /home 路由,導致 Modal 關閉。

建構表單

app/routes/home/kudo.$userId.tsx 中,匯入新的 Modal 元件,並呈現 Modal 而不是目前正在呈現的 Portal

現在,當點擊側邊面板中的使用者時,Modal 應該會開啟。

您的表單在顯示訊息預覽時需要已登入使用者的資訊,因此在建構表單之前,請將該資料新增至 loader 函式的回應

然後對該檔案中的 KudoModal 函式進行以下變更

這是一大段新程式碼,因此請查看進行了哪些變更

  1. 匯入您將需要的一些元件和 Hook。
  2. 設定您將需要處理表單資料和錯誤的各種表單變數。
  3. 建立將處理輸入變更的函式。
  4. 呈現表單元件的基本版面配置,以取代之前的 <h2> 標籤。

允許使用者自訂讚美

此表單還需要允許使用者使用選取方塊選擇自訂樣式。

app/components 中建立一個名為 select-box.tsx 的新檔案,該檔案匯出 SelectBox 元件

此元件與 FormField 元件類似,因為它是一個受控元件,它接收一些配置並允許其狀態由其父元件管理。

這些選取方塊將需要填入顏色和表情符號選項。建立一個輔助檔案以在 app/utils/constants.ts 中儲存可能的選項

現在在 app/routes/home/kudo.$userId.tsx 中,匯入 SelectBox 元件和常數。也新增將它們連接到表單狀態所需的變數和函式,並呈現 SelectBox 元件以取代 {/* Select Boxes Go Here */} 註解

選取方塊現在將會出現所有可能的選項。

新增讚美顯示元件

此表單將有一個預覽區段,使用者可以在其中看到收件者將看到的元件的實際呈現。

app/components 中建立一個名為 kudo.tsx 的新檔案

此元件接收 Props

  • profile:來自收件者 user 文件的 profile 資料。
  • kudoKudo 的資料和樣式選項。

匯入包含顏色和表情符號選項的常數,並用於呈現自訂樣式。

您現在可以將此元件匯入 app/routes/home/kudo.$userId.tsx,並將其呈現以取代 {/* The Preview Goes Here */} 註解

現在將呈現預覽,顯示目前已登入使用者的資訊以及他們要發送的樣式訊息。

建構發送讚美的 Action

表單現在視覺上已完整,剩下的部分就是讓它能夠運作!

app/utils 中建立一個名為 kudos.server.ts 的新檔案,您將在其中編寫任何與查詢或儲存讚美相關的函數。

在這個檔案中,匯出一個 createKudo 方法,該方法接收讚美表單資料、作者的 id 和接收者的 id。然後使用 Prisma 儲存這些資料

上面的查詢執行以下操作

  1. 傳入 message 字串和 style 嵌入式文件。
  2. 使用傳遞給函數的 id,將新的讚美連結到適當的作者接收者

將這個新函數匯入到 app/routes/home/kudo.$userId.tsx 檔案中,並建立一個 action 函數來處理表單資料和 createKudo 函數的調用

以下是上面程式碼片段的概觀

  1. 匯入新的 createKudo 函數,以及一些由 Prisma 生成的類型、Remix 的 ActionFunction 類型,以及您先前編寫的 requireUserId 函數。
  2. 從請求中提取所有您需要的表單資料和欄位。
  3. 驗證所有表單資料,並將適當的錯誤訊息回傳到表單以顯示,如果發生錯誤。
  4. 使用 createKudo 函數建立新的 kudo
  5. 將使用者重新導向到 /home 路由,導致彈窗關閉。

建立讚美訊息流

現在您的使用者可以互相發送讚美了,您將需要一種方式在使用者首頁 (/home 頁面) 上顯示這些讚美。

您已經建立了讚美顯示元件,因此您只需要檢索並呈現首頁上的讚美列表即可。

app/utils/kudos.server.ts 中建立並匯出一個名為 getFilteredKudos 的新函數。

上面的函數接收幾個不同的參數。以下是這些參數

  • userId:使用者的 id,查詢應檢索該使用者的讚美。
  • sortFilter:一個物件,將傳遞到查詢中的 orderBy 選項,以對結果進行排序。
  • whereFilter:一個物件,將傳遞到查詢中的 where 選項,以篩選結果。

注意:Prisma 生成的類型可用於安全地為您的查詢片段進行類型標註,例如上面使用的 Prisma.KudoWhereInput

現在在 app/routes/home.tsx 中,匯入該函數並在 loader 函數中調用它。同時匯入 Kudo 元件和呈現讚美訊息流所需的類型。

由 Prisma 生成的 KudoProfile 類型被組合起來創建一個 KudoWithProfile 類型。這是必需的,因為您的陣列包含來自作者個人資料資料的讚美。

如果您向一個帳戶發送幾個讚美並登錄該帳戶,您現在應該會在您的訊息流中看到呈現的讚美列表。

您可能會注意到當 getFilteredKudos 調用為排序和篩選選項提供空物件時。這是因為 UI 中尚沒有篩選或排序訊息流的方式。接下來,您將在訊息流頂部建立搜尋列來處理這個問題。

app/components 中建立一個名為 search-bar.tsx 的新檔案。這個元件將向 /home 頁面提交一個表單,傳遞查詢參數,這些參數將用於建立您需要的排序和篩選物件。

在上面的程式碼中,新增了一個 inputbutton 來處理文字篩選和搜尋參數的提交。

當 URL 中存在 filter 變數時,按鈕將變更為「清除篩選器」按鈕,而不是「搜尋」按鈕。

將該檔案匯入到 app/routes/home.tsx 中,並將其呈現在 {/* Search Bar Goes Here */} 註解的位置。

這些變更將處理訊息流的篩選,但是您也希望按各種欄位對訊息流進行排序。

app/utils/constants.ts 中新增一個 sortOptions 常數,用於定義可用的欄位。

現在將該常數和 SelectBox 元件匯入到 app/components/search-bar.tsx 檔案中,並在 button 元素之前呈現帶有這些選項的 SelectBox

現在您應該會在搜尋列中看到一個帶有選項的下拉選單。

建立搜尋列動作

當提交搜尋表單時,將向 /home 發出 GET 請求,篩選器和排序資料將在 URL 中傳遞。在 app/routes/home.tsx 匯出的 loader 函數中,從 URL 中提取 sortfilter 資料,並使用結果建立查詢

上面的程式碼

  1. 提取 URL 參數。
  2. 建立一個 sortOptions 物件,以傳遞到您的 Prisma 查詢中,該物件可能會根據 URL 中傳遞的資料而有所不同。
  3. 建立一個 textFilter 物件,以傳遞到您的 Prisma 查詢中,該物件可能會根據 URL 中傳遞的資料而有所不同。
  4. 更新 getFilteredKudos 調用,以包含新的篩選器。

現在,如果您提交表單,您應該會在訊息流中看到反映的結果!

顯示最近的讚美

您的訊息流需要的最後一件事是一種顯示最近發送的讚美的方式。這個元件將為最近收到讚美的前三名接收者顯示一個 UserCircle 元件。

app/components 中建立一個名為 recent-bar.tsx 的新檔案,程式碼如下

這個元件接收最近三個讚美的列表,並將它們呈現到一個面板中。

現在您需要編寫一個查詢來抓取該資料。在 app/utils/kudos.server.ts 中新增一個名為 getRecentKudos 的函數,該函數返回以下查詢

這個查詢

  1. createdAt遞減順序對結果進行排序,以取得從最新到最舊的記錄。
  2. 僅從該列表中取得前三個,以取得三個最新的文件。

現在您需要

  • RecentBar 元件和 getRecentKudos 函數匯入到 app/routes/home.tsx 檔案中。
  • 在該檔案的 loader 函數中調用 getRecentKudos
  • RecentBar 呈現在首頁上,以取代 {/* Recent Kudos Goes Here */} 註解的位置。

這樣一來,您的首頁就完成了,您應該會在您的應用程式中看到最近發送的三個讚美的列表!

摘要 & 接下來的內容

在本文中,您建立了此應用程式的主要功能,並在此過程中學習了很多概念,包括

  • 在 Remix 中重新導向
  • 使用資源路由
  • 使用 Prisma Client 篩選和排序資料
  • 在您的 Prisma Schema 中使用嵌入式文件
  • ... 以及更多!

在本系列的下一節中,您將完成此應用程式,方法是建立網站的個人資料設定部分,並建立一個圖片上傳元件來管理個人資料圖片。

不要錯過下一篇文章!

註冊 Prisma 電子報