Skip to content
This repository has been archived by the owner on Mar 20, 2023. It is now read-only.

Allow custom handling of runtime query errors #780

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,9 @@ The `graphqlHTTP` function accepts the following options:
- **`formatError`**: is deprecated and replaced by `customFormatErrorFn`. It will be
removed in version 1.0.0.

- **`handleRuntimeQueryErrorFn`**: An optional function which can be used to change the headers or status code of the response in case of a runtime query error. By default, the status code is set to `500`.
To ensure compatibility, make sure to take note of [the GraphQL spec regarding status codes](https://github.com/graphql/graphql-over-http/blob/master/spec/GraphQLOverHTTP.md#status-codes).

In addition to an object defining each option, options can also be provided as
a function (or async function) which returns this options object. This function
is provided the arguments `(request, response, graphQLParams)` and is called
Expand Down
41 changes: 40 additions & 1 deletion src/__tests__/usage-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@ import express from 'express';
import request from 'supertest';
import { expect } from 'chai';
import { describe, it } from 'mocha';
import { GraphQLSchema } from 'graphql';
import {
GraphQLNonNull,
GraphQLObjectType,
GraphQLSchema,
GraphQLString,
} from 'graphql';

import { graphqlHTTP } from '../index';

Expand Down Expand Up @@ -96,6 +101,40 @@ describe('Useful errors when incorrectly used', () => {
});
});

it('uses the custom runtime query error handling function', async () => {
const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'QueryRoot',
fields: {
test: {
type: new GraphQLNonNull(GraphQLString),
resolve() {
throw new Error('Throws!');
},
},
},
}),
});

const app = express();

app.use(
'/graphql',
graphqlHTTP({
handleRuntimeQueryErrorFn(_, response) {
response.setHeader('customRuntimeQueryError', "I'm a teapot");
response.statusCode = 418;
},
schema,
}),
);

const response = await request(app).get('/graphql?query={test}');

expect(response.status).to.equal(418);
expect(response.get('customRuntimeQueryError')).to.equal("I'm a teapot");
});

it('validates schema before executing request', async () => {
// @ts-expect-error
const schema = new GraphQLSchema({ directives: [null] });
Expand Down
37 changes: 28 additions & 9 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,15 @@ export interface OptionsData {
*/
formatError?: (error: GraphQLError) => GraphQLFormattedError;

/**
* Use this to modify the response when a runtime query error occurs. By
* default the statusCode will be set to 500.
*/
handleRuntimeQueryErrorFn?: (
result: ExecutionResult,
response: Response,
) => void;

/**
* An optional function for adding additional metadata to the GraphQL response
* as a key-value object. The result will be added to "extensions" field in
Expand Down Expand Up @@ -197,6 +206,7 @@ export function graphqlHTTP(options: Options): Middleware {
let formatErrorFn = formatError;
let pretty = false;
let result: ExecutionResult;
let optionsData: OptionsData | undefined;

try {
// Parse the Request to get GraphQL request parameters.
Expand All @@ -205,7 +215,7 @@ export function graphqlHTTP(options: Options): Middleware {
} catch (error: unknown) {
// When we failed to parse the GraphQL parameters, we still need to get
// the options object, so make an options call to resolve just that.
const optionsData = await resolveOptions();
optionsData = await resolveOptions();
pretty = optionsData.pretty ?? false;
formatErrorFn =
optionsData.customFormatErrorFn ??
Expand All @@ -215,7 +225,7 @@ export function graphqlHTTP(options: Options): Middleware {
}

// Then, resolve the Options to get OptionsData.
const optionsData = await resolveOptions(params);
optionsData = await resolveOptions(params);

// Collect information from the options data object.
const schema = optionsData.schema;
Expand Down Expand Up @@ -384,13 +394,22 @@ export function graphqlHTTP(options: Options): Middleware {
}
}

// If no data was included in the result, that indicates a runtime query
// error, indicate as such with a generic status code.
// Note: Information about the error itself will still be contained in
// the resulting JSON payload.
// https://graphql.github.io/graphql-spec/#sec-Data
if (response.statusCode === 200 && result.data == null) {
response.statusCode = 500;
if (result.errors != null || result.data == null) {
const handleRuntimeQueryErrorFn =
optionsData?.handleRuntimeQueryErrorFn ??
((_result, _response) => {
// If no data was included in the result, that indicates a runtime query
// error, indicate as such with a generic status code.
// Note: Information about the error itself will still be contained in
// the resulting JSON payload.
// https://graphql.github.io/graphql-spec/#sec-Data

if (_response.statusCode === 200 && _result.data == null) {
_response.statusCode = 500;
}
});

handleRuntimeQueryErrorFn(result, response);
}

// Format any encountered errors.
Expand Down