I’m working on a project with React frontend and Node.js backend using GraphQL. I need help with error handling in GraphQL operations. When my GraphQL queries fail, I want to send proper HTTP status codes (like 400 for bad requests, 409 for conflicts, etc.) from my server to the client side. The problem is that GraphQL always returns 200 OK even when there are errors in the response.
Server Setup (Node.js + GraphQL)
I’m using express-graphql for my GraphQL endpoint and trying to handle errors with a custom formatter:
const express = require('express');
const { graphqlHTTP } = require('express-graphql');
const { GraphQLError } = require('graphql');
const mongoose = require('mongoose');
const cors = require('cors');
const { buildSchema } = require('graphql');
const server = express();
const DATABASE_URL = process.env.DATABASE_CONNECTION;
const SERVER_PORT = 3000;
server.use(cors());
server.use(express.json());
server.use('/api/graphql', graphqlHTTP({
schema: mySchema,
rootValue: myResolvers,
graphiql: true,
formatError: (error) => {
console.log('Handling error:', error);
if (error instanceof GraphQLError) {
return new GraphQLError(error.message, {
originalError: error,
extensions: {
code: error.extensions?.code || 400,
details: error.extensions?.details || {},
},
});
}
return new GraphQLError('Something went wrong on server!', {
originalError: error,
extensions: { code: 500, details: {} },
});
},
}));
server.use((err, req, res, next) => {
const statusCode = err.statusCode || 500;
const errorMessage = err.message;
const errorData = err.data;
res.status(statusCode).json({ message: errorMessage, data: errorData });
});
const initializeServer = async () => {
try {
await mongoose.connect(DATABASE_URL);
server.listen(SERVER_PORT, () => {
console.log(`GraphQL server running on port ${SERVER_PORT}`);
});
} catch (error) {
console.log('Server initialization failed:', error);
}
};
initializeServer();
Client Side (React)
On the frontend, I’m making GraphQL requests but can’t get the proper status codes:
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
const RegistrationForm = () => {
const [loading, setLoading] = useState(false);
const [errorMsg, setErrorMsg] = useState(null);
const navigate = useNavigate();
const handleUserRegistration = async (formData) => {
setLoading(true);
const mutation = {
query: `
mutation RegisterUser($input: UserInput!) {
registerUser(userInput: $input) {
id
username
email
}
}
`,
variables: {
input: {
username: formData.username,
email: formData.email,
password: formData.password
}
}
};
try {
const response = await fetch('http://localhost:3000/api/graphql', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(mutation),
});
console.log('Response status:', response.status); // Always shows 200
const result = await response.json();
console.log('Server response:', result);
if (result.errors && result.errors.length > 0) {
const firstError = result.errors[0];
const errorCode = firstError.extensions?.code;
const errorDetails = firstError.extensions?.details;
const message = firstError.message;
if (errorCode === 409) {
setErrorMsg('This email is already registered!');
} else if (errorCode === 422) {
setErrorMsg('Please check your input data!');
} else {
setErrorMsg(message || 'Registration failed!');
}
setLoading(false);
return;
}
setLoading(false);
navigate('/dashboard');
} catch (error) {
console.error('Network error:', error);
setErrorMsg('Network error occurred!');
setLoading(false);
}
};
return (
<div>
{/* Registration form JSX here */}
</div>
);
};
export default RegistrationForm;
The Issue:
My backend creates errors with custom status codes in the extensions, but the HTTP response always comes back as 200 OK. The frontend never sees status codes like 409 or 422 in the actual HTTP response. I can access the error codes through the GraphQL error extensions, but I want to know if there’s a way to make the HTTP status reflect the actual error type.
Is there a standard approach for handling this with GraphQL and Express? Should I be setting the response status differently, or is working with error extensions the right way to go?