diff --git a/README.md b/README.md index e1f7bf59..1a1e96f3 100644 --- a/README.md +++ b/README.md @@ -173,6 +173,10 @@ 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 status code + or headers of the response in case of a runtime query error. By default, the status code is set to + `500`. + 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 diff --git a/src/__tests__/usage-test.ts b/src/__tests__/usage-test.ts index d63ee572..9c7dce80 100644 --- a/src/__tests__/usage-test.ts +++ b/src/__tests__/usage-test.ts @@ -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'; @@ -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] }); diff --git a/src/index.ts b/src/index.ts index 10a74752..b04fdb06 100644 --- a/src/index.ts +++ b/src/index.ts @@ -115,6 +115,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 @@ -200,6 +209,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. @@ -208,7 +218,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 ?? @@ -218,7 +228,7 @@ export function graphqlHTTP(options: Options): Middleware { } // Then, resolve the Options to get OptionsData. - const optionsData: OptionsData = await resolveOptions(params); + optionsData = await resolveOptions(params); // Collect information from the options data object. const schema = optionsData.schema; @@ -388,13 +398,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.