Error Handling
By default, Sofa returns a response that includes JSON representation of thrown error object from GraphQL with HTTP status code 500. But, you can enhance error handler by adding your errorHandler
function.
api.use(
'/api',
useSofa({
schema,
// `errors` is the array containing the `Error` objects
errorHandler(errors) {
for (const error of errors) {
console.error(`Error: ${error.message}`);
}
return new Response(errs[0].message, {
status: 500,
headers: { 'Content-Type': 'application/json' },
});
},
})
);
By default, it always returns a response with 200
if the request is valid.
If the request is invalid, it returns a response with 400
status code and the error message.
const res = await fetch('http://localhost:4000/api/createUser', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: 1, // Invalid name
}),
});
console.log(res.status); // 400
const data = await res.json();
console.log(data); // {"errors":[{"message":"Expected type String, found 1."}]}
HTTP Error Extensions
Just like GraphQL Yoga’s error handling , SOFA respects the status code and headers provided in the error extensions.
throw new GraphQLError(
`User with id '${args.byId}' not found.`,
// error extensions
{
extensions: {
http: {
status: 400,
headers: {
'x-custom-header': 'some-value',
},
},
},
}
);
In this case, you returns a response with 400
status code and x-custom-header
in the response headers.
Let’s say you have a simple GraphQL API like below;
import { createServer } from 'node:http';
import { useSofa } from 'sofa-api';
import { makeExecutableSchema } from '@graphql-tools/schema';
createServer(
useSofa({
basePath: '/api',
schema: makeExecutableSchema({
typeDefs: /* GraphQL */ `
type Query {
posts: [Post!]!
}
type Post {
id: ID!
title: String!
secret: String!
}
`,
resolvers: {
Query: {
posts() {
return getPosts();
},
},
Post: {
async secret(_, __, { request }) {
const authHeader = request.headers.get('Authorization');
if (!authHeader) {
throw new GraphQLError('Unauthorized', {
extensions: {
http: {
status: 401,
headers: {
'WWW-Authenticate': 'Bearer',
},
},
},
});
}
const [type, token] = authHeader.split(' ');
if (type !== 'Bearer') {
throw new GraphQLError('Invalid token type', {
extensions: {
http: {
status: 401,
headers: {
'WWW-Authenticate': 'Bearer',
},
},
},
});
}
if (token !== 'secret') {
throw new GraphQLError('Invalid token', {
extensions: {
http: {
status: 401,
headers: {
'WWW-Authenticate': 'Bearer',
},
},
},
});
}
return 'Secret value';
},
},
},
}),
})
).listen(4000);
In this case if you make a request to /api/posts
without a valid Authorization
header,
you will get a response with 401
status code and WWW-Authenticate
in the response headers.
But the response body will contain the data and errors.
const res = await fetch('http://localhost:4000/api/posts');
console.log(res.status); // 401
console.log(res.headers.get('WWW-Authenticate')); // Bearer
const data = await res.json();
expect(data).toEqual({
data: {
posts: [
{
id: '1',
title: 'Post 1',
secret: null,
},
{
id: '2',
title: 'Post 2',
secret: null,
},
],
},
errors: [
{
message: 'Unauthorized',
path: ['posts', 'secret'],
},
],
});
In this case only errored fields will be null
in the response body.
However if you make a request to /api/me
with x-user-id
header, you will get a response with 200
status code and x-custom-header
in the response headers.
const res = await fetch('http://localhost:4000/api/posts', {
headers: {
Authorization: 'Bearer secret',
},
});
console.log(res.status); // 200
const data = await res.json();
expect(data).toEqual({
data: {
posts: [
{
id: '1',
title: 'Post 1',
secret: 'Secret value',
},
{
id: '2',
title: 'Post 2',
secret: 'Secret value',
},
],
},
});