React中使用useEffect+Axios调用后端API遇404错误求助
Netflix克隆项目React请求404错误排查思路
开发Netflix克隆项目时,使用React的useEffect钩子调用后端API获取MongoDB电影列表时遇到404错误,Postman请求正常但React中报错,以下是具体排查方向:
核心排查点
1. 修复前端请求路径的相对路径问题
前端请求路径写的是lists(无开头斜杠),会导致请求相对于当前页面路由(比如当前页面是http://localhost:3000/home,实际请求会变成http://localhost:3000/home/lists),完全没有触发代理转发。
解决方法:给请求路径加上开头斜杠,修改后代码如下:
const res = await axios.get( `/lists${type ? "?type=" + type : ""}${genre ? "&genre=" + genre : ""}`, // ...其他配置 );
2. 验证代理配置是否生效
打开浏览器开发者工具的Network标签,查看请求的完整URL:
- 如果请求地址是
http://localhost:3000/lists:说明代理没生效,检查路径是否带斜杠,或者重启React开发服务器(proxy配置修改后必须重启)。 - 如果请求地址是
http://localhost:8800/api/lists:说明代理已生效,继续排查其他问题。
3. 确认后端路由拼接逻辑
后端index.js中挂载list路由的代码是app.use("/api/lists", listRoute);,而list.js中的GET路由是router.get("/", ...),完整的后端接口路径是http://localhost:8800/api/lists,和前端带斜杠的请求/lists加上代理http://localhost:8800/api/拼接后的路径一致,这部分逻辑是正确的。
4. 检查请求头的token有效性
虽然Postman请求正常,但要确认前端发送的token是否和Postman中一致,以及后端的verify中间件是否会因为token问题返回404(部分权限拦截逻辑可能错误返回404而非403)。可以暂时注释掉verify中间件,测试请求是否能成功,排除权限验证的影响。
5. 重启React开发服务器
proxy配置修改后,必须重启React的npm start服务,否则新的代理配置不会生效。
相关代码参考
package.json 文件
{ "name": "new", "version": "0.1.0", "private": true, "dependencies": { "@material-ui/core": "^4.11.4", "@material-ui/icons": "^4.11.2", "@testing-library/jest-dom": "^5.11.4", "@testing-library/react": "^11.1.0", "@testing-library/user-event": "^12.1.10", "axios": "^0.21.1", "react": "^17.0.2", "react-dom": "^17.0.2", "react-router-dom": "^5.2.0", "react-scripts": "4.0.3", "sass": "^1.35.1", "web-vitals": "^1.0.1" }, "scripts": { "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject" }, "eslintConfig": { "extends": [ "react-app", "react-app/jest" ] }, "browserslist": { "production": [ ">0.2%", "not dead", "not op_mini all" ], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] }, "proxy": "http://localhost:8800/api/" }
Home.jsx
import { useEffect, useState } from "react"; import Featured from "../../components/featured/Featured"; import List from "../../components/list/List"; import Navbar from "../../components/navbar/Navbar"; import axios from "axios"; import "./home.scss"; const Home = ({ type }) => { // eslint-disable-next-line const [lists, setLists] = useState([]); // eslint-disable-next-line const [genre, setGenre] = useState(null); useEffect(() => { const getRandomLists = async () => { try { const res = await axios.get( `/lists${type ? "?type=" + type : ""}${genre ? "&genre=" + genre : ""}`, { headers: { token: "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjYzYTA1ZjliZTRiZmEzYTBhOWMxYTQ5ZCIsImlzQWRtaW4iOnRydWUsImlhdCI6MTY3MTcxMjgyMCwiZXhwIjoxNjcyMTQ0ODIwfQ.W2e-RJ0XIQIVgGT47yhEOVOtWbdR5yYk11zmvN3vLzk", }, } ); console.log(res); } catch (error) { console.log(error); } }; getRandomLists(); }, [type, genre]); return ( <div className="home"> <Navbar /> <Featured type={type} /> <List /> <List /> <List /> <List /> <List /> <List /> </div> ); }; export default Home;
后端index.js
const express = require("express"); const app = express(); const mongoose = require("mongoose"); const dotenv = require("dotenv"); const authRoute = require("./routes/auth"); const userRoute = require("./routes/users"); const movieRoute = require("./routes/movies"); const listRoute = require("./routes/lists"); const cors = require("cors"); app.use(cors()); dotenv.config(); mongoose .connect(process.env.MONGO_URL, { useNewUrlParser: true, useUnifiedTopology: true, }) .then(() => console.log("DB Connection Successful!")) .catch((err) => console.log(err)); app.use(express.json()); app.use("/api/auth", authRoute); app.use("/api/users", userRoute); app.use("/api/movies", movieRoute); app.use("/api/lists", listRoute); app.listen(8800, () => { console.log("backend server is running on port 8800"); });
后端list.js
router.get("/", verify, async (req, res) => { const typeQuery = req.query.type; const genreQuery = req.query.genre; let list = []; try { if (typeQuery) { if (genreQuery) { list = await List.aggregate([ { $sample: { size: 10 } }, { $match: { type: typeQuery, genre: genreQuery } }, ]); } else { list = await List.aggregate([ { $sample: { size: 10 } }, { $match: { type: typeQuery } }, ]); } } else { list = await List.aggregate([{ $sample: { size: 10 } }]); } res.status(200).json(list); } catch (error) { res.status(500).json(error); } });
内容的提问来源于stack exchange,提问作者john s
相关产品推荐
相关产品推荐

