Wasp 实战:30分钟搭建带认证的全栈应用
厌倦了手动拼接 React+Express+Prisma?本教程带你用 Wasp 声明式框架,从零搭建带认证、数据库操作和 RPC 通信的 TodoList。告别繁琐胶水代码,掌握端到端类型安全,快速交付可部署的全栈骨架。

Wasp 实战:30分钟搭建带认证的全栈应用
每次接到管理后台或内部工具的需求,技术难点往往不在业务逻辑本身,而在环境搭建与底层胶水代码的拼接。前端 React 写视图,后端 Express 暴露接口,数据库靠 Prisma 建模,再手动补充 JWT 鉴权、CORS 跨域配置、前后端类型对齐……功能未动,脚手架先折腾一天。
本教程将带你使用 Wasp 声明式全栈框架,从零构建一个完整的 TodoList 应用。涵盖用户认证、数据库建模、前后端 RPC 调用与数据渲染。全程无需手动编写 Express 路由、处理跨域或手写类型定义,所有底层依赖均由 Wasp 编译器自动注入与生成。
环境准备
开始前请确认本地满足以下基础条件:
- Node.js >= 18.0(推荐通过 nvm 进行版本管理)
- npm 或 yarn 包管理器
- 具备基础 React 组件开发与 TypeScript 语法认知
- 操作系统:macOS / Linux / Windows WSL 均可完美支持
Wasp 底层编译器强依赖新版 Node API,版本过低会导致 CLI 构建链路中断。务必提前核对版本,避免在环境排查上消耗时间。
步骤一:安装 CLI 并初始化项目
Wasp 的核心工作流围绕编译器展开。全局安装 CLI 工具后,即可一键生成包含前后端完整依赖的项目骨架。
bash
npm install -g @wasp.sh/wasp-cli@latest
wasp new todo-app
cd todo-app
初始化完成后,项目目录结构清晰分离。核心文件 main.wasp 是声明式配置的绝对入口。Wasp 严格遵循架构与逻辑分离原则:声明文件集中定义路由、页面、数据操作与认证策略;业务文件仅负责具体实现。后续新增模块时,只需修改声明文件,编译器会自动处理路由注册、依赖注入与类型导出。
步骤二:定义数据库模型
Wasp 深度集成 Prisma ORM,数据库表结构直接通过 db/schema.prisma 进行声明。
prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
model User {
id Int @id @default(autoincrement())
username String @unique
tasks Task[]
}
model Task {
id Int @id @default(autoincrement())
description String
isDone Boolean @default(false)
user User @relation(fields: [userId], references: [id])
userId Int
}
配置中明确了 User 与 Task 的一对多关联。本地开发默认使用 SQLite 实现零配置启动。若需迁移至生产环境,仅需将 provider 切换为 postgresql 并更新连接串,上层业务代码完全无需改动,直接复用 Prisma 多数据库适配优势。
模型定义完成后,执行迁移命令同步物理表结构:
bash
cd ext/db
npx prisma migrate dev
npx prisma generate
步骤三:编写声明式架构
打开 main.wasp,填入以下核心配置定义应用边界:
wasp
app todo-app {
wasp: { version: "^0.16.0" },
title: "Todo App"
}
route RootRoute { path: "/", to: MainPage }
page MainPage {
authRequired: true,
component: import MainPage from "@ext/MainPage"
}
query getTasks {
fn: import { getTasks } from "@ext/queries",
entities: [Task]
}
action createTask {
fn: import { createTask } from "@ext/actions",
entities: [Task]
}
auth {
userEntity: User,
methods: {
usernameAndPassword: {}
},
onAuthFailedRedirectTo: "/login"
}
authRoute LoginPage { path: "/login", to: LoginPageComponent }
page LoginPageComponent {
component: import { LoginPage } from "@wasp/auth"
}
配置项逐层解构:
authRequired: true开启路由级守卫,拦截未授权访问,免去了手动编写 Express 中间件的重复劳动。query与action划定前后端 RPC 通信边界。关键字段entities: [Task]建立了数据依赖追踪,当 Task 表发生变更时,Wasp 会自动标记前端缓存失效,触发 TanStack Query 重新拉取,实现视图与数据库的强一致性。auth区块激活内置认证引擎,自动接管 Session 创建、Cookie 管理与密码哈希存储。
步骤四:实现前端交互与服务端逻辑
在 src/MainPage.tsx 编写主视图。Wasp 导出的 useQuery 与 useAction 封装了完整的请求生命周期,并提供严格的 TypeScript 推导。
tsx
import { useQuery, useAction } from "wasp/client";
import { getTasks } from "wasp/client/operations";
import { createTask } from "wasp/client/operations";
import { useState } from "react";
export default function MainPage() {
const { data: tasks, isLoading } = useQuery(getTasks);
const [description, setDescription] = useState("");
const handleCreate = useAction(createTask);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (description.trim()) {
await handleCreate({ description });
setDescription("");
}
};
if (isLoading) return <div>加载中...</div>;
return (
<div style={{ maxWidth: 400, margin: "40px auto", padding: 20 }}>
<h2>我的 Todo 列表</h2>
<form onSubmit={handleSubmit}>
<input value={description} onChange={(e) => setDescription(e.target.value)} placeholder="新任务..." style={{ width: "calc(100% - 80px)", padding: 8, marginRight: 8 }} />
<button type="submit" style={{ padding: "8px 16px" }}>添加</button>
</form>
<ul style={{ listStyle: "none", padding: 0, marginTop: 20 }}>
{tasks?.map((task) => (
<li key={task.id} style={{ padding: 8, borderBottom: "1px solid #eee" }}>
<span style={{ textDecoration: task.isDone ? "line-through" : "none" }}>{task.description}</span>
</li>
))}
</ul>
</div>
);
}
服务端逻辑在 src/queries.ts 与 src/actions.ts 中实现。Wasp 执行时会通过上下文自动注入 context.user 对象,开发者可直接读取当前会话身份,彻底告别从 Request Header 手动解析 Token 的繁琐流程。
ts
// src/queries.ts
import { Task } from "wasp/entities";
export const getTasks = async (_args: void, context: any): Promise<Task[]> => {
if (!context.user) throw new Error("Unauthorized");
return context.entities.Task.findMany({ where: { userId: context.user.id }, orderBy: { id: "desc" } });
};
// src/actions.ts
import { Task } from "wasp/entities";
export const createTask = async (args: { description: string }, context: any): Promise<Task> => {
if (!context.user) throw new Error("Unauthorized");
return context.entities.Task.create({ data: { description: args.description, userId: context.user.id } });
};
步骤五:启动与验证
执行以下命令拉起完整开发环境:
bash
wasp start
编译器完成后,浏览器将自动跳转首页。首次访问触发 auth 配置重定向至 /login,完成注册后进入主页面。提交表单后,列表数据实时刷新。手动关闭页面重新打开,路由守卫将再次拦截,验证 Session 状态持久化机制。整个闭环无需手动编写任何网络请求拦截器或状态同步代码。
避坑指南
- 版本强对齐:
main.wasp声明的wasp: { version }必须与全局 CLI 大版本保持同步。编译中断时,优先执行npm i -g @wasp.sh/wasp-cli@latest拉取最新兼容版本。 - Prisma 迁移冲突:调整
schema.prisma后必须重跑migrate dev。开发阶段若表结构出现脏数据,直接删除dev.db重新迁移是最快恢复方式。 - IDE 类型报红:Wasp 启动后会在
.wasp/out/sdk动态生成类型声明文件。若编辑器提示找不到模块,等待编译完成并重启 TypeScript 语言服务即可消除误报。 - 声明文件热重载:业务代码修改支持即时热更新;但调整
main.wasp结构配置时,需等待编译器重新生成底层路由或手动重启进程。
扩展方向
当前骨架已覆盖基础全栈交付链路。后续可沿以下路径深化:
- 在
auth.methods中追加google: {}快速接入 OAuth 单点登录。 - 结合 Wasp 内置
email模块实现关键操作邮件通知。 - 补充
action deleteTask完善任务生命周期管理。 - 通过
wasp deploy命令一键发布至 Render 或 Fly.io,体验开箱即用的云端交付。
项目源码参考:https://github.com/wasp-lang/wasp
声明式全栈架构通过牺牲底层控制的绝对自由度,换取了极高的开发效率与类型安全性。掌握此类工具链,能大幅压缩重复性基建时间,让技术精力真正回归业务价值本身。