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

路由中间件报错Cannot set headers after they are sent to the client解决

解决Express校验中间件重复响应与控制器提前执行问题

最初的异步校验中间件实现

我最初编写了如下异步校验中间件:

export async function validationMiddleware<T extends object>(
  dtoClass: new () => T,
  validateTarget: string,
  req: Request,
  res: Response,
  next: NextFunction,
) {
  const dtoInstance = new dtoClass();
  const request = {
    body: req.body,
    query: req.query,
    params: req.params,
  };
  Object.assign(dtoInstance, request[validateTarget as keyof typeof request]);
  //request[validateTarget as keyof typeof request] = dtoInstance;
  const errors: ValidationError[] = await validate(dtoInstance, {
    forbidUnknownValues: true,
    whitelist: true,
    forbidNonWhitelisted: true,
  });
  if (errors.length > 0) {
    const validationErrors = errors.map((error) => ({
      [error.property]: Object.values(error.constraints || {}),
    }));
    const errorMessage = 'Invalid data provided';
    const errorResponse = { message: errorMessage, errors: validationErrors };
    return res.status(422).json(errorResponse);
  }
  next();
}

路由中这样调用:

userRoutes.post('/users/signup', (req, res, next) => {
  validationMiddleware(CreateUserDto, 'body', req, res, next);
  userController.signUp(req, res);
});

出现的问题

  • 当请求数据无校验错误时,会抛出Cannot set headers after they are sent to the client错误
  • 当请求数据有错误时,客户端能收到错误响应,但控制器方法signUp仍会执行

我尝试了多种方法都没能解决:

  • 处理validate的Promise结果以避免异步函数,实现同步流程
  • 引入hasError变量,返回该值,仅在无错误时调用控制器方法
  • 在无错误时通过else语句调用next()
  • 发送响应后用return终止中间件

改用Promise.then处理异步校验的尝试

后来我修改了中间件,移除async/await,改用then处理validate的Promise:

export function validationMiddleware<T extends object>(
  dtoClass: new () => T,
  validateTarget: string,
  req: Request,
  res: Response,
  next: NextFunction,
) {
  console.log('validation');
  let hasErrors = false;
  const dtoInstance = new dtoClass();
  const request = {
    body: req.body,
    query: req.query,
    params: req.params,
  };
  Object.assign(dtoInstance, request[validateTarget as keyof typeof request]);
  //request[validateTarget as keyof typeof request] = dtoInstance;
  //const errors: ValidationError[] = [];
  validate(dtoInstance, {
    forbidUnknownValues: true,
    whitelist: true,
    forbidNonWhitelisted: true,
  }).then((errors) => {
    if (errors.length > 0) {
      const validationErrors = errors.map((error) => ({
        [error.property]: Object.values(error.constraints || {}),
      }));
      const errorMessage = 'Invalid data provided';
      const errorResponse = { message: errorMessage, errors: validationErrors };
      res.status(422).json(errorResponse);
    } else {
      next();
    }
  });
}

但问题依旧:控制器方法还是会提前执行,因为校验的Promise解析完成时间晚于控制器方法的调用。

使用validateSync同步校验解决问题

最后发现class-validator的validate有同步版本validateSync,修改后的中间件核心逻辑如下:

const dtoInstance = new dtoClass();
const request = {
  body: req.body,
  query: req.query,
  params: req.params,
};
Object.assign(dtoInstance, request[validateTarget as keyof typeof request]);
//request[validateTarget as keyof typeof request] = dtoInstance;
const errors: ValidationError[] = validateSync(dtoInstance, {
  forbidUnknownValues: true,
  whitelist: true,
  forbidNonWhitelisted: true,
});
if (errors.length > 0) {
  console.log('validation');
  const validationErrors = errors.map((error) => ({
    [error.property]: Object.values(error.constraints || {}),
  }));
  const errorMessage = 'Invalid data provided';
  const errorResponse = { message: errorMessage, errors: validationErrors };
  res.status(422).json(errorResponse);
}
return errors.length > 0;

路由调用改为:

userRoutes.post('/users/signup', (req, res, next) => {
  if (!validationMiddleware(CreateUserDto, 'body', req, res, next))
    userController.signUp(req, res);
});

现在功能完全正常,实现了预期需求:校验请求,有错误则返回客户端,无错误则执行控制器逻辑,且校验逻辑未侵入控制器。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 11:15:12