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

TypeScript客户端提取ApolloError消息的技术问询

处理Apollo Server中addUser突变的重复键错误提取问题

我正在学习TypeScript和Apollo Server,现在遇到一个问题:在处理addUser突变时,要求用户名和邮箱唯一,验证通过时正常,但验证失败(比如重复键)时,无法从ApolloError中提取错误消息返回给客户端。MongoDB返回的错误信息类似:

duplicate key error collection: be_for_reel_db.users index: userName_1 dup key: { userName: "foo" }

但前端无法提取这段文本提示用户。技术栈:React 18、Apollo Server Express 3.6.2、GraphQL 16.3.0。

客户端代码

客户端用try-catch处理突变:

try {
  const { data } = await addUser({
    variables: {
      userName: signupForm.signupUsername,
      email: signupForm.signupEmail,
      password: signupForm.signupPassword,
    },
  });
} catch (err) {
  setErrorMessage(true);
  console.log(err);
}

这里console.log能看到ApolloError,但TypeScript报错"err" is of type unknown,无法直接访问err.message。

已尝试的方法

  • 尝试从Apollo客户端导入ApolloError作为类型,编译报错。
  • 服务器端用formatError自定义错误:
    const server = new ApolloServer({
      typeDefs,
      resolvers,
      context: authMiddleware,
      formatError: (formattedError, error) => {
        return { message: "This is my custom error" };
      },
    });
    
    自定义消息能返回,但前端还是无法访问。
  • 解析器中添加逻辑:
    Mutation: {
      addUser: async (parent, args) => {
        const { userName, email, password } = args;
        const today = Date();
        const newUser = {
          userName,
          email,
          password,
          created: today,
          polls: [],
          votes: [],
          comments: [],
        };
    
        const user = await User.create(newUser);
    
        if (!user) return { message: "Operation failed" };
    
        const token = signToken(user);
        return { token, user };
      },
      // 其他突变
    }
    
    但突变失败时Apollo直接抛出错误,剩余代码不会执行,if (!user)永远走不到。

后续尝试

按建议移除客户端try-catch,错误直接显示在浏览器控制台,但还是无法提取。又在服务器端解析错误:

try {
  const user = await User.create(newUser);
  return user;
} catch (err) {
  const pieces = err.message.split(": ");
  let message = pieces.pop();
  message = `${pieces.pop()}: ${message}`;
  return message;
}

解析后能生成类似{ username: foo }的消息,但返回客户端后还是被包裹在ApolloError里,前端依旧无法访问。


解决方案

1. 服务器端正确捕获并格式化MongoDB重复键错误

在解析器中,需要捕获MongoDB的重复键错误(错误码11000),然后抛出GraphQL错误,而不是直接返回字符串。这样Apollo Server会正确处理错误,并传递给客户端:

import { ApolloError } from 'apollo-server-express';

Mutation: {
  addUser: async (parent, args) => {
    const { userName, email, password } = args;
    const today = new Date().toISOString(); // 建议用标准格式的时间字符串
    const newUser = {
      userName,
      email,
      password,
      created: today,
      polls: [],
      votes: [],
      comments: [],
    };

    try {
      const user = await User.create(newUser);
      const token = signToken(user);
      return { token, user };
    } catch (err: any) {
      // 捕获MongoDB重复键错误
      if (err.code === 11000) {
        // 提取重复的字段(userName或email)
        const duplicateField = Object.keys(err.keyPattern)[0];
        throw new ApolloError(
          `该${duplicateField === 'userName' ? '用户名' : '邮箱'}已被占用`,
          'DUPLICATE_FIELD'
        );
      }
      // 其他错误统一处理
      throw new ApolloError('创建用户失败,请稍后重试');
    }
  },
}

2. 客户端正确处理ApolloError并提取消息

在客户端,需要先判断错误类型,TypeScript中可以用类型守卫来安全访问错误属性:

import { ApolloError } from '@apollo/client';

try {
  const { data } = await addUser({
    variables: {
      userName: signupForm.signupUsername,
      email: signupForm.signupEmail,
      password: signupForm.signupPassword,
    },
  });
  // 处理用户创建成功后的逻辑,比如跳转、存储token等
} catch (err) {
  if (err instanceof ApolloError) {
    // 提取错误消息并展示给用户
    setErrorMessage(err.message);
    console.log('错误详情:', err);
  } else {
    setErrorMessage('未知错误,请稍后重试');
  }
}

3. 优化服务器端formatError(可选)

如果需要统一格式化所有错误,确保客户端能拿到标准结构的错误信息,可以配置formatError:

const server = new ApolloServer({
  typeDefs,
  resolvers,
  context: authMiddleware,
  formatError: (formattedError) => {
    return {
      message: formattedError.message,
      code: formattedError.extensions?.code || 'UNKNOWN_ERROR',
      ...(formattedError.extensions?.exception && {
        details: formattedError.extensions.exception
      })
    };
  },
});

这样客户端不仅能拿到错误消息,还能根据code字段做针对性处理(比如针对DUPLICATE_FIELD错误做特殊提示样式)。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 21:17:02