前端开发者想要快速转型为全栈开发,好奇后端技术框架是怎样? 数据库怎么设计?服务怎么部署?别急,我最近用 TypeScript 搓了个小项目,把全栈这条线捋了一遍,分享给你。
为啥选这个题目
全栈开发的难点不是某一项技术,而是理解多个环节如何协作:前端组件怎么调用后端 API、API 怎么操作数据库、数据库变更怎么通过迁移管理、整个应用怎么打包部署。
所以我做了fullstack-kanban 正是为解决这个痛点而设计的学习项目。它统一用 TypeScript 语言,覆盖了全栈开发的完整链路:
浏览器 (React 19)
│ HTTP + JWT
▼
Next.js 16 ──── CORS ──── NestJS 11
│
Prisma ORM
│
PostgreSQL 16
一行 docker compose up --build 就能本地跑起来。下面逐一介绍每个技术点。
一、前端:Next.js 16 + React 19 + Tailwind CSS 4
Next.js App Router — 文件即路由
Next.js 16 的 App Router 让路由定义回归直觉——文件夹结构就是 URL 结构:
src/app/
├── page.tsx → / (登录页)
├── boards/
│ ├── page.tsx → /boards (看板列表)
│ └── [boardId]/
│ └── page.tsx → /boards/123 (看板详情)
layout.tsx 作为根布局包裹所有页面,AuthProvider 和 ThemeProvider 在这里注入全局状态。
React 19 — 组件、Hooks 与状态管理
项目没有引入 Redux 或 Zustand,而是用 React 原生的 Context API 管理全局认证状态。这是一个适合入门的状态管理方案——先理解 useState / useEffect / useContext / useCallback 四个核心 Hook,再决定是否需要更重的方案。
认证逻辑就三步:
createContext创建上下文AuthProvider包裹子组件,管理 token 和 user 状态useAuth()自定义 Hook 暴露login/logout/user
Tailwind CSS 4 — 原子化样式
Tailwind 4 用 @import "tailwindcss" 一行替代了旧版的配置文件,通过 @theme 指令定义主题变量。亮色/暗色切换只需修改 CSS 变量:
:root { --background: oklch(1 0 0); }
.dark { --background: oklch(0.12 0 0); }
shadcn/ui — 不是组件库的组件库
shadcn/ui 的核心理念:组件代码直接复制到你的项目里。没有额外的 npm 包依赖,你可以完全控制每一行代码。项目使用了 Button、Card、Dialog、AlertDialog 等 12 个组件,涵盖表单、弹窗、确认框、滚动区域、骨架屏等常见场景。
二、拖拽交互:从前端状态到后端事务
拖拽是本项目最复杂的交互,也是理解全栈协作的最佳切入点。
前端 — 乐观更新
拖拽采用 乐观更新 策略:先立即更新 UI,再后台发请求。失败时重新获取数据回滚。这给用户"即时响应"的体验。
架构分层清晰:
DragDropContext(Board 组件)— 协调整个拖拽过程Droppable(List 组件)— 定义放置区域Draggable(Card 组件)— 定义可拖拽元素
后端 — 位置重排事务
卡片拖动后,后端需要在数据库事务中调整位置。核心逻辑分两种情况:
同列表内移动:区间内的卡片位移 ±1,腾出目标位置。
移动前:[A:0] [B:1] [C:2] [D:3] [E:4] 将 B(1) 移动到位置 3
第一步:position > 1 且 <= 3 的卡片前移 C(2→1), D(3→2)
第二步:B 的 position 设为 3
结果: [A:0] [C:1] [D:2] [B:3] [E:4]
跨列表移动:源列表补位 + 目标列表腾位,两步 updateMany 在一个 $transaction 中原子执行。
这些操作都在 Prisma 的交互式事务中完成——要么全部成功,要么全部回滚,保证数据一致性。
深入阅读:卡片拖动调用栈分析 — 从用户松开鼠标到数据库 COMMIT 的完整链路。
三、后端:NestJS 11 模块化架构
如果你熟悉 Angular,NestJS 的设计会让你感到亲切。如果不熟悉也没关系——NestJS 的模块系统核心概念只有这几个:
| 概念 | 职责 |
|---|---|
| Module | 把相关的控制器和服务打包,是组织代码的基本单元 |
| Controller | 处理 HTTP 请求,接收参数、返回响应,不写业务逻辑 |
| Service | 存放业务逻辑,通过依赖注入被 Controller 调用 |
| Guard | 在请求到达 Controller 之前拦截检查(如是否已登录) |
| Pipe | 对请求参数做转换和校验 |
| DTO | 用类定义请求数据的结构和校验规则 |
每个功能模块(auth / boards / lists / cards)遵循相同的结构:*.module.ts + *.controller.ts + *.service.ts + dto/。
请求的一生
以 GET /boards/1 为例:
HTTP 请求到达 Express
→ CORS 中间件检查来源
→ ValidationPipe 校验参数
→ 路由匹配到 BoardsController.findOne()
→ JwtAuthGuard 验证 JWT,解析出 userId
→ ParseIntPipe 将 "1" 转为数字
→ BoardsService.findOne(userId, 1)
→ Prisma 查询数据库 + 所有权检查
→ JSON 响应返回
装饰器声明式地完成了路由定义、参数提取、认证校验——这些都是后端开发的通用模式。
深入阅读:NestJS 工程方案详解
四、认证:JWT + Passport
认证是前后端协作最紧密的环节之一,流程简洁清晰:
前端 后端
POST /auth/login │
{ username, password } ────> │ bcrypt.compare() 验证密码
│ jwtService.sign() 签发 Token
│
{ access_token: "eyJ..." } <────
│
localStorage 存储 Token │
│
GET /boards │
Authorization: Bearer eyJ... ──> JwtStrategy 验证签名和过期时间
│ req.user = { userId, username }
│
boards 数据 <────────────────── BoardsService.findAll(userId)
关键设计:
- bcrypt 加盐哈希密码,数据库里存的是不可逆的哈希值
- JWT 7 天有效期,Token 中携带
sub(用户 ID)和username - 401 自动登出:前端的
lib/api.ts拦截 401 响应,自动清除 token 并跳转登录页 - DTO 校验:
class-validator装饰器确保用户名至少 3 字符、密码至少 6 字符
五、数据库:PostgreSQL + Prisma ORM
关系建模
四张表形成清晰的一对多层级关系:
User 1──* Board 1──* List 1──* Card
关键 Schema 设计决策:
- 级联删除(
onDelete: Cascade)— 删除 Board 时自动删除其下所有 List 和 Card,避免孤儿数据 - 复合唯一约束(
@@unique([boardId, position]))— 同一看板内列表位置不重复,同一列表内卡片位置不重复 - Prisma 7 的
prisma-clientprovider — 新版生成器,输出到generated/prisma/而非node_modules
为什么用 ORM 而不是手写 SQL
// 手写 SQL — 容易拼错、没有类型提示
const result = await db.query("SELECT * FROM users WHERE username = $1", [username]);
// Prisma — 类型安全、自动补全、重构友好
const user = await prisma.user.findUnique({ where: { username } });
Prisma 的 include 语法替代了手写 JOIN:
prisma.board.findMany({
where: { userId },
include: {
lists: {
orderBy: { position: 'asc' },
include: { cards: { orderBy: { position: 'asc' } } },
},
},
});
// 等价于多层 LEFT JOIN + ORDER BY
深入阅读:PostgreSQL 工程方案详解
六、部署:Docker Compose 一键启动
全栈应用有前端、后端、数据库三个服务,手动配置环境变量和启动顺序很繁琐。Docker Compose 把这些全部自动化:
# docker-compose.yml 核心结构
services:
postgres: # 数据库 — 健康检查确保就绪
backend: # API — 等待 postgres 健康后启动,自动执行迁移
frontend: # Web — 等待 backend 健康后启动
关键工程实践:
- 健康检查(
healthcheck)— 数据库用pg_isready,后端用wget自检,确保依赖服务真正就绪 - 多阶段构建(
AS builder/AS runner)— 构建阶段安装全部依赖并编译,运行阶段只拷贝产物,镜像体积从数百 MB 降到几十 MB - 依赖启动顺序(
depends_on+condition: service_healthy)— 保证 postgres 就绪后才启动 backend - 数据持久化(命名卷
postgres-data)— 容器删除重建后数据不丢失 - 网络隔离 — 容器间通过服务名通信(
@postgres:5432),宿主机通过映射端口访问(localhost:5433)
深入阅读:Docker 工程方案详解
动手实践
快速启动
git clone https://github.com/PeixuanLi/fullstack-kanban.git
cd fullstack-kanban
docker compose up --build
- 前端:http://localhost:3000
- 后端 API:http://localhost:3001
不用装 Node、不用配 PostgreSQL,有 Docker 就行。
建议的学习路径
- 跑起来,用起来 — 注册账号,创建看板,拖拽卡片,感受完整的用户流程
- 读前端代码 — 从
frontend/src/app/page.tsx开始,跟着路由走一遍 - 读后端代码 — 从
backend/src/main.ts开始,理解请求如何被处理 - 读数据库 Schema — 打开
backend/prisma/schema.prisma,理解表结构和关系 - 读拖拽调用栈 — 对照 card-drag-call-stack.md 理解一次拖拽的完整前后端链路
- 尝试修改 — 给卡片加个颜色标签、给看板加个描述字段,练习从前端到数据库的全链路开发
文档索引(按需取用)
| 文档 | 内容 |
|---|---|
| Next.js/React 前端工程方… | App Router、React Hooks、状态管理、API 封装、拖拽实现、Tailwind CSS |
| shadcn/ui 使用说明 | 组件库配置、已安装组件、添加新组件、主题定制 |
| NestJS 工程方案详解 | 模块架构、控制器、服务、DTO 校验、JWT 认证、Prisma 集成 |
| PostgreSQL 工程方案详解 | 数据建模、Prisma ORM、迁移管理、连接池、事务操作 |
| Docker 工程方案详解 | Compose 编排、Dockerfile 多阶段构建、端口映射、数据卷 |
| 卡片拖动调用栈分析 | 从鼠标松开到数据库 COMMIT 的完整调用链 |
写在最后
全栈开发没有想象中那么复杂。核心就三点:前端发请求 → 后端处理 + 查库 → 数据库存数据。每个环节都有成熟工具,关键是理解它们怎么协作。
这个项目我刻意控制复杂度:技术选型"刚好够用",不堆新东西;但该有的工程实践(鉴权、校验、事务、容器化)一个没少。毕竟学习项目也得贴近真实场景,不然练了也白练。
如果你看完觉得有点启发,欢迎:
- 去 GitHub 点个 Star,让更多人看到
- Fork 一份,改成你自己的小项目练手
- 或者直接提个 Issue,聊聊你的想法
有问题随时交流,代码和人,都欢迎来撩 😄