以前我认为前端就是写写页面、改改样式,至于数据咋来的,那不都是后端的事吗?前端只需要渲染数据不就行了?
然而大错特错,恰恰是这个数据,就是前后端联调的关键。
真正开发其实是:
前端 + 后端 + 数据 + 接口 + 协作
我之前写的项目都是静态页面,从来没有真正接入真实数据,当我开始做大项目时就发现问题了。
如果只是闷头写数据,你连数据类型都不知道,你怎么写?
所以首先就要去翻接口文档,看看后端传过来的数据是什么,格式如何,类型如何,才能正确渲染数据。
当然,只是成功接收数据还是远远不够的,对于我们这种前端都半桶水的新手,要先了解后端到底干了什么,数据是怎样流动的。
数据流动方向:
js
React页面
↓
axios/fetch
↓
HTTP请求
↓
Node/FastAPI
↓
MySQL
↓
JSON返回
↓
前端渲染
下面我用一个小demo简单说明前后端联调的过程。
我们先从后端开始,自己写一个接口,其实后端没有想象中那么难。我们可以用node.js来写后端代码,用的就是javascript语言,学习成本比较低。
首先建立一个server文件夹,写入index.js文件。创建express服务器和cors中间件(前后端跨域),再定义post和get的API即可完成一个简单的后端接口。
js
const express = require("express");
const cors = require("cors");
const jwt = require("jsonwebtoken");
const app = express();
const SECRET_KEY = "your_secret_key";
const user = {
id: 1,
username: "admin",
password: "qwiqdqd90090",
};
app.post("/login", (req, res) => {
const { username, password } = req.body;
if (username === user.username && password === user.password) {
//登录成功,生成token
const token = jwt.sign({ id: 1, username: "admin" }, SECRET_KEY, {
expiresIn: "1h", //token有效期为1小时,防止token泄露别人可以永久登录你的账号
});
return res.json({
code: 0,
data: { token },
});
} else {
return res.status(401).json({ message: "Invalid credentials" });
}
});
app.get("/user", authMiddleware, (req, res) => {
return res.json({
id: req.user.id,
username: req.user.username,
});
});
app.listen(3000, () => {
console.log("Server is running on port 3000");
});
再加入鉴权中间件,这个是jwt用来进行身份认证的内容。token用来维持用户状态。
js
// 鉴权中间件
function authMiddleware(req, res, next) {
const authHeader = req.headers.authorization;
if (!authHeader) return res.status(401).json({ message: "未登录" });
const token = authHeader.split(" ")[1];
try {
const decoded = jwt.verify(token, SECRET_KEY);
req.user = decoded;
next();
} catch {
return res.status(401).json({ message: "token无效或已过期" });
}
}
完成后我们启动后端,看到输出Server is running on port 3000则成功,表示后端在端口3000运行。
js
node index.js
接下来通过Postman来测试后端接口是否正常,Postman可以模拟前端请求,看到后端的返回信息,常用于测试接口是否正确。
Postman 最大的意义,
其实是:
把"前端问题"和"后端问题"分离。
因为:
如果 Postman 都请求失败,
那问题一定在后端。
如果 Postman 成功,但前端失败,
那问题就在前端请求逻辑。
我们首先登录,获取鉴权token,根据后端接口知道,登录接口是post,前端提交username和password,通过认证后后端返回{ code: 0, data: { token }, }。所以我们在请求时选择POST请求login接口,并且在请求体body中写入username和password。成功返回200,以及code,token。

现在复制token到请求头Headers,添加Authorization,内容为Bearer token值(jwt鉴权格式)。换成GET请求user接口,返回user信息即为成功。

现在初始化前端项目,新建utils文件夹,写入request.js文件,使用axios 全局封装 ,专门用来解决前后端分离项目中重复的请求配置 和统一的认证 / 错误处理问题。
axios 封装真正解决的是
统一整个项目的请求行为。
比如:
- 自动携带 token
- 统一错误处理
- 统一 baseURL
- 统一 loading
- 统一超时逻辑
这些都是工程化开发里的基础,因为,工程化=统一。
js
import axios from "axios";
const request = axios.create({
baseURL: "http://localhost:3000",
});
//请求拦截器
request.interceptors.request.use((config) => {
const token = localStorage.getItem("token");
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
//响应拦截器
request.interceptors.response.use(
(response) => response, // 2xx 成功,直接把数据给你
(error) => {
if (error.response && error.response.status === 401) {
const requestUrl = error.config?.url || "";
if (requestUrl !== "/login") {
alert("登录已过期,请重新登录");
localStorage.removeItem("token");
}
}
return Promise.reject(error);
},
);
export default request;
再写一个简单的前端页面App.js
js
import { useState, useEffect } from "react";
import request from "./utils/request";
import "./App.css";
function App() {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [loggedIn, setLoggedIn] = useState(false);
const [userInfo, setUserInfo] = useState(null);
useEffect(() => {
const token = localStorage.getItem("token");
if (token) {
setLoggedIn(true);
}
}, []);
async function handleLogin(event) {
event.preventDefault();
const trimmedUsername = username.trim();
const trimmedPassword = password.trim();
if (!trimmedUsername || !trimmedPassword) {
alert("请输入用户名和密码");
return;
}
try {
//await 把异步的 reject 拉回到当前同步流里,变成了同步错误
const response = await request.post("/login", {
username: trimmedUsername,
password: trimmedPassword,
});
if (response.data.code === 0) {
localStorage.setItem("token", response.data.data.token);
setLoggedIn(true);
alert("登录成功");
} else {
alert("登录失败:" + response.data.message);
}
} catch (error) {
const message =
error.response?.data?.message || error.message || "未知错误";
alert("请求失败:" + message);
}
}
async function fetchUserInfo() {
try {
const response = await request.get("/user");
setUserInfo(response.data);
} catch (error) {
const message =
error.response?.data?.message || error.message || "未知错误";
alert("获取用户信息失败:" + message);
}
}
return (
<form className="App">
<input
type="text"
placeholder="请输入用户名"
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
<input
type="password"
placeholder="请输入密码"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<button type="button" onClick={handleLogin}>
登录
</button>
<button type="button" onClick={fetchUserInfo} disabled={!loggedIn}>
获取用户信息
</button>
{loggedIn && <p>✅ 已登录</p>}
{userInfo && (
<div
style={{ marginTop: "20px", background: "#f0f0f0", padding: "10px" }}
>
<h3>用户信息:</h3>
<p>ID: {userInfo.id}</p>
<p>用户名: {userInfo.username}</p>
</div>
)}
</form>
);
}
export default App;
点击登录即可登录成功(POST请求login)

点击获取用户信息(GET请求user)

这样一个前后端分离的小demo就完成啦,不过这里没有写数据库的内容,直接在后端定义了user列表。
写完这个 demo 后, 我第一次真正理解:
前后端联调并不是"接口调通"这么简单。
它背后其实涉及:
- HTTP 请求
- 数据流
- 接口规范
- 登录鉴权
- 状态管理
- 错误处理
- 前后端协作
工程化不是突然出现的高级概念。
而是:
从第一次真正接入真实数据开始。