Repository: mercurius-js/mercurius (js)

Issue:
GQL spec compliance for http status codes (200 everywhere)
I've been migrating from Apollo to Mercurius and noticed that queries that return GQL errors have status code 500. This trips up our client code, thinking it's a Network Error instead of an application error.

I took a look at the spec here:
https://github.com/graphql/graphql-over-http/blob/main/spec/GraphQLOverHTTP.md#applicationjson

It seems to mention that:

> The server SHOULD NOT use a 4xx or 5xx status code.

> Note: For compatibility with legacy servers, this specification allows the use of 4xx or 5xx status codes for failed requests where the response uses the application/json media type, but it is strongly discouraged. To use 4xx and 5xx status codes, please use the application/graphql-response+json media type

This has already been discussed here: https://github.com/mercurius-js/mercurius/issues/599#issuecomment-940734753 

I implemented a custom errorFormatter to address this as mentioned in the documentation.

However, now that the spec recommends `200` everywhere, I think it might make sense to change it in the next Mercurius version accordingly.

Pull request:
Breaking Change: statusCode 200 for all responses
Closes #860

Patch:
diff --git a/docs/api/options.md b/docs/api/options.md
index c9b1c7f..ca874df 100644
--- a/docs/api/options.md
+++ b/docs/api/options.md
@@ -544,10 +544,10 @@ To control the status code for the response, the third optional parameter can be
 
 ```js
 throw new mercurius.ErrorWithProps('Invalid User ID', {moreErrorInfo})
-// using the defaultErrorFormatter, the response statusCode will be 500 when there is a single error
+// using the defaultErrorFormatter, the response statusCode will be 200 as defined in the graphql-over-http spec
 
-throw new mercurius.ErrorWithProps('Invalid User ID', {moreErrorInfo}, 200)
-// using the defaultErrorFormatter, the response statusCode will be 200 when there is a single error
+throw new mercurius.ErrorWithProps('Invalid User ID', {moreErrorInfo}, 500)
+// using the defaultErrorFormatter, the response statusCode will be 500 as specified in the parameter
 
 const error = new mercurius.ErrorWithProps('Invalid User ID', {moreErrorInfo}, 500)
 error.data = {foo: 'bar'}
@@ -574,3 +574,12 @@ app.register(mercurius, {
     errorFormatter: (result) => ({ statusCode: result.errors[0].originalError.statusCode, response: result })
 })
 ```
+
+### Non-GraphQL Errors
+If an error is thrown (by either Fastify/Mercurius plugin or user code) before the request reaches the GraphQL layer then the error will be caught and format of the error in the response will be:
+```js
+{
+  statusCode
+  error // error code if any
+  message // error message
+}
diff --git a/docs/http.md b/docs/http.md
index 1129412..b91b31e 100644
--- a/docs/http.md
+++ b/docs/http.md
@@ -34,13 +34,14 @@ When a GraphQL input document is invalid and fails GraphQL validation, the HTTP
 - **Data**: `null`
 - **Errors**: `MER_ERR_GQL_VALIDATION`
 
-#### Multiple errors
+#### Response with errors
 
-When a GraphQL response contains multiple errors and no data, the HTTP Status Code is `400 Bad Request`.
+When a GraphQL response contains errors, the HTTP Status Code is `200 OK` as defined in the [GraphQL Over HTTP
+ Specification](https://github.com/graphql/graphql-over-http/blob/main/spec/GraphQLOverHTTP.md#applicationjson).
 
-- **HTTP Status Code**: `400 Bad Request`
+- **HTTP Status Code**: `200 OK`
 - **Data**: `null`
-- **Errors**: `Multiple`
+- **Errors**: `Array<GraphQLError>` (`.length >= 1`)
 
 #### Single error with `statusCode` property
 
@@ -48,90 +49,8 @@ When a GraphQL response contains a single error with the `statusCode` property s
 
 - **HTTP Status Code**: `Error statusCode`
 - **Data**: `null`
-- **Errors**: `Single`
-
-#### Single error with no `statusCode` property
-
-When a GraphQL response contains a single error with no `statusCode` property set and no data, the HTTP Status Code is `500 Internal Server Error`.
-
-- **HTTP Status Code**: `500 Internal Server Error`
-- **Data**: `null`
-- **Errors**: `Single with no .statusCode property`
+- **Errors**: `Array<GraphQLError>` (`.length === 1`)
 
 ### Custom behaviour
 
 If you wish to customise the default HTTP Status Code behaviour, one can do this using the [`errorFormatter`](./api/options.md#plugin-options) option.
-
-#### `200 OK` on all requests
-
-Enable `200 OK` on all requests as follows:
-
-```js
-'use strict'
-
-const Fastify = require('fastify')
-const mercurius = require('..')
-
-const app = Fastify()
-
-const schema = `
-  type Query {
-    add(x: Int, y: Int): Int
-  }
-`
-
-const resolvers = {
-  Query: {
-    add: async (_, obj) => {
-      const { x, y } = obj
-      return x + y
-    }
-  }
-}
-
-/**
- * Define error formatter so we always return 200 OK
- */
-function errorFormatter (err, ctx) {
-  const response = mercurius.defaultErrorFormatter(err, ctx)
-  response.statusCode = 200
-  return response
-}
-
-app.register(mercurius, {
-  schema,
-  resolvers,
-  errorFormatter
-})
-
-app.listen(3000)
-```
-
-With an invalid request:
-
-```graphql
-{
-  add(wrong: 1 x: 2)
-}
-```
-
-The response is:
-
-HTTP Status Code: `200 OK`
-
-```json
-{
-  "data": null,
-  "errors": [
-    {
-      "message": "Unknown argument \"wrong\" on field \"Query.add\".",
-      "locations": [
-        {
-          "line": 2,
-          "column": 7
-        }
-      ]
-    }
-  ]
-}
-```
\ No newline at end of file
diff --git a/lib/errors.js b/lib/errors.js
index 6dc659c..5fb4435 100644
--- a/lib/errors.js
+++ b/lib/errors.js
@@ -7,7 +7,7 @@ class ErrorWithProps extends Error {
   constructor (message, extensions, statusCode) {
     super(message)
     this.extensions = extensions
-    this.statusCode = statusCode || 500
+    this.statusCode = statusCode || 200
   }
 }
 
@@ -50,7 +50,7 @@ function defaultErrorFormatter (err, ctx) {
   // There is always app if there is a context
   const log = ctx.reply ? ctx.reply.log : ctx.app.log
 
-  let statusCode = err.data ? 200 : (err.statusCode || 500)
+  let statusCode = err.data ? 200 : (err.statusCode || 200)
   if (err.errors) {
     errors = err.errors.map((error, idx) => {
       log.info({ err: error }, error.message)
@@ -70,26 +70,29 @@ function defaultErrorFormatter (err, ctx) {
         // If single error defined, use status code if present
         if (typeof err.errors[0].originalError !== 'undefined' && typeof err.errors[0].originalError.statusCode === 'number') {
           statusCode = err.errors[0].originalError.statusCode
-        // Otherwise, use 500
+        // Otherwise, use 200 as per graphql-over-http spec
         } else {
-          statusCode = 500
+          statusCode = 200
         }
-      } else {
-        // Otherwise, if multiple errors are defined, set status code to 400
-        statusCode = 400
       }
     }
   } else {
     log.info({ err }, err.message)
   }
 
-  return {
-    statusCode,
-    response: {
-      data: err.data || null,
-      errors
-    }
-  }
+  // If error is not a GraphQLError, then send error message only
+  const response = err.errors
+    ? {
+        data: err.data || null,
+        errors
+      }
+    : {
+        statusCode,
+        error: err.extensions && err.extensions.code ? err.extensions.code : 'Internal Server Error',
+        message: err.message
+      }
+
+  return { statusCode, response }
 }
 
 function addErrorsToExecutionResult (execution, errors) {
diff --git a/lib/routes.js b/lib/routes.js
index 0c40ab0..b2dd6fc 100644
--- a/lib/routes.js
+++ b/lib/routes.js
@@ -358,7 +358,12 @@ module.exports = async function (app, opts) {
 
       return Promise.all(request.body.map(async (r, operationId) => {
         // Create individual reqs for multiple operations, otherwise reference the original req
-        const operationReq = operationsCount > 1 ? Object.create(request) : request
+        const operationReq = operationsCount > 1
+          ? {
+              ...request,
+              [kRequestContext]: Object.create(request[kRequestContext])
+            }
+          : request
 
         Object.assign(operationReq[kRequestContext], { operationId })
 
diff --git a/package.json b/package.json
index 3ce4b5b..6e55f53 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "mercurius",
-  "version": "10.5.0",
+  "version": "10.5.1",
   "description": "Fastify GraphQL adapter with gateway and subscription support",
   "main": "index.js",
   "types": "index.d.ts",
@@ -30,7 +30,7 @@
   },
   "devDependencies": {
     "@graphql-tools/merge": "^8.0.0",
-    "@graphql-tools/schema": "^8.0.0",
+    "@graphql-tools/schema": "^9.0.2",
     "@graphql-tools/utils": "^8.0.0",
     "@sinonjs/fake-timers": "^9.1.2",
     "@types/node": "^18.0.0",


Test patch:
diff --git a/tap-snapshots/test/routes.js.test.cjs b/tap-snapshots/test/routes.js.test.cjs
index 976c096..0bc98fd 100644
--- a/tap-snapshots/test/routes.js.test.cjs
+++ b/tap-snapshots/test/routes.js.test.cjs
@@ -103,8 +103,9 @@ exports['test/routes.js TAP server should return 200 on graphql errors (if field
 }
 `
 
-exports['test/routes.js TAP server should return 500 on graphql errors (if field can not be null) > must match snapshot 1'] = `
+exports['test/routes.js TAP server should return 200 on graphql errors (if field can not be null) > must match snapshot 1'] = `
 {
+  "data": null,
   "errors": [
     {
       "message": "Simple error",
@@ -118,7 +119,6 @@ exports['test/routes.js TAP server should return 500 on graphql errors (if field
         "hello"
       ]
     }
-  ],
-  "data": null
+  ]
 }
 `
diff --git a/test/batched.js b/test/batched.js
index cdf98c1..1147fda 100644
--- a/test/batched.js
+++ b/test/batched.js
@@ -388,3 +388,74 @@ test('POST batched query has an individual context for each operation', async (t
   sinon.assert.calledWith(contextSpy, 0, 2, sinon.match(/TestQuery/))
   sinon.assert.calledWith(contextSpy, 1, 2, sinon.match(/DoubleQuery/))
 })
+
+test('POST batched query respects custom class-based context', async (t) => {
+  const app = Fastify()
+
+  const schema = `
+    type Query {
+      test: String
+    }
+  `
+
+  class CustomContext {
+    constructor () {
+      this.test = 'custom'
+    }
+
+    method () {
+      return this.test
+    }
+  }
+
+  const resolvers = {
+    test: async (args, ctx) => {
+      t.type(ctx, 'object')
+      t.type(ctx.reply, 'object')
+      t.type(ctx.app, 'object')
+      t.type(ctx.method, 'function')
+      t.equal(ctx.test, 'custom')
+      t.equal(ctx.method(), 'custom')
+      t.equal(ctx.constructor, CustomContext)
+      return ctx.method()
+    }
+  }
+
+  app.register(GQL, {
+    schema,
+    resolvers,
+    context: (request, reply) => {
+      t.type(request, 'object')
+      t.type(reply, 'object')
+      return new CustomContext()
+    },
+    allowBatchedQueries: true
+  })
+
+  const post = await app.inject({
+    method: 'POST',
+    url: '/graphql',
+    body: [
+      {
+        operationName: 'TestQuery',
+        query: 'query TestQuery { test }'
+      },
+      {
+        operationName: 'DoubleQuery',
+        query: 'query DoubleQuery { test }'
+      }
+    ]
+  })
+
+  t.same(JSON.parse(post.body), [
+    {
+      data: {
+        test: 'custom'
+      }
+    }, {
+      data: {
+        test: 'custom'
+      }
+    }
+  ])
+})
diff --git a/test/errors.js b/test/errors.js
index 2e2649d..03670bc 100644
--- a/test/errors.js
+++ b/test/errors.js
@@ -836,7 +836,11 @@ test('bad json', async (t) => {
 
   t.equal(res.statusCode, 400)
   t.same(res.json(),
-    { data: null, errors: [{ message: 'Unexpected token h in JSON at position 1' }] }
+    {
+      statusCode: 400,
+      error: 'Internal Server Error',
+      message: 'Unexpected token h in JSON at position 1'
+    }
   )
 })
 
@@ -1030,7 +1034,7 @@ test('errors - should default to `statusCode` from error if present, when there
   t.equal(res.statusCode, 409)
 })
 
-test('errors - should default to HTTP Status Code `400 Bad Request` if multiple errors are present and no data in the response', async (t) => {
+test('errors - should default to HTTP Status Code `200 OK` if multiple errors are present and no data in the response', async (t) => {
   t.plan(2)
 
   const schema = `
@@ -1081,7 +1085,7 @@ test('errors - should default to HTTP Status Code `400 Bad Request` if multiple
       }
     ]
   })
-  t.equal(res.statusCode, 400)
+  t.equal(res.statusCode, 200)
 })
 
 test('errors - should default to HTTP Status Code `400 Bad Request` if GraphQL validation fails', async (t) => {
@@ -1128,7 +1132,7 @@ test('errors - should default to HTTP Status Code `400 Bad Request` if GraphQL v
   t.equal(res.statusCode, 400)
 })
 
-test('errors - should default to HTTP Status Code `500 Internal Server Error` if single error present but no status code defined', async (t) => {
+test('errors - should default to HTTP Status Code `200 OK` if single error present but no status code defined', async (t) => {
   t.plan(2)
 
   const schema = `
@@ -1177,5 +1181,50 @@ test('errors - should default to HTTP Status Code `500 Internal Server Error` if
       }
     ]
   })
+  t.equal(res.statusCode, 200)
+})
+
+test('errors - should throw HTTP Status Code `500 Internal Server Error` if non-GraphQL error', async (t) => {
+  t.plan(2)
+
+  const schema = `
+    type Query {
+      successful: String!
+    }
+  `
+
+  const resolvers = {
+    Query: {
+      successful () {
+        t.fail('Should not be called')
+        return 'Runs OK'
+      }
+    }
+  }
+
+  const app = Fastify()
+  t.teardown(app.close.bind(app))
+
+  app.register(GQL, {
+    schema,
+    resolvers
+  })
+
+  await app.ready()
+
+  app.graphql.addHook('preParsing', async function (schema, source, context) {
+    throw new Error('Test error')
+  })
+
+  const res = await app.inject({
+    method: 'GET',
+    url: '/graphql?query={successful}'
+  })
+
+  t.same(JSON.parse(res.body), {
+    statusCode: 500,
+    error: 'Internal Server Error',
+    message: 'Test error'
+  })
   t.equal(res.statusCode, 500)
 })
diff --git a/test/gateway/hooks-with-batching.js b/test/gateway/hooks-with-batching.js
index 1386167..596c012 100644
--- a/test/gateway/hooks-with-batching.js
+++ b/test/gateway/hooks-with-batching.js
@@ -455,12 +455,9 @@ test('gateway - preParsing hooks should handle errors', async t => {
   })
 
   t.same(JSON.parse(res.body), {
-    data: null,
-    errors: [
-      {
-        message: 'a preParsing error occured'
-      }
-    ]
+    statusCode: 500,
+    error: 'Internal Server Error',
+    message: 'a preParsing error occured'
   })
 })
 
@@ -555,12 +552,9 @@ test('gateway - preValidation hooks should handle errors', async t => {
   })
 
   t.same(JSON.parse(res.body), {
-    data: null,
-    errors: [
-      {
-        message: 'a preValidation error occured'
-      }
-    ]
+    statusCode: 500,
+    error: 'Internal Server Error',
+    message: 'a preValidation error occured'
   })
 })
 
@@ -651,12 +645,9 @@ test('gateway - preExecution hooks should handle errors', async t => {
   })
 
   t.same(JSON.parse(res.body), {
-    data: null,
-    errors: [
-      {
-        message: 'a preExecution error occured'
-      }
-    ]
+    statusCode: 500,
+    error: 'Internal Server Error',
+    message: 'a preExecution error occured'
   })
 })
 
@@ -1175,12 +1166,9 @@ test('gateway - onResolution hooks should handle errors', async t => {
   })
 
   t.same(JSON.parse(res.body), {
-    data: null,
-    errors: [
-      {
-        message: 'a onResolution error occured'
-      }
-    ]
+    statusCode: 500,
+    error: 'Internal Server Error',
+    message: 'a onResolution error occured'
   })
 })
 
diff --git a/test/gateway/hooks.js b/test/gateway/hooks.js
index f770c18..017679f 100644
--- a/test/gateway/hooks.js
+++ b/test/gateway/hooks.js
@@ -452,12 +452,9 @@ test('gateway - preParsing hooks should handle errors', async t => {
   })
 
   t.same(JSON.parse(res.body), {
-    data: null,
-    errors: [
-      {
-        message: 'a preParsing error occured'
-      }
-    ]
+    statusCode: 500,
+    error: 'Internal Server Error',
+    message: 'a preParsing error occured'
   })
 })
 
@@ -552,12 +549,9 @@ test('gateway - preValidation hooks should handle errors', async t => {
   })
 
   t.same(JSON.parse(res.body), {
-    data: null,
-    errors: [
-      {
-        message: 'a preValidation error occured'
-      }
-    ]
+    statusCode: 500,
+    error: 'Internal Server Error',
+    message: 'a preValidation error occured'
   })
 })
 
@@ -648,12 +642,9 @@ test('gateway - preExecution hooks should handle errors', async t => {
   })
 
   t.same(JSON.parse(res.body), {
-    data: null,
-    errors: [
-      {
-        message: 'a preExecution error occured'
-      }
-    ]
+    statusCode: 500,
+    error: 'Internal Server Error',
+    message: 'a preExecution error occured'
   })
 })
 
@@ -1172,12 +1163,9 @@ test('gateway - onResolution hooks should handle errors', async t => {
   })
 
   t.same(JSON.parse(res.body), {
-    data: null,
-    errors: [
-      {
-        message: 'a onResolution error occured'
-      }
-    ]
+    statusCode: 500,
+    error: 'Internal Server Error',
+    message: 'a onResolution error occured'
   })
 })
 
diff --git a/test/hooks-with-batching.js b/test/hooks-with-batching.js
new file mode 100644
index 0000000..a4ebeb2
--- /dev/null
+++ b/test/hooks-with-batching.js
@@ -0,0 +1,78 @@
+'use strict'
+
+const { test } = require('tap')
+const Fastify = require('fastify')
+const sinon = require('sinon')
+const GQL = require('..')
+
+test('batched query has an individual context for each operation through all the lifecycle hooks', async (t) => {
+  const app = Fastify()
+
+  const preParsingSpy = sinon.spy()
+  const preValidationSpy = sinon.spy()
+  const preExecutionSpy = sinon.spy()
+  const onResolutionSpy = sinon.spy()
+
+  const schema = `
+      type Query {
+        test: String
+      }
+    `
+
+  const resolvers = {
+    test: () => 'test'
+  }
+
+  await app.register(GQL, {
+    schema,
+    resolvers,
+    allowBatchedQueries: true
+  })
+
+  app.graphql.addHook('preParsing', (_, __, ctx) => {
+    preParsingSpy(ctx.operationId, ctx.operationsCount, ctx.__currentQuery)
+  })
+
+  app.graphql.addHook('preValidation', (_, __, ctx) => {
+    preValidationSpy(ctx.operationId, ctx.operationsCount, ctx.__currentQuery)
+  })
+
+  app.graphql.addHook('preExecution', (_, __, ctx) => {
+    preExecutionSpy(ctx.operationId, ctx.operationsCount, ctx.__currentQuery)
+  })
+
+  app.graphql.addHook('onResolution', (_, ctx) => {
+    onResolutionSpy(ctx.operationId, ctx.operationsCount, ctx.__currentQuery)
+  })
+
+  await app.inject({
+    method: 'POST',
+    url: '/graphql',
+    body: [
+      {
+        operationName: 'TestQuery',
+        query: 'query TestQuery { test }'
+      },
+      {
+        operationName: 'DoubleQuery',
+        query: 'query DoubleQuery { test }'
+      }
+    ]
+  })
+
+  sinon.assert.calledTwice(preParsingSpy)
+  sinon.assert.calledWith(preParsingSpy, 0, 2, sinon.match(/TestQuery/))
+  sinon.assert.calledWith(preParsingSpy, 1, 2, sinon.match(/DoubleQuery/))
+
+  sinon.assert.calledTwice(preValidationSpy)
+  sinon.assert.calledWith(preValidationSpy, 0, 2, sinon.match(/TestQuery/))
+  sinon.assert.calledWith(preValidationSpy, 1, 2, sinon.match(/DoubleQuery/))
+
+  sinon.assert.calledTwice(preExecutionSpy)
+  sinon.assert.calledWith(preExecutionSpy, 0, 2, sinon.match(/TestQuery/))
+  sinon.assert.calledWith(preExecutionSpy, 1, 2, sinon.match(/DoubleQuery/))
+
+  sinon.assert.calledTwice(onResolutionSpy)
+  sinon.assert.calledWith(onResolutionSpy, 0, 2, sinon.match(/TestQuery/))
+  sinon.assert.calledWith(onResolutionSpy, 1, 2, sinon.match(/DoubleQuery/))
+})
diff --git a/test/hooks.js b/test/hooks.js
index f71e1b7..d973c35 100644
--- a/test/hooks.js
+++ b/test/hooks.js
@@ -273,12 +273,9 @@ test('preParsing hooks should handle errors', async t => {
   })
 
   t.same(JSON.parse(res.body), {
-    data: null,
-    errors: [
-      {
-        message: 'a preParsing error occured'
-      }
-    ]
+    statusCode: 500,
+    error: 'Internal Server Error',
+    message: 'a preParsing error occured'
   })
 })
 
@@ -317,15 +314,9 @@ test('preParsing hooks should handle ErrorWithProps', async t => {
   })
 
   t.same(JSON.parse(res.body), {
-    data: null,
-    errors: [
-      {
-        message: 'a preParsing error occured',
-        extensions: {
-          code: 'USER_ID_INVALID'
-        }
-      }
-    ]
+    statusCode: 500,
+    error: 'USER_ID_INVALID',
+    message: 'a preParsing error occured'
   })
 })
 
@@ -395,12 +386,9 @@ test('preValidation hooks should handle errors', async t => {
   })
 
   t.same(JSON.parse(res.body), {
-    data: null,
-    errors: [
-      {
-        message: 'a preValidation error occured'
-      }
-    ]
+    statusCode: 500,
+    error: 'Internal Server Error',
+    message: 'a preValidation error occured'
   })
 })
 
@@ -466,12 +454,9 @@ test('preExecution hooks should handle errors', async t => {
   })
 
   t.same(JSON.parse(res.body), {
-    data: null,
-    errors: [
-      {
-        message: 'a preExecution error occured'
-      }
-    ]
+    statusCode: 500,
+    error: 'Internal Server Error',
+    message: 'a preExecution error occured'
   })
 })
 
@@ -863,12 +848,9 @@ test('onResolution hooks should handle errors', async t => {
   })
 
   t.same(JSON.parse(res.body), {
-    data: null,
-    errors: [
-      {
-        message: 'a onResolution error occured'
-      }
-    ]
+    statusCode: 500,
+    error: 'Internal Server Error',
+    message: 'a onResolution error occured'
   })
 })
 
diff --git a/test/persisted.js b/test/persisted.js
index ea4f499..06b575f 100644
--- a/test/persisted.js
+++ b/test/persisted.js
@@ -235,7 +235,11 @@ test('automatic POST new persisted query and error', async (t) => {
     }
   })
 
-  t.same(JSON.parse(res.body), { data: null, errors: [{ message: 'PersistedQueryNotFound' }] })
+  t.same(JSON.parse(res.body), {
+    statusCode: 400,
+    error: 'Internal Server Error',
+    message: 'PersistedQueryNotFound'
+  })
 })
 
 test('automatic POST invalid version persisted query and error', async (t) => {
@@ -272,7 +276,11 @@ test('automatic POST invalid version persisted query and error', async (t) => {
     }
   })
 
-  t.same(JSON.parse(res.body), { data: null, errors: [{ message: 'PersistedQueryNotSupported' }] })
+  t.same(JSON.parse(res.body), {
+    statusCode: 400,
+    error: 'Internal Server Error',
+    message: 'PersistedQueryNotSupported'
+  })
 })
 
 test('automatic POST invalid extension and error', async (t) => {
@@ -303,7 +311,11 @@ test('automatic POST invalid extension and error', async (t) => {
     }
   })
 
-  t.same(JSON.parse(res.body), { data: null, errors: [{ message: 'Unknown query' }] })
+  t.same(JSON.parse(res.body), {
+    statusCode: 400,
+    error: 'Internal Server Error',
+    message: 'Unknown query'
+  })
 })
 
 test('automatic POST invalid extension without persistedQueries and error', async (t) => {
@@ -337,7 +349,11 @@ test('automatic POST invalid extension without persistedQueries and error', asyn
     }
   })
 
-  t.same(JSON.parse(res.body), { data: null, errors: [{ message: 'PersistedQueryNotSupported' }] })
+  t.same(JSON.parse(res.body), {
+    statusCode: 400,
+    error: 'Internal Server Error',
+    message: 'PersistedQueryNotSupported'
+  })
 })
 
 test('automatic POST persisted query after priming', async (t) => {
diff --git a/test/routes.js b/test/routes.js
index 822b9cf..a2d334c 100644
--- a/test/routes.js
+++ b/test/routes.js
@@ -69,8 +69,9 @@ test('POST route, no query error', async (t) => {
 
   t.equal(res.statusCode, 400)
   t.same(JSON.parse(res.body), {
-    errors: [{ message: 'Unknown query' }],
-    data: null
+    statusCode: 400,
+    error: 'Internal Server Error',
+    message: 'Unknown query'
   })
 })
 
@@ -1119,7 +1120,7 @@ test('server should return 200 on graphql errors (if field can be null)', async
   t.matchSnapshot(JSON.stringify(JSON.parse(response.body), null, 2))
 })
 
-test('server should return 500 on graphql errors (if field can not be null)', async (t) => {
+test('server should return 200 on graphql errors (if field can not be null)', async (t) => {
   const app = Fastify()
   const schema = `
     type Query {
@@ -1148,7 +1149,7 @@ test('server should return 500 on graphql errors (if field can not be null)', as
     body: { query }
   })
 
-  t.equal(response.statusCode, 500)
+  t.equal(response.statusCode, 200)
   t.matchSnapshot(JSON.stringify(JSON.parse(response.body), null, 2))
 })