Reputation: 9283
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?
Upvotes: 3
Views: 12018
Reputation: 1
The setHttpPlugin you've defined is a custom Apollo Server plugin. Apollo Server provides a plugin system that allows you to extend its functionality by defining custom plugins.
In your setHttpPlugin, you define a plugin that modifies the HTTP response status code based on the presence of errors in the response.
async function StartServer() {
const setHttpPlugin = {
async requestDidStart() {
return {
async willSendResponse({ response }) {
response.http.status = response.errors[0].status || 500
},
}
},
}
const app = express()
const PORT = process.env.PORT || 3002
const Server = new ApolloServer({
typeDefs,
resolvers,
status400ForVariableCoercionErrors: true,
csrfPrevention: false,
context: async ({ req }) => {
return req
},
formatError: (err) => {
console.log(err)
return {
message: err.message || 'Interal server error plase try again',
status: err.extensions.http.status || 500,
code: err.extensions.code || 'INTERNAL_SERVER_ERROR',
}
},
plugins: [ApolloServerPluginLandingPageGraphQLPlayground, setHttpPlugin],
})}
export const createPostResolver = async (_, { postInfo }, context) => {
const { id, error } = await ProtectRoutes(context)
if (error) {
throw new GraphQLError('Session has expired', {
extensions: {
code: 'BAD_REQUEST',
http: {
status: 401,
},
},
})
}}
Upvotes: 0
Reputation: 1159
apollo-server-express V3 supports this. Create your own plugin. Then you can look in the errors that are thrown to determine the status code.
import {ApolloServerPlugin} from "apollo-server-plugin-base/src/index";
const statusCodePlugin:ApolloServerPlugin = {
async requestDidStart(requestContext) {
return {
async willSendResponse(requestContext) {
const errors = (requestContext?.response?.errors || []) as any[];
for(let error of errors){
if(error?.code === 'unauthorized'){
requestContext.response.http.status = 401;
}
if(error?.code === 'access'){
requestContext.response.http.status = 403;
}
}
}
}
},
};
export default statusCodePlugin;
Upvotes: 0
Reputation: 890
Based on Daniels answer i have managed to write middleware.
import { HttpQueryError, runHttpQuery } from 'apollo-server-core';
import { ApolloServer } from 'apollo-server-express';
// Source taken from: https://github.com/apollographql/apollo-server/blob/928f70906cb881e85caa2ae0e56d3dac61b20df0/packages/apollo-server-express/src/ApolloServer.ts
// Duplicated apollo-express middleware
export const badRequestToOKMiddleware = (apolloServer: ApolloServer) => {
return async (req, res, next) => {
runHttpQuery([req, res], {
method: req.method,
options: await apolloServer.createGraphQLServerOptions(req, res),
query: req.method === 'POST' ? req.body : req.query,
request: req,
}).then(
({ graphqlResponse, responseInit }) => {
if (responseInit.headers) {
for (const [name, value] of Object.entries(responseInit.headers)) {
res.setHeader(name, value);
}
}
res.statusCode = (responseInit as any).status || 200;
// Using `.send` is a best practice for Express, but we also just use
// `.end` for compatibility with `connect`.
if (typeof res.send === 'function') {
res.send(graphqlResponse);
} else {
res.end(graphqlResponse);
}
},
(error: HttpQueryError) => {
if ('HttpQueryError' !== error.name) {
return next(error);
}
if (error.headers) {
for (const [name, value] of Object.entries(error.headers)) {
res.setHeader(name, value);
}
}
res.statusCode = error.message.indexOf('UNAUTHENTICATED') !== -1 ? 200 : error.statusCode;
if (typeof res.send === 'function') {
// Using `.send` is a best practice for Express, but we also just use
// `.end` for compatibility with `connect`.
res.send(error.message);
} else {
res.end(error.message);
}
},
);
};
}
app.use(apolloServer.graphqlPath, badRequestToOKMiddleware(apolloServer));
Upvotes: 0
Reputation: 1
Try adding response and setting the response status code as so, assuming your err.status is already an integer like 401 etc.:
const graphqlKoaMiddleware = graphqlKoa(ctx => {
return ({
schema,
response: request.resonse,
formatError: (err) => {
response.statusCode = err.status;
return ({message: err.message, status: err.status})},
context: {
stationConnector: new StationConnector(),
passengerTypeConnector: new PassengerTypeConnector(),
authConnector: new AuthConnector(),
cookies: ctx.cookies
}
})})
Upvotes: 0
Reputation: 1505
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
Upvotes: 3
Reputation: 84867
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)
})
}
}
Upvotes: 8
Reputation: 2280
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.
Upvotes: 1