
用 Wasp 構建 Trello 風格看板應用Waspello 全棧實現與實戰解析【免費下載鏈接】waspThe batteries-included full-stack framework for the AI era. Develop JS/TS web apps (React, Node.js, and Prisma) using declarative code that abstracts away complex full-stack features like auth, background jobs, RPC, email sending, end-to-end type safety, single-command deployment, and more.項目地址: https://gitcode.com/GitHub_Trending/wa/waspWaspello 是 Wasp 官方倉庫中一個 Trello 風格的看板Kanban Board應用示例它演示了如何用極少的樣板代碼構建一個中等復雜度的多用戶實時應用——認證、查詢與操作大多只在一個聲明式的main.wasp.ts文件中完成。本文將以 examples/waspello/README.md 為骨架結合倉庫源碼深入講解 Waspello 的聲明式配置、數據模型、操作實現、拖放排序原理、本地開發流程與端到端測試幫助你掌握用 Wasp 快速落地一個真實全棧應用的整體套路。項目概覽Waspello 做了什么Waspello 是一個典型的 Trello 風格應用支持多用戶使用核心能力包括郵箱 / 密碼認證并額外配置了 Google 社交登錄多個看板每個看板包含若干列表List與卡片Card跨用戶實時更新——查詢Query在數據被修改Mutation后自動失效并重新拉取列表與卡片的拖放重排卡片上的圖片附件。官方演示環境將前端部署在 Netlify、后端部署在 Fly.io但本地開發完全基于 Wasp CLI 完成。值得注意的是整個應用的主要邏輯聲明集中在一個文件 examples/waspello/main.wasp.ts 中這正是 Wasp“聲明式全?!崩砟畹闹苯芋w現。聲明式核心一個文件定義認證、路由與應用配置Waspello 使用 Wasp 的 TypeScript Specwasp.sh/spec來聲明應用。打開 examples/waspello/main.wasp.ts可以看到整個應用的骨架import { app, page, route } from wasp.sh/spec; import { readFile } from fs/promises; import MainPage from ./src/cards/MainPage with { type: ref }; import Layout from ./src/Layout with { type: ref }; import { authSpec } from ./src/auth/auth.wasp; import { cardsSpec } from ./src/cards/cards.wasp; export default app({ name: waspello, wasp: { version: 0.26.0 }, title: (await readFile(appTitle.txt, utf-8)).trim(), auth: { userEntity: User, methods: { usernameAndPassword: {}, google: {}, }, onAuthFailedRedirectTo: /login, }, client: { rootComponent: Layout, }, spec: [ route(MainRoute, /, page(MainPage, { authRequired: true })), authSpec, cardsSpec, ], });逐項解讀這份聲明name與wasp.version定義應用名waspello以及聲明所要求的最低 Wasp 版本0.26.0這保證了聲明語法與生成器行為的兼容性title從項目根目錄的appTitle.txt讀取應用標題演示了在聲明階段讀取本地文件的用法auth聲明認證配置——用戶實體為User啟用usernameAndPassword用戶名 密碼與googleGoogle OAuth兩種方式并設置認證失敗時重定向到/loginclient.rootComponent將 examples/waspello/src/Layout.tsx 指定為客戶端根組件用于包裹所有頁面例如渲染導航欄spec注冊路由與頁面route(MainRoute, /, page(MainPage, { authRequired: true }))表示首頁路徑/對應MainPage且該頁面要求登錄后才能訪問。認證相關的登錄、注冊路由被拆分到了 examples/waspello/src/auth/auth.wasp.tsimport { page, route, type Spec } from wasp.sh/spec; import LoginPage from ./LoginPage with { type: ref }; import SignupPage from ./SignupPage with { type: ref }; export const authSpec: Spec [ route(SignupRoute, /signup, page(SignupPage)), route(LoginRoute, /login, page(LoginPage)), ];而看板業務相關的 Query / Action 則集中在 examples/waspello/src/cards/cards.wasp.ts兩者通過spec數組合并到主文件中形成清晰的分模塊組織方式。登錄頁與注冊頁實現分別位于 examples/waspello/src/auth/LoginPage.tsx 和 examples/waspello/src/auth/SignupPage.tsx配合EmailAndPassForm.jsx、GoogleAuthButton.jsx等組件完成 UI。數據模型User、List、Card 三實體關系Wasp 直接使用 Prisma 定義數據模型Waspello 的實體定義在 examples/waspello/schema.prisma 中datasource db { provider postgresql url env(DATABASE_URL) } generator client { provider prisma-client-js } model User { id Int id default(autoincrement()) lists List[] cards Card[] } model List { id Int id default(autoincrement()) name String pos Float user User relation(fields: [userId], references: [id]) userId Int cards Card[] } model Card { id Int id default(autoincrement()) title String pos Float list List relation(fields: [listId], references: [id]) listId Int author User relation(fields: [authorId], references: [id]) authorId Int }從模型可以看出數據源固定為 PostgreSQLDATABASE_URL通過環境變量注入List屬于某個User一對多Card屬于某個List且記錄創建者authorList與Card都帶有一個pos: Float字段用于在拖放排序時表示相對順序詳見下文“位置計算”一節。這套模型正是聲明式 Query / Action 中entities聲明與權限判斷的基礎。Query 與 Action聲明實體依賴實現多租戶隔離Waspello 的看板業務層只有兩類文件聲明文件 examples/waspello/src/cards/cards.wasp.ts 與實現文件 examples/waspello/src/cards/lists.js、examples/waspello/src/cards/cards.js。聲明部分非常直觀import { action, query, type Spec } from wasp.sh/spec; import { createCard, updateCard } from ./cards with { type: ref }; import { createList, createListCopy, deleteList, getListsAndCards, updateList, } from ./lists with { type: ref }; export const cardsSpec: Spec [ query(getListsAndCards, { entities: [List, Card] }), action(createList, { entities: [List] }), action(updateList, { entities: [List] }), action(deleteList, { entities: [List, Card] }), action(createListCopy, { entities: [List, Card] }), action(createCard, { entities: [Card] }), action(updateCard, { entities: [Card] }), ];每個query/action通過entities聲明它依賴哪些實體Wasp 生成器據此自動注入context.entities并為對應實體生成客戶端調用函數useQuery、createCard等。這也是“跨用戶實時更新”的實現基礎Wasp 會在客戶端發起的 Mutation 成功后自動失效受影響的 Query從而觸發重新拉取讓不同用戶看到的數據保持同步。實現端最值得學習的是“多租戶隔離”的寫法。以 examples/waspello/src/cards/lists.js 中的查詢為例import { HttpError } from wasp/server; export const getListsAndCards async (args, context) { if (!context.user) { throw new HttpError(403); } return context.entities.List.findMany({ // We want to make sure user can get only his own info. where: { user: { id: context.user.id } }, include: { cards: true }, }); };要點在于每個操作都必須校驗context.user存在否則拋出 403并在查詢條件中把context.user.id作為過濾條件確保用戶只能讀到自己的數據。寫入類操作同樣嚴格例如updateList使用updateMany({ where: { id: listId, user: { id: context.user.id } } })讓“非本人列表”的更新影響行數為 0deleteList則先findUnique校驗list.userId context.user.id再級聯刪除該列表下的所有卡片createListCopy在復制列表時還會用Promise.all逐張復制其中的卡片??ㄆ牟僮鲗崿F見 examples/waspello/src/cards/cards.jsupdateCard同樣先校驗卡片歸屬再執行更新。拖放排序的實現原理浮點位置與二分插值Waspello 的拖放體驗基于hello-pangea/dnd詳見 examples/waspello/package.json 中的依賴前端交互邏輯在 examples/waspello/src/cards/MainPage.jsx 中。核心技巧則是 examples/waspello/src/cards/PositionContext.jsx 實現的“位置計算器”新元素插入末尾時位置取當前最大pos加上固定間隔DND_ITEM_POS_SPACING2 ** 16 65536元素在列表內移動時新位置取相鄰兩個元素pos的平均值二分插值保證不改變其他元素的位置值空列表的初始位置為DND_ITEM_POS_SPACING - 1。由于pos是Float類型這種策略可以在不重寫整列位置的前提下完成任意次插入與移動這也是看板類應用常見的排序方案。PositionProvider通過 React Context 把getPosOfNewItem、getPosOfItemMovedWithinList、getPosOfItemInsertedInAnotherListAfter等計算函數提供給列表與卡片組件。拖放完成后前端調用由 Wasp 生成的客戶端操作updateList/updateCard從wasp/client/operations導入把新的pos和listId提交到后端后端校驗歸屬后更新數據庫隨后 Query 失效觸發重新拉取整個流程閉環。MainPage.jsx中針對卡片跨列表移動與同列表內移動分別調用calcNewPosOfDndItemInsertedInAnotherList和calcNewPosOfDndItemMovedWithinList并區分了BOARD列表級與CARD卡片級兩種拖放類型。本地開發從數據庫到運行的五步流程按照 examples/waspello/README.md 的說明本地跑起 Waspello 需要以下步驟安裝 Wasp 依賴先執行wasp install讓 Wasp CLI 準備好項目所需的工具鏈與依賴。啟動數據庫Waspello 使用 PostgreSQL最簡單的方式是用 Docker 在本地拉起一個 Postgreswasp start db執行數據庫遷移在另一個終端運行wasp db migrate-dev這會根據 examples/waspello/schema.prisma 生成 Prisma Client 并應用遷移歷史遷移文件位于 examples/waspello/migrations。配置環境變量將env.server復制為.env.server并填入實際值。其中必須包含DATABASE_URL指向wasp start db啟動的本地 Postgres若啟用 Google 登錄還需配置對應的 OAuth 憑據服務端配置方式可參考 web/docs/project/env-vars.md。啟動開發服務器wasp start命令會同時啟動客戶端與服務器并自動運行 Vite 與 Node 服務打開瀏覽器即可注冊賬號、創建列表與卡片。如果遇到依賴或環境問題可參考倉庫根目錄的 README.md 了解 Wasp 的整體安裝與使用方式。端到端測試Playwright 覆蓋核心用戶路徑Waspello 用 Playwright 做端到端測試測試套件位于 examples/waspello/e2e-tests/tests包含helpers.ts封裝注冊、登錄、隨機憑據生成等工具與simple.spec.ts。按 README 說明運行全部 e2e 測試只需npm run test該命令定義于 examples/waspello/package.json會先執行playwright install --with-deps安裝瀏覽器依賴再以DEBUGpw:webserver playwright test --config e2e-tests/運行測試。examples/waspello/e2e-tests/tests/simple.spec.ts 覆蓋了認證與基礎用法的完整鏈路注冊新用戶后應跳轉到首頁/點擊退出按鈕回到/login使用錯誤密碼登錄應看到 “Invalid credentials” 提示正確登錄后依次創建列表Add a list→ 輸入標題 →Add list、創建多張卡片Add a card→ 輸入標題 →Add card、再創建第二個列表并逐一斷言頁面元素正確渲染。這套測試與 CI 集成在每個 PR 上運行保證了看板核心交互在迭代中不回歸。小結從示例到實戰的啟示Waspello 用極少的樣板代碼展示了一條完整的“聲明式全?!甭窂絤ain.wasp.ts聲明應用與認證、schema.prisma定義數據模型、.wasp.ts聲明 Query / Action 并自動獲得類型安全與客戶端調用、HttpErrorcontext.user完成權限隔離、浮點位置算法配合拖放庫實現實時排序、Playwright 守護端到端質量。如果你正在規劃自己的看板、項目管理或任何多用戶協作類應用這個示例的聲明結構與權限寫法是可直接復用的最佳起點?!久赓M下載鏈接】waspThe batteries-included full-stack framework for the AI era. Develop JS/TS web apps (React, Node.js, and Prisma) using declarative code that abstracts away complex full-stack features like auth, background jobs, RPC, email sending, end-to-end type safety, single-command deployment, and more.項目地址: https://gitcode.com/GitHub_Trending/wa/wasp創作聲明:本文部分內容由AI輔助生成(AIGC),僅供參考