How to set http status code in GraphQL

2020-04-15 11:47发布

问题:

I want to set an http status code in my GraphQL authentication query, depending on if auth attempt was successful (200), unauthorised (401) or missing parameters (422).

I am using Koa and Apollo and have configured my server like so:

const graphqlKoaMiddleware = graphqlKoa(ctx => {
  return ({
    schema,
    formatError: (err) => ({ message: err.message, status: err.status }),
    context: {
      stationConnector: new StationConnector(),
      passengerTypeConnector: new PassengerTypeConnector(),
      authConnector: new AuthConnector(),
      cookies: ctx.cookies
    }
  })
})

router.post("/graphql", graphqlKoaMiddleware)

As you can see, I have set my formatError to return a message and status but currently only the message is getting returned. The error message comes from the error that I throw in my resolver function.

For example:

const resolvers = {
  Query: {
    me: async (obj, {username, password}, ctx) => {
      try {
        return await ctx.authConnector.getUser(ctx.cookies)  
      }catch(err){
        throw new Error(`Could not get user: ${err}`);
      }
    }
  }
}

My only issue with this method is it is setting the status code in the error message and not actually updating the response object.

Does GraphQL require a 200 response even for failed queries / mutations or can I some how update the response objects status code? If not, How do I set the aforementioned error object status code?

回答1:

Unless the GraphQL request itself is malformed, GraphQL will return a 200 status code, even if an error is thrown inside one of the resolvers. This is by design so there's not really a way to configure Apollo server to change this behavior.

That said, you could easily wire up your own middleware. You can import the runHttpQuery function that the Apollo middleware uses under the hood. In fact, you could pretty much copy the source code and just modify it to suit your needs:

const graphqlMiddleware = options => {
  return (req, res, next) => {
    runHttpQuery([req, res], {
      method: req.method,
      options: options,
      query: req.method === 'POST' ? req.body : req.query,
    }).then((gqlResponse) => {
      res.setHeader('Content-Type', 'application/json')

      // parse the response for errors and set status code if needed

      res.write(gqlResponse)
      res.end()
      next()
    }, (error) => {
      if ( 'HttpQueryError' !== error.name ) {
        return next(error)
      }

      if ( error.headers ) {
        Object.keys(error.headers).forEach((header) => {
          res.setHeader(header, error.headers[header])
        })
      }

      res.statusCode = error.statusCode
      res.write(error.message)
      res.end()
      next(false)
    })
  }
}


回答2:

as you can see here formatError doesn't support status code, what you could do is create a status response type with message and status fields and return the corresponding on your resolver.

Does GraphQL require a 200 response even for failed queries / mutations? No, if the query fails it will return null and the error that you throw in the server side.



回答3:

For apollo-server, install the apollo-server-errors package. For authentication errors,

import { AuthenticationError } from "apollo-server-errors";

Then, in your resolver throw new AuthenticationError('unknown user');

This will return a 400 status code.

Read more about this topic in this blog