记账本 Ledger 开发复盘:从零手写一个全栈应用
这两天我给自己定了个目标:仓库里不能只有主页和博客,要有一个真正能用的、从零写的项目。于是有了这篇复盘的主角——ledger-app,一个前后端分离的个人记账本。
这篇文章不写教程,写决策过程:为什么这么选、怎么实现的、踩了什么坑、下一版要做什么。
一、为什么做记账本
选项目时我给自己三个标准:
- 真能用——不是 todo demo,是每天都会打开的 App 级功能;
- 链路完整——登录 → 增删改查 → 统计视图,覆盖一个产品的核心闭环;
- 面试可讲——技术点能引出 15 分钟的回答(鉴权、数据隔离、测试、部署)。
记账本三个全占:有真实需求、CRUD 天然完整、统计图表自带亮点。
二、技术选型
| 层 | 选择 | 理由 |
|---|---|---|
| 前端 | Vue 3 + Vite | 组合式 API 简洁,Vite 启动快 |
| 图表 | ECharts 5 | 折线 + 环形图,配置对象化,黑金主题好定制 |
| 后端 | Express | 生态最大,中间件模型写鉴权最直观 |
| 数据库 | SQLite(Node 内置 node:sqlite) |
零原生编译依赖,npm install 秒装,单文件备份 |
| 认证 | JWT + bcryptjs | 无状态,前后端分离标配 |
最值得说的是数据库选型:better-sqlite3 这类库需要原生编译,在 Windows/macOS/Linux 之间折腾版本匹配是劝退点。Node 22 起内置 node:sqlite,整个后端没有任何原生依赖,跨平台秒装——我把这当作一个”刻意减少依赖面”的工程决策。
三、架构与数据设计
两张表:
users(id, username UNIQUE, password, created_at)
transactions(id, user_id FK,
type IN('income','expense'), amount > 0,
category, date, note, created_at)
两个设计重点:
- 数据隔离——所有 SQL 都带
user_id = ?条件,天然防止 A 用户操作 B 用户数据;更新/删除时校验归属,不属于自己的返回 404; - 参数化查询——全部使用 prepared statement,SQL 注入无从谈起(写完确实被”金额 = 0”之类的脏数据教育过,所以把校验全都写在了服务端)。
鉴权流程:注册/登录 → 服务端签发 JWT(7 天过期)→ 前端存 localStorage → 每个请求带 Authorization: Bearer <token> → authRequired 中间件统一校验,401 时前端自动跳登录。
四、统计接口的三条 SQL
统计页三个数据源,都是不超过十行的聚合查询:
- 单月汇总:
GROUP BY type求和; - 近 6 个月趋势:按月分组取
substr(date, 1, 7),直接用字符串前缀截出 YYYY-MM,比日期函数省事且够用; - 分类占比:
GROUP BY category ORDER BY total DESC。
前端用 ECharts 画成双线趋势图和环形图,工具提示、配色全部对齐站点的黑金主题。
五、踩过的坑(真实记录)
NODE_ENV=production让前端构建失败——环境变量是production时npm install自动跳过 devDependencies,vite 报Could not resolve 'vite'。排查了三轮才发现是环境问题,按需用NODE_ENV=development npm install解决;node --test test/报ERR_UNSUPPORTED_DIR_IMPORT——直接用node --test自动发现用例即可,目录参数在新版本里反而成了坑;- ECharts 打包超 500kB——用
manualChunks把 echarts / vue 拆成独立 chunk,首屏加载更快(顺带学会了 Vite 的按需分包配置)。
六、测试:零依赖的端到端测试
用 Node 内置 node:test + 内置 fetch,一共 15 个用例,覆盖:注册 / 登录 / 重复注册 / 密码过短 / 未登录 401 / 记账 CRUD / 非法金额 / 三组统计。没有引入任何测试框架——不是偷懒,是想展示”用标准库能力写出可维护的测试”。
cd server && npm test
# 15 pass / 0 fail
七、部署
docker compose up -d 一条命令:后端 node:22-alpine,前端 nginx 托静态资源 + 反代 /api,SQLite 数据挂 volume 持久化。真要上线,换个生产 JWT_SECRET 就行。
八、收获 & 简历话术
最大的收获不是语法,是完整的工程手感:从建表到接口到图表到部署,每一层都知道自己在干什么。面试时这项目可以这样讲:
独立完成个人记账全栈应用:Vue3 + ECharts 前端、Express + SQLite 后端、JWT 用户鉴权与数据隔离、参数化查询防注入;使用 Node 内置 SQLite 消除原生编译依赖;编写 15 个端到端测试覆盖全链路。
九、下一步
- 预算提醒(超支告警)
- 导出 CSV
- 分类自定义
代码在 github.com/hedongli1/ledger-app,欢迎提 PR——至少我自己会看 😄