Express应用中GraphQL错误状态码的前后端传递处理方法
解决Express+GraphQL后端传递自定义HTTP状态码的问题
GraphQL 规范默认所有响应都返回 200 OK,错误信息会放在响应体的 errors 数组中,这就是你前端始终拿到 200 的核心原因。要传递 409、422 这类状态码,需要在 Express 层面拦截响应,根据错误信息动态修改 HTTP 状态码,具体步骤如下:
1. 定义自定义错误类
创建带状态码标识的错误类,方便在 resolver 中抛出时标记对应的 HTTP 状态:
class GraphQLHTTPError extends Error { constructor(message, statusCode) { super(message); this.name = 'GraphQLHTTPError'; this.statusCode = statusCode; Error.captureStackTrace(this, this.constructor); } }
2. 在 Resolver 中抛出自定义错误
处理业务逻辑时,遇到冲突、验证失败等场景,抛出自定义错误:
// 示例:创建用户的 mutation resolver createUser: async (parent, { email }, context) => { const existingUser = await User.findOne({ email }); if (existingUser) { // 抛出409冲突错误 throw new GraphQLHTTPError('该邮箱已被注册', 409); } // 验证参数格式 if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { // 抛出422验证错误 throw new GraphQLHTTPError('邮箱格式无效', 422); } // 正常创建用户逻辑 const user = await User.create({ email }); return user; }
3. 配置 GraphQL 错误格式化
将自定义错误转换为 GraphQL 规范的错误格式,并在 extensions 中携带状态码:
如果你用 Apollo Server Express:
const { ApolloServer } = require('apollo-server-express'); const server = new ApolloServer({ typeDefs, resolvers, formatError: (err) => { // 识别自定义错误 if (err.originalError instanceof GraphQLHTTPError) { return { message: err.message, extensions: { statusCode: err.originalError.statusCode, code: err.originalError.name } }; } // 其他错误保持默认处理 return err; }, // 将 req/res 传入上下文(可选,若需在 resolver 中直接操作响应) context: ({ req, res }) => ({ req, res }) }); // 挂载到 Express server.applyMiddleware({ app, path: '/graphql' });
如果你用 express-graphql:
const { graphqlHTTP } = require('express-graphql'); app.use('/graphql', graphqlHTTP({ schema, rootValue: resolvers, formatError: (err) => { if (err.originalError instanceof GraphQLHTTPError) { return { message: err.message, extensions: { statusCode: err.originalError.statusCode } }; } return err; } }));
4. 添加 Express 响应拦截中间件
这是关键步骤:拦截 GraphQL 响应,根据 errors 中的状态码修改 HTTP 状态:
app.use((req, res, next) => { // 仅处理 GraphQL 路径的 POST 请求 if (req.path === '/graphql' && req.method === 'POST') { const originalSend = res.send; res.send = function(data) { try { const responseData = JSON.parse(data); // 提取第一个错误的状态码(若存在) if (responseData.errors?.length) { const statusCode = responseData.errors[0].extensions?.statusCode; if (typeof statusCode === 'number') { res.status(statusCode); } } } catch (e) { // 解析失败时保持默认状态码 } // 调用原 send 方法发送响应 return originalSend.call(this, data); }; } next(); });
前端处理示例
现在前端可以正常获取到对应的 HTTP 状态码,用 fetch 示例:
const createUserMutation = ` mutation CreateUser($email: String!) { createUser(email: $email) { id email } } `; fetch('/graphql', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query: createUserMutation, variables: { email: 'test@example.com' } }) }) .then(res => { // 根据状态码处理不同错误场景 if (res.status === 409) { alert('邮箱已被注册'); } else if (res.status === 422) { alert('邮箱格式错误'); } return res.json(); }) .then(data => { if (!data.errors) { console.log('用户创建成功', data.data.createUser); } })
为什么之前的方法没生效?
- 仅在
extensions中设置statusCode不会改变 HTTP 状态,因为 GraphQL 规范默认返回 200 res.ok基于 HTTP 状态码判断,之前状态码一直是 200,所以res.ok始终为 true,无法区分错误场景
内容的提问来源于stack exchange,提问作者Mr.Unforgettable
相关产品推荐
相关产品推荐

