You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用jsonwebtoken生成Token时出现ERR_HTTP_HEADERS_SENT错误求助

问题:MERN项目中用户注册/登录时JWT生成触发"Cannot set headers after they are sent to the client"错误

错误日志

throw er; // Unhandled 'error' event
      ^

Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
    at new NodeError (node:internal/errors:393:5)
    at ServerResponse.setHeader (node:_http_outgoing:644:11)
    at ServerResponse.header (D:\web dev\projects\Mern-Project\server\node_modules\express\lib\response.js:794:10)
    at ServerResponse.send (D:\web dev\projects\Mern-Project\server\node_modules\express\lib\response.js:174:12)
    at ServerResponse.json (D:\web dev\projects\Mern-Project\server\node_modules\express\lib\response.js:278:15)
    at D:\web dev\projects\Mern-Project\server\router\auth.js:45:34
    at D:\web dev\projects\Mern-Project\server\node_modules\mongoose\lib\model.js:5228:18
    at process.processTicksAndRejections (node:internal/process/task_queues:77:11)
Emitted 'error' event on Function instance at:
    at D:\web dev\projects\Mern-Project\server\node_modules\mongoose\lib\model.js:5230:15
    at process.processTicksAndRejections (node:internal/process/task_queues:77:11) {
  code: 'ERR_HTTP_HEADERS_SENT'
}

相关代码

Auth路由代码

const express = require("express");
const { default: mongoose } = require("mongoose");
const router = express.Router();
const bcrypt = require("bcrypt");
const jwt = require("jsonwebtoken");
require("../db/conn");
const User = require("../model/userSchema");
router.get("/", (req, res)=>{
    res.send("Hello world to server router js");

});

router.post("/register", (req, res)=>{
  bcrypt.hash(req.body.password, 12, function (err, hash) { 
    const newUser = new User({
      name:req.body.name,
      email: req.body.email,
      phone: req.body.phone,
      work: req.body.work,
      password: hash
      
    });
    // Create token
    const email = req.body.email;
    const token = jwt.sign(
      { user_id: newUser._id, email },
      process.env.TOKEN_KEY,
      {
        expiresIn: "2h",
      }
    );
    // save user token
    newUser.token = token;
    res.status(201).json(newUser);

    if(!newUser.name || !newUser.email || !newUser.phone || ! newUser.work || !newUser.password){
      return res.status(422).json({error: "Plz fill the required field"});
    }
    User.findOne({email: newUser.email}, function (err, userExist) { 
      if(err){
        console.log(err);
        res.status(500).json({error: "failed to register"});
      }else{
        if(userExist){
          return res.status(422).json({error: "Email already Exist"});
        }else{
          newUser.save();
          res.status(201).json({message: "user registered succesfully"});
        }
      }
     });

   });
  });

  //LOgin route
  router.post("/signin", (req, res)=>{
    if(!req.body.email || !req.body.password){
      return res.status(400).json({error: "Plz fill the required data"});
   }else{
    bcrypt.hash(req.body.password, 12, function (err, hash) { 
      User.findOne({email: req.body.email}, function (err, foundUser) {
        if(err){
          console.log(err);
        }else{
          if(foundUser){
            bcrypt.compare(req.body.password, foundUser.password, function (err, result) {
                  if(result){
                    res.json({message: "successfully log in"})
                  }else{
                    res.json({message: "incorrect password"});
                  }
              });
              const email = req.body.email;
              const token = jwt.sign(
                { user_id: foundUser._id, email },
                process.env.TOKEN_KEY,
                {
                  expiresIn: "2h",
                }
              );
              foundUser.token = token;
              res.status(200).json(foundUser);
          }else{
            res.status(400).json({message: "user not found"});
          };
        }
      })
    })
  }
  });

module.exports = router;

用户Schema与模型代码

const mongoose = require("mongoose");

const userSchema = new mongoose.Schema({
    name:{
        type:String,
        required:true
    },
    email:{
        type:String,
        required:true
    },
    phone:{
        type:Number,
        required:true
    },
    work:{
        type:String,
        required:true
    },
    password:{
        type:String,
        required:true
    },
    tokens:[
         { token:{
            type:String,
            required:true
         }

    }]

});

const User = mongoose.model("User", userSchema);

module.exports = User;

问题原因分析

  1. 多次发送HTTP响应:Express每个请求仅允许发送一次响应,你的代码存在多处重复响应逻辑:
    • 注册路由:先执行res.status(201).json(newUser),后续又执行字段校验、邮箱查重的响应操作,导致同一请求多次发送响应头。
    • 登录路由:bcrypt.compare回调中已发送res.json({message: ...}),后续又执行res.status(200).json(foundUser),重复响应。
  2. Token存储错误:Schema中tokens是数组类型,但代码直接赋值newUser.token = token,不符合Schema定义,需将Token添加到数组中。
  3. 逻辑顺序颠倒:注册路由先生成Token并返回响应,再做字段校验和邮箱查重,业务逻辑顺序完全错误——应先校验,通过后再生成Token并保存用户。
  4. 登录冗余操作:登录时无需重新哈希密码,bcrypt.hash(req.body.password, 12, ...)是多余操作,直接用原密码和数据库哈希值对比即可。

修复后的代码示例

修复后的Auth路由

const express = require("express");
const router = express.Router();
const bcrypt = require("bcrypt");
const jwt = require("jsonwebtoken");
require("../db/conn");
const User = require("../model/userSchema");

router.get("/", (req, res) => {
    res.send("Hello world to server router js");
});

// 注册路由修复
router.post("/register", async (req, res) => {
    try {
        // 先做字段校验
        const { name, email, phone, work, password } = req.body;
        if (!name || !email || !phone || !work || !password) {
            return res.status(422).json({ error: "请填写所有必填字段" });
        }

        // 检查邮箱是否已存在
        const userExist = await User.findOne({ email });
        if (userExist) {
            return res.status(422).json({ error: "邮箱已存在" });
        }

        // 哈希密码
        const hash = await bcrypt.hash(password, 12);
        const newUser = new User({
            name,
            email,
            phone,
            work,
            password: hash
        });

        // 生成Token
        const token = jwt.sign(
            { user_id: newUser._id, email },
            process.env.TOKEN_KEY,
            { expiresIn: "2h" }
        );

        // 将Token添加到tokens数组
        newUser.tokens.push({ token });

        // 保存用户到数据库
        await newUser.save();

        res.status(201).json({ message: "用户注册成功", user: newUser });
    } catch (err) {
        console.log(err);
        res.status(500).json({ error: "注册失败" });
    }
});

// 登录路由修复
router.post("/signin", async (req, res) => {
    try {
        const { email, password } = req.body;
        if (!email || !password) {
            return res.status(400).json({ error: "请填写邮箱和密码" });
        }

        // 查询用户
        const foundUser = await User.findOne({ email });
        if (!foundUser) {
            return res.status(400).json({ message: "用户不存在" });
        }

        // 密码校验
        const isMatch = await bcrypt.compare(password, foundUser.password);
        if (!isMatch) {
            return res.status(400).json({ message: "密码错误" });
        }

        // 生成Token
        const token = jwt.sign(
            { user_id: foundUser._id, email },
            process.env.TOKEN_KEY,
            { expiresIn: "2h" }
        );

        // 添加Token到用户的tokens数组并保存
        foundUser.tokens.push({ token });
        await foundUser.save();

        res.status(200).json({ message: "登录成功", user: foundUser, token });
    } catch (err) {
        console.log(err);
        res.status(500).json({ error: "登录失败" });
    }
});

module.exports = router;

注意事项

  • 改用async/await替代回调函数,避免嵌套逻辑混乱,代码可读性更强。
  • 确保每个请求仅发送一次响应,所有分支逻辑通过return终止后续代码执行。
  • 严格按照Schema定义操作数据,数组类型字段用push添加元素。
  • 登录流程无需重复哈希密码,直接使用bcrypt.compare对比原密码与数据库哈希值。

内容的提问来源于stack exchange,提问作者Jatin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 16:45:30