Web-Dev-For-Beginners 银行项目 API 实战指南:Node.js + Express 构建账户与交易后端服务
【免费下载链接】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-bank-project/api的官方 API 文档,系统讲解如何快速启动一个基于 Node.js + Express 的银行后端服务,深入剖析其全部 5 条 REST 路由、请求/响应契约与错误码,并结合 server.js 源码逐段解析内存数据库、MD5 事务 ID、余额联动与 CORS 配置的实现细节,帮助你理解前端银行应用与后端 API 如何协同工作。
一、API 在银行项目中的角色
在 7-bank-project 这一虚构银行项目中,api子目录是整个课程练习的后端支撑。项目由四课组成——HTML 模板与路由、登录注册表单、数据获取与使用、状态管理——而所有课程的数据读写都依赖这个 API。
该 API 官方文档(英文原文,捷克语译本见 translations/cs/7-bank-project/api/README.md)明确说明:API 已预先构建完毕,不属于练习的一部分。学习者的核心任务在前端,但理解 API 的接口契约是完成练习的前提。
从源码结构看,整个后端仅由两个文件构成,非常精简:
- server.js:全部业务逻辑(约 200 行)
- package.json:依赖与启动脚本定义
依赖清单(来自 package.json):
| 依赖 | 版本 | 用途 |
|---|---|---|
express | ^4.21.2 | Web 框架,路由与中间件 |
body-parser | ^1.20.3 | 解析 URL-encoded 与 JSON 请求体 |
cors | ^2.8.5 | 跨域资源共享中间件 |
eslint(dev) | ^7.5.0 | 代码检查 |
prettier(dev) | ^2.0.5 | 代码格式化 |
前端应用通过http://localhost:5000/api访问该服务(见 solution/app.js 中serverUrl常量的定义),二者分别运行在3000与5000端口,构成典型的前后端分离开发架构。
二、环境准备与服务器启动
2.1 前置条件
根据官方文档,唯一硬性要求是安装Node.js。仓库 package.json 中声明了运行环境约束:"node": ">=10",即 Node.js 10 及以上版本即可满足要求。
2.2 启动步骤
官方文档给出了完整的四步流程:
- 克隆仓库:将 Web-Dev-For-Beginners 仓库克隆到本地
- 进入 API 目录:
cd 7-bank-project/api(仓库根目录下的路径,与文档中Web-Dev-For-Beginners/7-bank-project/api对应) - 安装依赖:执行
npm install,等待依赖安装完成(耗时取决于网络状况) - 启动服务:执行
npm start
npm start实际执行的是node server.js(见 package.json 的 scripts 配置)。
2.3 端口与运行模式
启动后,服务器监听在5000 端口。源码中端口定义如下(server.js):
const port = process.env.PORT || 5000;即可以通过环境变量PORT覆盖默认端口。
文档特别强调:该服务器需要与主银行应用服务器(监听 3000 端口)同时运行,不要关闭它。前端应用依赖此 API 获取账户数据,关闭会导致登录、注册、交易查询全部失效。
2.4 重要限制:数据仅存于内存
注意:所有条目存储在内存中,不会持久化保存,因此当服务器停止时,所有数据都会丢失。
这是本 API 最重要的特性之一,源码中的注释也明确标注(server.js):
// Store data in-memory, not suited for production use! const db = { ... };实际影响:
- 每次重启服务器,账户与交易数据清空
- 数据库内置了一个
test测试账户(余额 75,含 3 条示例交易),供前端练习直接登录使用 - 该设计完全适合教学场景,但不适合生产环境
三、API 接口全览
官方文档给出的路由表如下:
| 路由 | 说明 |
|---|---|
GET /api/ | 获取服务器信息 |
POST /api/accounts/ | 创建账户,例如:{ user: 'Yohan', description: 'My 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: 'Bought a book', amount: -20 } |
DELETE /api/accounts/:user/transactions/:id | 删除指定交易 |
所有路由统一挂载了/api前缀,这是通过一行代码实现的(server.js):
app.use(apiPrefix, router);下面逐一结合源码深入解析每个接口的实现细节。
四、接口逐条深度解析
4.1 获取服务器信息: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。
实用价值:这是前端课程中验证 API 是否正常运行的标准命令(在 2-forms/README.md 和 3-data/README.md 中均有使用):
curl http://localhost:5000/api # 期望响应: "Bank API v1.0.0"4.2 创建账户:POST /api/accounts/
请求体示例(官方文档):
{ "user": 'Yohan', "description": 'My budget', "currency": 'EUR', "balance": 100 }源码实现(server.js)包含一套完整的参数校验逻辑:
校验 1:必填参数——user与currency缺失时返回:
{ "error": "Missing parameters" }状态码400。
校验 2:账户唯一性——若db[req.body.user]已存在,返回409:
{ "error": "User already exists" }校验 3:余额类型转换——balance若为非数字字符串,会尝试parseFloat()转换,转换失败(isNaN)返回400:
{ "error": "Balance must be a number" }创建账户对象时,description缺省为"{user}'s budget",balance缺省为0,transactions初始为空数组:
const account = { user: req.body.user, currency: req.body.currency, description: req.body.description || `${req.body.user}'s budget`, balance: balance || 0, transactions: [], }; db[req.body.user] = account; return res.status(201).json(account);成功时返回201 Created及完整账户对象。注意user在这里充当了内存数据库的键,这也解释了为何系统强制要求用户名唯一。
4.3 查询账户:GET /api/accounts/:user
实现代码(server.js):
router.get('/accounts/:user', (req, res) => { const account = db[req.params.user]; if (!account) { return res.status(404).json({ error: 'User does not exist' }); } return res.json(account); });- 通过 URL 路径参数
:user直接作为内存对象的键查找 - 账户不存在时返回
404+{ "error": "User does not exist" } - 成功返回完整账户 JSON,包含
user、currency、description、balance、transactions五个字段
内置测试账户的完整数据结构(来自 server.js 与 3-data/README.md):
{ "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 } ] }课程中建议直接用用户名test登录,即可立即看到带示例数据的仪表盘效果。
4.4 删除账户:DELETE /api/accounts/:user
实现代码(server.js):
router.delete('/accounts/:user', (req, res) => { const account = db[req.params.user]; if (!account) { return res.status(404).json({ error: 'User does not exist' }); } delete db[req.params.user]; res.sendStatus(204); });- 账户不存在返回
404 - 存在则直接从内存对象中
delete,并返回204 No Content(无响应体)
4.5 添加交易:POST /api/accounts/:user/transactions
请求体示例(官方文档):
{ "date": "2020-07-23T18:25:43.511Z", "object": "Bought a book", "amount": -20 }实现代码(server.js)包含 4 层校验与处理:
第 1 层:账户存在性——账户不存在返回404。
第 2 层:必填参数——date、object、amount任一缺失返回400:
{ "error": "Missing parameters" }第 3 层:金额类型转换——amount经parseFloat()转换后若为NaN返回400:
{ "error": "Amount must be a number" }第 4 层:交易唯一性(核心设计)——使用 Node.js 内置crypto模块,将date + object + amount拼接字符串做 MD5 哈希,生成 32 位十六进制交易 ID(server.js):
const id = crypto .createHash('md5') .update(req.body.date + req.body.object + req.body.amount) .digest('hex');若该 ID 已存在于账户的交易列表中,返回409:
{ "error": "Transaction already exists" }这保证了内容完全相同的交易不会重复添加(幂等性设计)。
余额联动:交易添加成功后,余额自动更新(server.js):
account.balance += transaction.amount;响应:返回201 Created及带id的完整交易对象:
{ "id": "8825ff3e8331277911174fd1b73ff889", "date": "2020-07-24", "object": "Bought book", "amount": -20 }4.6 删除交易:DELETE /api/accounts/:user/transactions/:id
实现代码(server.js):
router.delete('/accounts/:user/transactions/:id', (req, res) => { const account = db[req.params.user]; if (!account) { return res.status(404).json({ error: 'User does not exist' }); } const transactionIndex = account.transactions.findIndex( (transaction) => transaction.id === req.params.id ); if (transactionIndex === -1) { return res.status(404).json({ error: 'Transaction does not exist' }); } account.transactions.splice(transactionIndex, 1); res.sendStatus(204); });- 先校验账户存在性,再通过
findIndex查找交易 - 交易不存在返回
404+{ "error": "Transaction does not exist" } - 存在则
splice移除并返回204 No Content
注意:与添加交易不同,删除交易不会回滚余额——account.balance在删除路径中保持不变。这一点从源码可以确认。
五、HTTP 状态码与错误契约汇总
综合以上分析,全部接口的错误与状态码约定可汇总如下:
| 状态码 | 场景 | 响应体 |
|---|---|---|
200 | GET 查询成功、POST 添加成功(部分接口为201) | 数据对象/字符串 |
201 | 创建账户、添加交易成功 | 创建的对象 |
204 | 删除账户/交易成功 | 无响应体 |
400 | 缺少必填参数 / 数值类型非法 | { "error": "..." } |
404 | 账户或交易不存在 | { "error": "..." } |
409 | 账户已存在 / 交易已存在 | { "error": "..." } |
前端课程在实现login()与register()时,正是利用data.error字段判断请求失败并展示错误信息(见 solution/app.js 与 3-data/README.md 中的getAccount模式)。
六、中间件与 CORS 配置解析
服务初始化部分(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());- body-parser:同时启用 URL-encoded 与 JSON 两种解析器,因此 api.http 示例中既可以用
application/x-www-form-urlencoded提交表单,也可以用application/json提交 JSON - CORS 白名单:
origin正则只放行localhost或127.x.x.x域的跨域请求,恰好覆盖前端开发服务器(运行于localhost:3000)的场景,同时拒绝其他来源——这是一个面向教学环境的最小化安全策略 app.options('*', cors())为预检请求(OPTIONS)放行,保障前端带Content-Type头的跨域 POST 请求能够正常通过
七、用 api.http 快速联调
仓库提供了一个非常实用的联调脚本 api.http,配合 VS Code 的 REST Client 扩展即可在不写任何前端代码的情况下验证全部接口。文件开头注释说明了依赖:
# You need REST Client extension for VS Code to use this file脚本按顺序覆盖了 5 条核心链路:
GET http://localhost:5000/api/ POST http://localhost:5000/api/accounts/ Content-Type: application/x-www-form-urlencoded user=sinedied¤cy=$&balance=50 GET http://localhost:5000/api/accounts/sinedied DELETE http://localhost:5000/api/accounts/sinedied POST http://localhost:5000/api/accounts/sinedied/transactions Content-Type: application/json { "date": "2020-07-24", "object": "Bought book", "amount": -20 } DELETE http://localhost:5000/api/accounts/sinedied/transactions/8825ff3e8331277911174fd1b73ff889注意最后一条 DELETE 中的交易 ID 并非随意编写,而是对"2020-07-24" + "Bought book" + -20做 MD5 的结果——与POST创建交易时生成的 ID 一致,这正是 MD5 确定性哈希的特性。你可以用任意 Node.js 环境验证:
node -e "console.log(require('crypto').createHash('md5').update('2020-07-24' + 'Bought book' + -20).digest('hex'))" # 输出: 8825ff3e8331277911174fd1b73ff889八、API 与前端课程的协作模式
理解 API 之后,可以看到它与四课内容形成完整闭环:
| 课程 | 与 API 的关联 |
|---|---|
| 1. HTML 模板与路由 | 搭建应用骨架,为后续 API 数据展示准备 DOM 结构 |
| 2. 登录注册表单 | 通过POST /api/accounts/注册、GET /api/accounts/:user登录验证 |
| 3. 数据获取与使用 | 核心课程:用 Fetch API 调用本后端,处理异步响应与错误 |
| 4. 状态管理 | 管理从 API 获取的账户数据在前端的持久化与刷新 |
前端完整实现位于 solution/app.js,其中 serverUrl 常量直接指向http://localhost:5000/api;而课程练习(3-data)中展示的getAccount模式与curl http://localhost:5000/api连通性测试,均以本 API 文档为前提。
九、已知限制与学习建议
限制(文档与源码共同确认):
- 数据不持久化:所有数据存于内存,重启即清空(官方文档明确提示,server.js 注释也标注 "not suited for production use!")
- 无身份认证:API 不校验密码,用户名即身份,
GET /api/accounts/:user可直接获取任意账户数据——这是刻意简化,便于教学 - 删除交易不联动余额:从源码确认,DELETE 路径不更新
account.balance
学习建议:
- 若想深入掌握该 API 的构建方式,官方文档推荐了 Node.js 入门视频系列(视频 17–21 覆盖本 API)与 Express API 交互式教程,可循此路径学习从零搭建
- 动手实验时,先启动 API(
npm install && npm start),再启动前端应用,两个终端窗口保持同时运行 - 用
curl命令逐步验证每个接口的请求与响应,理解状态码语义后再进入前端联调
结语
7-bank-project/api虽是一个教学用途的精简后端,却浓缩了 REST API 设计的核心要素:路由组织、参数校验、错误契约、幂等设计、余额一致性维护与跨域配置。通过本文对 server.js 的逐段解读,你不仅掌握了如何启动与调用这套接口,更能理解其背后的实现取舍——这正是 Web-Dev-For-Beginners 课程"先会用、再理解"教学理念的体现。
【免费下载链接】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),仅供参考