Web-Dev-For-Beginners 银行项目实战:Node.js + Express 打造的 Bank API 运行、六条 REST 路由与源码级解析
【免费下载链接】Web-Dev-For-Beginners24 Lessons, 12 Weeks, Get Started as a Web Developer项目地址: https://gitcode.com/GitHub_Trending/we/Web-Dev-For-Beginners
本篇技术指南聚焦微软开源教程 Web-Dev-For-Beginners 第 7 大模块「Build a Bank」配套的后端服务Bank API(位于 7-bank-project/api)。该 API 由 Node.js + Express 编写,是银行课程前端各课时(模板路由、登录注册表单、数据获取、状态管理)共享的数据源。阅读本文后,你将掌握 Bank API 的完整启动方式、全部 REST 端点与请求/响应结构,并能对照 server.js 源码理解每个端点的校验逻辑、幂等策略与内存存储实现。
在 7-bank-project 这套虚构银行应用课程中,前端的登录、注册、余额展示与交易录入都围绕"账户(account)与交易(transaction)"这两类数据展开。Bank API 正是为这些前端能力提供数据服务的后端:课程的设计理念是——API 已经替你写好,它不是课程练习的一部分,学习者的重心放在纯前端课程(模板路由、表单、数据获取与状态管理)上,只需把 API 跑起来作为数据源使用。不过,对"如何用 Express 从零搭一个这样的 API"感兴趣的读者,官方也提供了配套的 Node.js 视频系列(其中第 17~21 集完整讲解本项目 API)以及交互式 Express API 教程可供延伸学习。
Bank API 在银行项目中的定位
先厘清这份 API 文档所属的整体工程上下文。仓库中 7-bank-project/README.md 将银行项目划分为四个课时:
- HTML Templates and Routes in a Web App
- Build a Login and Registration Form
- Methods of Fetching and Using Data
- Concepts of State Management
其中课时 3 的核心实操就是启动 API 服务器,并用浏览器的fetch向http://localhost:5000/api发起请求读取/写入账户数据(例如fetch('//localhost:5000/api/accounts/' + user)),足以看出 Bank API 是支撑整套课程前端能力的关键后端组件。
从工程目录看,API 是一个完全独立的 Node.js 应用,有自己的依赖清单与启动脚本:
- 入口实现:7-bank-project/api/server.js
- 依赖与脚本声明:7-bank-project/api/package.json
- REST 客户端示例(VS Code REST Client 插件可直接执行):7-bank-project/api/api.http
- 英文原版 API 文档:7-bank-project/api/README.md
环境要求与启动服务器
前置条件
运行 Bank API 的唯一硬性前提是本地已安装 Node.js。根据 package.json 的声明,运行环境要求node >= 10,同时声明的运行时依赖为:
"dependencies": { "body-parser": "^1.20.3", "cors": "^2.8.5", "express": "^4.21.2" }开发依赖还包含eslint(npm run lint执行)与prettier(npm run format执行),用于代码风格检查与格式化。
三步启动
文档给出的启动流程非常简洁,共三步:
- 通过
git clone克隆当前 Web-Dev-For-Beginners 仓库; - 在终端中进入
7-bank-project/api目录,执行npm install安装依赖(等待时间取决于网络状况); - 安装完成后执行
npm start启动服务。
npm start实际执行的命令定义在 package.json 的scripts字段中:
"scripts": { "start": "node server.js", "lint": "eslint", "format": "prettier --single-quote --write *.js" }即直接以node server.js运行入口文件。启动成功后,控制台会打印Server listening on port 5000,服务器默认监听5000端口。
两个值得注意的运行细节
从源码中可以确认两点超出文档字面描述的关键信息:
- 端口可用环境变量覆盖。server.js 第 8 行声明
const port = process.env.PORT || 5000;,也就是说默认 5000 只是一个兜底值,如需换端口可执行PORT=3001 npm start。但教学配套代码(如课时 3 的fetch示例与 api.http)都写死了 5000 端口,因此按默认配置运行最稳妥。 - 数据只存在内存中,绝不持久化。文档特别强调:所有记录(账户与交易)都保存在内存中,一旦服务器停止,全部数据将丢失,重启后回到初始状态。原因同样在源码中——server.js 直接用一个内存对象
db充当数据库,源码注释也写明:"Store data in-memory, not suited for production use!"。因此 Bank API 只适合教学演示,不能用于生产环境。
API 端点总览
文档用一张表格完整列出了 Bank API 的六条路由,覆盖了账户与交易的增删查全部操作。结合 server.js 中实际的router定义(所有路由都挂载在/api前缀之下),汇总如下:
| 方法 | 路由 | 说明 |
|---|---|---|
| GET | /api/ | 获取服务器信息 |
| POST | /api/accounts/ | 创建账户,例如{ "user": "Giovanni", "description": "Il mio budget", "currency": "EUR", "balance": 100 } |
| GET | /api/accounts/:user | 获取指定账户的全部数据 |
| DELETE | /api/accounts/:user | 删除指定账户 |
| POST | /api/accounts/:user/transactions | 添加一笔交易,例如{ "date": "2020-07-23T18:25:43.511Z", "object": "Acquistato un libro", "amount": -20 } |
| DELETE | /api/accounts/:user/transactions/:id | 删除指定交易 |
需要说明的是,原文表格中个别行存在笔误(如
/api/account/:user/transactions少了一个s)。上表与下方详解均以 server.js 中的实际路由为准——源码里全部是复数形式accounts。
为方便阅读,下文按功能把它们拆成"账户操作"与"交易操作"两组,逐一讲解请求体约束、校验规则与响应状态码。这些都是源码里真实存在的逻辑,对照 server.js 可直接验证。
账户操作:创建、查询与删除
服务器信息:GET /api/
路由处理器在 server.js:
router.get('/', (req, res) => { return res.send(`${pkg.description} v${pkg.version}`); });它读取 package.json 中声明的description("Bank API")与version("1.0.0"),以纯文本返回Bank API v1.0.0。这条路由常被当作"服务器是否存活"的健康检查——课时 3 就先用curl http://localhost:5000/api验证 API 已成功启动。
创建账户:POST /api/accounts/
创建账户的完整处理逻辑位于 server.js,包含如下校验阶梯:
- 必填参数检查:
user(用户名)与currency(货币)二者缺一即返回400 Bad Request,响应体为{ "error": "Missing parameters" }; - 重名检查:若
db中已存在同名用户,返回409 Conflict,响应体为{ "error": "User already exists" }; - 余额类型容错:
balance若存在但不是数字,会尝试parseFloat(balance)转换;转换结果若为NaN,返回400与{ "error": "Balance must be a number" }; - 字段默认值:
description缺省时自动生成<user>'s budget;balance缺省为0;transactions初始为空数组[]。
创建成功返回201 Created与完整账户对象。原文示例创建一个名为 Giovanni、币种 EUR、余额 100 的账户:
{ "user": "Giovanni", "description": "Il mio budget", "currency": "EUR", "balance": 100 }对应响应(201)大致为:
{ "user": "Giovanni", "currency": "EUR", "description": "Il mio budget", "balance": 100, "transactions": [] }获取账户:GET /api/accounts/:user
处理器见 server.js:按路径参数:user从内存对象db中查找账户,找到则返回完整账户数据(含transactions数组);不存在则返回404 Not Found与{ "error": "User does not exist" }。
由于服务器启动时已在内存中预置了一个名为test的测试账户(见 server.js),你甚至不需要先创建任何账户,就可以立刻体验查询能力——课时 3 正是用curl http://localhost:5000/api/accounts/test来演示数据获取的。该预置账户内容为:用户test、货币符号$、余额 75,并含三笔预置交易:
db = { test: { user: 'test', currency: '$', description: 'Test account', balance: 75, transactions: [ { id: '1', date: '2020-10-01', object: 'Pocket money', amount: 50 }, { id: '2', date: '2020-10-03', object: 'Book', amount: -10 }, { id: '3', date: '2020-10-04', object: 'Sandwich', amount: -5 } ] } };删除账户:DELETE /api/accounts/:user
处理器见 server.js:账户不存在时返回404与错误对象;存在则执行delete db[req.params.user]将其从内存中移除,并返回204 No Content(无响应体)。
交易操作:添加与删除
添加交易:POST /api/accounts/:user/transactions
这是业务上最核心的端点,处理逻辑位于 server.js,它同时完成了"记账"与"更新余额"两件事:
- 账户存在性检查:目标账户不存在则返回
404 { "error": "User does not exist" }; - 必填字段检查:
date(日期)、object(交易说明)、amount(金额)三者缺一即返回400 { "error": "Missing parameters" }。需要注意一个源码细节:金额为0时!req.body.amount判定为真,同样会被当作"缺少参数"拒绝; - 金额容错:
amount不是数字时尝试parseFloat转换,结果若为NaN返回400 { "error": "Amount must be a number" }。文档示例中的-20表示支出(负数扣减余额); - 交易 ID 由内容哈希生成:这是该端点最有意思的实现——交易 ID 并非自增或随机数,而是对
date + object + amount拼接字符串取MD5:
const id = crypto .createHash('md5') .update(req.body.date + req.body.object + req.body.amount) .digest('hex');- 重复交易拦截:由于 ID 由内容决定,完全相同的三要素组合会算出相同的 ID;若账户中已存在该 ID,返回
409 { "error": "Transaction already exists" },从而天然实现"同一笔交易不能重复提交"的幂等保护; - 记账与余额更新:交易对象
{ id, date, object, amount }被push进账户的transactions数组,同时执行account.balance += transaction.amount累加余额; - 成功返回
201 Created与交易对象(含自动生成的id)。
原文给出的添加交易示例(支出 20,购买图书):
{ "date": "2020-07-23T18:25:43.511Z", "object": "Acquistato un libro", "amount": -20 }这里date采用的是 ISO 8601 时间戳格式(如2020-07-23T18:25:43.511Z)。
删除交易:DELETE /api/accounts/:user/transactions/:id
处理器见 server.js:先确认账户存在(否则404),再用findIndex按:id定位交易;找不到返回404 { "error": "Transaction does not exist" };找到则执行splice移除并返回204 No Content。
源码层面的两个补充事实:
- 因为添加交易时返回的
id是 MD5 哈希串,删除时直接复用该id即可。仓库自带的 api.http 就演示了完整流程:先 POST 一笔{ "date": "2020-07-24", "object": "Bought book", "amount": -20 }得到哈希 ID,再用DELETE /api/accounts/sinedied/transactions/8825ff3e...删除它; - 删除交易不会回滚余额——
balance只在添加交易时被累加,删除分支只做了splice,没有反向balance -= amount的操作。如果你在真实场景中想要"删除即冲正",需要在调用侧自行补做处理。
中间件与服务器骨架
要让以上路由真正可调用,还需理解 server.js 中的三个中间件与整体装配方式,这也能解释为什么各种请求格式都能被正确解析:
const app = express(); app.use(bodyParser.urlencoded({ extended: true })); app.use(bodyParser.json()); app.use(cors({ origin: /http:\/\/(127(\.\d){3}|localhost)/ })); app.options('*', cors());bodyParser.urlencoded({ extended: true }):支持解析表单格式(application/x-www-form-urlencoded)的请求体。仓库中的 api.http 在创建账户时特意用了这种格式:
POST http://localhost:5000/api/accounts/ Content-Type: application/x-www-form-urlencoded user=sinedied¤cy=$&balance=50bodyParser.json():支持解析application/json请求体——文档中的账户/交易示例与前端fetch调用都以 JSON 形式发送数据,因此两个解析器缺一不可;cors({ origin: /http:\/\/(127(\.\d){3}|localhost)/ }):开启跨域资源共享,但把允许的来源限定为localhost及127.x.x.x形式的本机地址。这正是银行前端页面在另一个端口/静态服务器上运行时,仍能直接向 5000 端口 API 发起浏览器跨域请求的前提。app.options('*', cors())则用于响应浏览器发送的 CORS 预检请求。
路由装配与启动收尾在文件末尾完成:
// Add 'api' prefix to all routes app.use(apiPrefix, router); // Start the server app.listen(port, () => { console.log(`Server listening on port ${port}`); });其中apiPrefix = '/api'(server.js),因此所有router上定义的路由最终都以/api开头对外暴露,与文档路由表一一对应。
命令行快速实测
启动服务器后,可以用curl直接验证每个端点。下面是一组可与源码逐行对证的完整操作序列(仅示意命令,账户数据会随服务器重启而清空):
# 1) 健康检查:返回 "Bank API v1.0.0" curl http://localhost:5000/api # 2) 读取预置的 test 账户(无需先创建,服务器自带种子数据) curl http://localhost:5000/api/accounts/test # 3) 创建账户(JSON 方式) curl -X POST http://localhost:5000/api/accounts/ \ -H "Content-Type: application/json" \ -d '{"user": "Giovanni", "description": "Il mio budget", "currency": "EUR", "balance": 100}' # 4) 为账户添加一笔支出交易(-20 表示支出) curl -X POST http://localhost:5000/api/accounts/Giovanni/transactions \ -H "Content-Type: application/json" \ -d '{"date": "2020-07-23T18:25:43.511Z", "object": "Bought a book", "amount": -20}' # 5) 查询账户,确认余额已被自动更新为 80 curl http://localhost:5000/api/accounts/Giovanni # 6) 删除某笔交易(:id 替换为第 4 步返回的 id) curl -X DELETE http://localhost:5000/api/accounts/Giovanni/transactions/<id>如果你使用 VS Code 并安装了 REST Client 扩展,也可以直接打开 7-bank-project/api/api.http 文件,点击每个###分隔的请求块上方的 "Send Request",用图形化方式完成同样的增删查验证。
与前端课程代码的配合方式
Bank API 的价值最终体现在它与银行前端模块的对接上:
- 课时 3「Methods of Fetching and Using Data」(7-bank-project/3-data/README.md) 将"启动 API 服务器并测试连通性"作为前置步骤(
curl http://localhost:5000/api),随后用fetch('//localhost:5000/api/accounts/' + encodeURIComponent(user))编写真实的getAccount()异步数据获取函数,并把返回的账户 JSON 渲染到页面。该课时的作业(7-bank-project/3-data/assignment.md)中也以const API_BASE_URL = 'http://localhost:5000/api';作为扩展练习的基准地址; - 完整版前端解决方案(7-bank-project/solution/app.js)顶部保留了一行
const serverUrl = 'http://localhost:5000/api'; // reserved for future server swap。从源码结构看,该方案为了便于离线运行,当前版本用 localStorage 模拟了 API 行为,并把这行真实 API 地址预留作"未来无缝切换真实后端"的接口;课程学习者参照课时 3/4 的引导,即可自己动手把这一行注释真正变成可用的数据链路。
换言之,Bank API 在本课程中扮演的是"教学数据源 + 真实后端蓝本"双重角色:运行期它是前端练习可直接请求的服务,源码层面它又是一份体积小巧、适合逐行精读的 Express 教学样本。
使用注意与限制
最后把实践中最容易踩的坑集中说明如下:
- 数据不持久:所有账户与交易存于内存
db对象,服务器重启即全部丢失并恢复为预置的test账户初始状态,切勿在上面存放任何真实业务数据; - 端口约定:服务默认监听 5000,虽可用
PORT环境变量覆盖,但课程全部前端示例与 api.http 均按 5000 编写,改动端口需同步调整调用方; - CORS 限定本机:浏览器跨域白名单只包含
localhost与127.x.x.x,这意味着该 API 的设计场景就是纯本地开发教学,无法直接对接远程部署的页面; - 金额校验语义:
amount为0会被判定为缺参(400),而balance为0则是合法的默认值——两者校验口径不同,来自源码实现,调用时需留意; - 无身份认证:整个 API 没有任何登录/鉴权机制,账户名即全部访问凭据。这是因为 2-forms 课时 讲解的前端"登录"仅为本地 UI 状态模拟,与真正安全的用户体系无关,本 API 仅供课程教学使用。
总而言之,Bank API 用约 200 行 Express 代码演示了一个小型 REST 服务从中间件配置、路由组织、请求校验到状态维护的完整写法。对照 server.js 通读一遍,再配合 api.http 逐个端点实测,你便能同时吃透"如何使用它支撑银行前端课程"与"如何用 Node.js 自建同款 API"这两个层次的问题。
【免费下载链接】Web-Dev-For-Beginners24 Lessons, 12 Weeks, Get Started as a Web Developer项目地址: https://gitcode.com/GitHub_Trending/we/Web-Dev-For-Beginners
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考