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

NodeJS JWT登录后私有路由报错:Cannot set headers after they are sent to the client

问题

开发在线医疗咨询应用后端时,JWT认证遇到异常:
项目包含/register、/login、/appointments三个路由,/register和/login可正常访问,但用/login生成的JWT Token放入Authorization请求头访问/appointments私有路由时,抛出如下错误:

node:internal/errors:484
    ErrorCaptureStackTrace(err);
    ^

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 (C:\Users\krish\Desktop\mrcooper-task\server\node_modules\express\lib\response.js:794:10)
    at ServerResponse.send (C:\Users\krish\Desktop\mrcooper-task\server\node_modules\express\lib\response.js:174:12)
    at module.exports.login (C:\Users\krish\Desktop\mrcooper-task\server\controllers\authController.js:62:7) {
  code: 'ERR_HTTP_HEADERS_SENT'
}

Node.js v18.12.0

奇怪的是,重启服务器后用同一个Token能正常访问/appointments路由。已知该错误通常是单次请求发送多个响应导致,但检查代码后未发现明显多响应情况,疑惑是否存在条件分支中的响应泄漏,想知道重启前报错的原因。

相关代码

index.js

const express = require("express");
const cors = require("cors");
const mongoose = require("mongoose");
const app = express();

require('dotenv').config();

//External routes
const authRoutes = require("./routes/authRoutes");
const appointRoutes = require("./routes/appointRoutes");

// Middlewares
app.use(cors());
app.use(express.json());

//DB connection
const dbURI =
  process.env.DB_URL;
mongoose
  .connect(dbURI, { useNewUrlParser: true, useUnifiedTopology: true })
  .then((result) =>
    app.listen(8080, () =>
      console.log("App sucessfully started on localhost port 8080")
    )
  )
  .catch((err) => console.log(err));

//Internal routes
app.use(authRoutes);
app.use(appointRoutes);

appointRoutes.js

const { Router } = require("express");
const verify = require("./verifyToken")

const router = Router();

router.get("/appointments",verify, (req, res) => {
  res.send({ message: "Appointment route" });
});

module.exports = router;

authRoutes.js

const { Router } = require("express");
const User = require("../models/User");
const bcrypt = require("bcryptjs");
const jwt = require("jsonwebtoken");
const { registerValidator, loginValidator } = require("../validation");

const router = Router();

router.post("/register", async (req, res) => {
  //Validate data before creating a user
  const { error } = registerValidator(req.body);
  if (error) {
    return res.status(400).send(error.details[0].message);
  }
  //Check if email already exists
  const emailExists = await User.findOne({ email: req.body.email });
  if (emailExists) {
    return res.status(400).send("Email already exists");
  }

  const { name, email, password, catogery, DOB } = req.body;

  //Hash password
  const salt = await bcrypt.genSalt(10);
  const hashedPassword = await bcrypt.hash(password, salt);
  try {
    const user = await User.create({
      name,
      email,
      password: hashedPassword,
      catogery,
      DOB,
    });
    res.status(201).json({ user: user._id });
  } catch (err) {
    res.status(400).send(err);
  }
});

router.post("/login", async (req, res) => {
  const { email, password } = req.body;

  //Validate data before authenticating a user
  const { error } = loginValidator(req.body);
  if (error) {
    return res.status(400).send(error.details[0].message);
  }
  //Check if email doesn't exists
  const user = await User.findOne({ email: req.body.email });
  if (!user) {
    return res
      .status(400)
      .send("Email doesn't exists. Please register and try again");
  }
  //Password is incorrect
  const validPassword = await bcrypt.compare(password, user.password);
  if (!validPassword) return res.status(400).send("Invalid Password");

  //Create and assign JWT token
  const token = jwt.sign({_id: user._id}, process.env.TOKEN_SECRET)
  res.header('auth-token', token).send(token);
  res.send("Logged In!");
});

module.exports = router;

问题分析与解决

根源:单次请求发送多个响应

错误日志明确指向login方法中的多次响应。看authRoutes.js里的/login路由代码:

res.header('auth-token', token).send(token);
res.send("Logged In!");

这里先调用res.send(token)发送了一次完整响应,紧接着又调用res.send("Logged In!")尝试发送第二次响应。HTTP协议要求单次请求只能对应一个响应,第二次尝试发送时,响应头已经发送给客户端,所以触发ERR_HTTP_HEADERS_SENT错误。

为什么重启后现象看似正常?

重启服务器后,你大概率没有重复调用/login接口,而是直接使用之前生成的有效Token访问/appointments——此时/login路由没有被触发,自然不会出现多次响应的错误,所以/appointments能正常访问。本质上不是重启修复了问题,只是避开了触发错误的场景。

修复方案

修改/login路由,确保单次请求只发送一次响应。可以选择合并信息或者保留单一响应:

// 方案1:同时返回Token和登录提示
const token = jwt.sign({_id: user._id}, process.env.TOKEN_SECRET)
res.header('auth-token', token).send({
  message: "Logged In!",
  token: token
});

// 方案2:仅返回Token(兼容原有客户端逻辑)
const token = jwt.sign({_id: user._id}, process.env.TOKEN_SECRET)
res.header('auth-token', token).send(token);

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 14:50:30