按照传统定义,服务器就是一台计算机,它专门监听发往开放端口的请求,比如HTTP、WebSocket,GRPC等等、无论是什么端口,只要端口可以通过互联网访问,所以其他计算机就可以通过其连接到他
为什么我们不能把所有后端逻辑都写在前端?
刚开始学 Web 的时候其实很容易产生一个疑问:既然 JavaScript 已经可以发送 HTTP 请求,浏览器也提供了 fetch、WebSocket 等 API,那为什么还需要后端?前端直接请求数据库、第三方 API,甚至把所有业务逻辑都写进浏览器里,不是少了一层,架构还更简单吗?
问题在于,前端运行在用户的电脑上,而后端运行在我们控制的服务器上。这两个运行环境的区别,决定了很多事情根本不能放心交给前端。
1. Security:前端是一个完全不可信的环境
这是最重要的一点。
我们写:
fetch("/api/user")
虽然代码是我们写的,但真正执行这段代码的机器属于用户。用户可以打开 DevTools 看代码、修改 JavaScript、抓 HTTP 请求,甚至完全绕开网页,自己用 Postman、curl 或脚本构造请求。
所以不能认为:
「我的前端页面没有这个按钮,用户就不能执行这个操作。」
例如管理员后台里有:
if (user.role === "admin") {
showDeleteButton();
}
把按钮隐藏起来只能改变 UI,并不能真正保护数据。
如果真正的删除接口:
DELETE /api/users/123
没有在后端再次判断「这个请求者到底是不是管理员」,攻击者完全可以绕开页面直接请求这个接口。
更严重的是各种密钥。
假设前端代码里直接写:
const API_KEY = "sk-xxxxxxxx";
这个 Key 基本就等于公开了。因为 JavaScript 最终必须下载到用户浏览器才能执行,所以无论怎么打包、压缩甚至混淆,都不能把真正的秘密安全地藏在前端。
这也是为什么数据库密码、服务器凭证、第三方服务 Secret、支付密钥等东西必须留在后端。
所以这里可以记一个非常重要的 Web 开发原则:
前端负责交互,但永远不能作为最终的安全边界。真正的身份认证、权限校验和敏感业务规则必须由服务器执行。
2. CORS:浏览器不会允许网页随便读取其他网站的数据
这里特别容易和反向代理混在一起。
假设我的网站运行在:
https://myblog.com
前端突然执行:
fetch("https://api.example.com/data")
这时候浏览器会发现:
网页来源:myblog.com
请求目标:api.example.com
它们不是同一个 Origin(源)。
Origin 主要由:
协议 + 主机 + 端口
决定。
浏览器受到同源策略(Same-Origin Policy)的限制,不会允许 JavaScript 毫无限制地读取其他源的数据。
如果 api.example.com 希望允许 myblog.com 的网页访问,它需要通过响应头明确告诉浏览器,例如:
Access-Control-Allow-Origin: https://myblog.com
复杂一些的请求甚至可能先出现:
OPTIONS /api/data
也就是 CORS Preflight(预检请求)。
所以 CORS 更准确的理解应该是:
不是前端“有没有把请求头配完整”的问题,而是目标服务器通过 CORS 响应头告诉浏览器:我是否允许这个来源的网页读取我的响应。
而且有个很容易误解的地方:
CORS 主要是浏览器施加的安全机制,不等于服务器本身拒绝了这个 HTTP 请求。
所以你可能遇到一种很经典的情况:
Postman 请求:成功
curl 请求:成功
后端请求:成功
浏览器 fetch:CORS Error
因为前三者没有浏览器的同源策略限制。
前端虽然可以通过 fetch 等 Web API 请求网络资源,但它并不像后端一样,可以自由地向任意服务器发起请求并读取结果。前端 JavaScript 运行在浏览器的安全环境中,受到同源策略和 CORS 的约束。
例如我们的网页运行在:
https://myapp.com
现在前端想直接获取:
https://api.example.com/data
即使我们知道这个 API 地址,也知道应该传什么参数:
fetch("https://api.example.com/data")
浏览器依然会检查这个跨域请求是否被目标服务器允许。如果对方没有允许 myapp.com 这个来源,前端代码本身并不能强行绕过这个限制。
这就是关键区别:
浏览器前端
↓
受到同源策略 / CORS 限制
↓
第三方服务器
而如果由我们的后端去请求:
浏览器
↓
自己的后端
↓
第三方服务器
服务器之间的 HTTP 请求不受浏览器 CORS 机制约束。
因此,假如一个应用需要整合多个数据源:
天气 API ─────┐
地图 API ─────┤
支付 API ─────┼──→ Backend ──→ Frontend
内部服务 A ───┤
内部服务 B ───┘
如果把这些工作全部扔给前端,那么前端不仅要分别处理这些 API,而且会受到各个服务的跨域策略限制;有些服务甚至根本不允许浏览器直接调用。
而交给后端以后,后端可以主动请求这些服务,对数据进行组合、加工,再通过自己的 API统一返回给前端。
前端的网络能力是浏览器“批准给你的”,后端的网络能力则是服务器自己拥有的。CORS 就是这种能力差异最典型的体现之一。
3. Database:浏览器不应该直接连接数据库
这个问题其实比前两个更加直观。
假设我们不用后端,让 React 直接连接 MySQL:
浏览器
↓
MySQL
那么首先就出现一个无法回避的问题:
数据库账号和密码放哪?
放前端?
那用户打开 DevTools:
host = db.example.com
username = root
password = 123456
恭喜,数据库基本可以准备跑路了。
即使暂时不考虑安全,还有连接管理问题。
数据库连接不是完全没有成本的。建立连接涉及网络通信、认证、数据库资源占用等过程。如果:
10000 个用户
↓
每个浏览器自己连接数据库
↓
10000+ 数据库连接
数据库压力会非常大,而且你根本无法很好地控制这些连接。
所以通常变成:
10000 个用户
↓
HTTP 请求
↓
后端
↓
Connection Pool
↓
Database
所谓 Connection Pool(数据库连接池),可以先理解成:
后端提前维护一批可以重复使用的数据库连接,请求来了借一个,用完以后还回池子,而不是每次请求都重新创建一个数据库连接。
比如:
Request A ─┐
Request B ─┤
Request C ─┤
Request D ─┼──→ 后端 ──→ [ DB Connection Pool ] ──→ MySQL
Request E ─┤ │ │ │ │ │
... │ 复用有限数量的连接
10000用户 ─┘
这样后端就成了数据库前面的一层控制面:可以控制谁能查询、能查询什么、一次查多少、连接数量是多少,还可以做事务、缓存、限流等。
其实还有第 4 个问题:Business Logic
从「为什么需要后端」这个问题继续往下推,还有一个非常核心的原因:业务规则必须有一个可信的执行位置。
例如购物网站下单:
商品价格:¥100
优惠券:-¥20
库存:剩 1 件
最终支付:¥80
如果这些全部由前端决定:
const finalPrice = price - discount;
用户完全可以把它改成:
const finalPrice = 0.01;
然后告诉服务器:
{
"product": 123,
"price": 0.01
}
所以真正可靠的流程应该是:
前端:
我要买商品 123
使用优惠券 ABC
↓
后端:
商品 123 现在多少钱?
优惠券 ABC 是否有效?
这个用户能不能使用?
库存还有没有?
最终价格是多少?
创建订单
↓
数据库
也就是说,前端可以展示业务逻辑的结果,但关键业务逻辑最终必须由后端重新计算和验证。









