React请求Express接口时req.body.userId为undefined问题排查
问题根因定位
你遇到的req.body.userId始终为undefined,是两处基础用法错误叠加导致的,和你排查过的body-parser配置、CORS规则、React组件传参、MongoDB模型定义都没有关系:
- 第一处错误:接口路由定义为GET请求,却试图从
req.body获取参数。HTTP GET请求规范默认不携带请求体,Express的express.json()中间件只会解析POST/PUT/PATCH这类带请求体的方法的body内容,GET请求下req.body本身就是空对象,自然拿不到任何值。 - 第二处错误:axios的GET请求传参写法完全错误。
axios.get方法只接收两个参数:第一个是请求URL,第二个是请求配置对象。你写的axios.get(url, {userId}, {headers: xxx})属于三参错误写法,{userId}被当成了完整配置对象,后面携带请求头的第三个参数直接被axios忽略,userId根本没有被发送到服务端。
你之前查阅的10多份同类解决方案大多针对「未注册express.json()中间件」的场景,但你已经正确注册了中间件,所以那些方案不生效。
修完主问题后你还会碰到3个隐藏bug,这里一并给出修正方案:
- CORS中间件处理OPTIONS预检请求时没有直接返回,会导致请求继续走到后续路由,偶发跨域报错
- Post模型中直接使用
ObjectId类型但没有从mongoose导入,服务启动时会直接抛出引用错误 - 前端mobx中直接从axios返回值取业务字段是错的,axios响应的真实业务数据存放在
data字段下,直接取会拿到undefined
逐文件修正方案
1. 服务端路由与控制器修改
两种方案二选一,推荐遵循RESTful规范的方案A:
方案A:保留GET请求,参数走路径/查询参数
修改user.routes路由文件,把userId定义为路径参数:
// 匹配URL格式: /api/user/get/[实际userId值] router.get('/get/:userId', userController.getUserById)
修改userController中的参数获取逻辑:
async getUserById(req, res) { try { // 路径参数从req.params对象中取值,如果用查询字符串传参则从req.query取值 const {userId} = req.params console.log(userId) // 此处可正常拿到前端传的userId const user = await User.findById(userId) if (!user) { return res.status(404).json({message: '用户不存在'}) } return res.status(200).json(user) } catch (e) { console.log(e) res.status(500).json({message: '服务端内部错误'}) } }
方案B:改为POST请求,从req.body取参数
如果你一定要从请求体传参,直接把路由方法改为POST即可,控制器取参逻辑不用动:
// user.routes文件 router.post('/get', userController.getUserById)
2. 前端mobx请求逻辑修改
对应上面选的方案调整请求写法,同时修正axios返回值取值错误:
如果选方案A(GET+路径参数):
fetchUserData = async(userId) => { try { // GET请求只传两个参数,userId拼接到路径中,请求头放到第二个配置参数里 const response = await axios.get(`http://localhost:5000/api/user/get/${userId}`, { headers:{Authorization: `Bearer ${localStorage.getItem('token')}`}, }) // 从响应的data字段取真实业务数据 const user = response.data this.user = user this.setUserPage( user.login, user._id, user.link, user.friends, user.communities ) console.log(user) console.log(`user id in store ${userId}`) } catch (e) { console.log(e) } }
如果选方案B(POST+请求体传参):
fetchUserData = async(userId) => { try { // POST请求第二个参数是请求体,第三个参数才是配置 const response = await axios.post('http://localhost:5000/api/user/get', {userId}, { headers:{Authorization: `Bearer ${localStorage.getItem('token')}`}, }) const user = response.data // 后续赋值逻辑和上面一致 this.user = user this.setUserPage( user.login, user._id, user.link, user.friends, user.communities ) } catch (e) { console.log(e) } }
3. CORS中间件修正
修改CORS中间件的OPTIONS请求处理逻辑,直接返回200状态,不要继续走后续路由:
function cors(req, res, next) { res.header("Access-Control-Allow-Origin", "*") res.header("Access-Control-Allow-Methods", "GET, PUT, PATCH, POST, DELETE, OPTIONS") res.header("Access-Control-Allow-Headers", "Content-Type, Authorization, Origin, X-Requested-With, Accept, X-Auth-Token") if (req.method === "OPTIONS") { // 预检请求直接返回,不要调用next() return res.sendStatus(200) } next() }
4. Post模型修正
导入mongoose的Types对象,正确使用ObjectId类型:
// 开头加Types导入 const {Schema, model, Types} = require('mongoose') const moment = require('moment') const Post = new Schema({ title: {type: String, required: true}, content: {type: String, required: true}, // 用Types.ObjectId代替直接写ObjectId userId: {type: Types.ObjectId, required: true, ref: 'User'}, login: {type: String, ref: 'User'}, likes: {type: Array, default: []}, dislikes: {type: Array, default: []}, comments: {type: Array, default: []}, date: {type: String, default: () => { return moment().format('D/MM/YYYY hh:mm') }} }) module.exports = model('Post', Post )
内容的提问来源于stack exchange,提问作者mortvicious
相关产品推荐
相关产品推荐

