Web-Dev-For-Beginners 银行项目 API 实战指南:Node.js + Express 构建账户与交易后端服务
2026/9/11 6:57:47 网站建设 项目流程

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.2Web 框架,路由与中间件
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常量的定义),二者分别运行在30005000端口,构成典型的前后端分离开发架构。

二、环境准备与服务器启动

2.1 前置条件

根据官方文档,唯一硬性要求是安装Node.js。仓库 package.json 中声明了运行环境约束:"node": ">=10",即 Node.js 10 及以上版本即可满足要求。

2.2 启动步骤

官方文档给出了完整的四步流程:

  1. 克隆仓库:将 Web-Dev-For-Beginners 仓库克隆到本地
  2. 进入 API 目录cd 7-bank-project/api(仓库根目录下的路径,与文档中Web-Dev-For-Beginners/7-bank-project/api对应)
  3. 安装依赖:执行npm install,等待依赖安装完成(耗时取决于网络状况)
  4. 启动服务:执行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:必填参数——usercurrency缺失时返回:

{ "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缺省为0transactions初始为空数组:

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,包含usercurrencydescriptionbalancetransactions五个字段

内置测试账户的完整数据结构(来自 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 层:必填参数——dateobjectamount任一缺失返回400

{ "error": "Missing parameters" }

第 3 层:金额类型转换——amountparseFloat()转换后若为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 状态码与错误契约汇总

综合以上分析,全部接口的错误与状态码约定可汇总如下:

状态码场景响应体
200GET 查询成功、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正则只放行localhost127.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&currency=$&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 文档为前提。

九、已知限制与学习建议

限制(文档与源码共同确认)

  1. 数据不持久化:所有数据存于内存,重启即清空(官方文档明确提示,server.js 注释也标注 "not suited for production use!")
  2. 无身份认证:API 不校验密码,用户名即身份,GET /api/accounts/:user可直接获取任意账户数据——这是刻意简化,便于教学
  3. 删除交易不联动余额:从源码确认,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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询