graphql-yoga
graphql-yoga copied to clipboard
Does not support healthcheck endpoints such as `/graphql/health`
In RedwoodJS, when using helix, we had a healthcheck at the endpoint /graphql/health
because graphql
was the serverless function for the graphQL server.
const HEALTH_CHECK_PATH = '/health'
export type OnHealthcheckFn = (event: APIGatewayProxyEvent) => Promise<any>
export function createHealthcheckContext(
onHealthcheckFn?: OnHealthcheckFn,
corsContext?: CorsContext
) {
return {
isHealthcheckRequest(requestPath: string) {
return requestPath.endsWith(HEALTH_CHECK_PATH) / / 👈
},
Bit, with Yoga, please consider this code in https://github.com/dotansimha/graphql-yoga/blob/55c01aa9e122099ae87848508bed806337e8a2f5/packages/common/src/server.ts#L312
const urlObj = new URL(request.url)
if (urlObj.pathname === '/health') { / / 👈
return new Response(`{ "message": "alive" }`, {
status: 200,
headers: {
'Content-Type': 'application/json',
'x-yoga-id': this.id,
},
})
}
if (urlObj.pathname === '/readiness') { / / 👈
urlObj.pathname = '/health' / / 👈
const readinessResponse = await fetch(urlObj.toString())
if (
readinessResponse.status === 200 &&
readinessResponse.headers.get('x-yoga-id') === this.id
) {
return new Response(`{ "message": "ready" }`, {
status: 200,
headers: {
'Content-Type': 'application/json',
},
})
}
throw new Error(
`Readiness check failed with status ${readinessResponse.status}`,
)
}
Note that with helix, we checked endsWith
vs Yoga checks ===
the path.
- Could Yoga also support endsWith so a serverless function can be used?
- Are custom checks supported? If not, redwood has to update its docs. See: https://redwoodjs.com/docs/graphql#health-checks
- How does
readiness
different fromhealth
and when should each be used?
Note: Redwood would very much like a custom healthcheck function so we can test deploy CI and make sure a query can fetch data from the associated database. We'd add some query string to /graphql/health?some=thing
to run test(s) and expect a result that we can confirm easily using Pingdom to check that it's up.
Kingdom cannot issue POSTs apparently to test -- and for some reason Netlify may be encoding or double encoding
https://rwjs-deploy-target-ci.netlify.app/.netlify/functions/graphql?query=%7Bredwood%7Bversion%7D%7D
As the response is
{"errors":[{"message":"Must provide query string."}]}
cc @thedavidprice
Previously (and on "most" hosting providers) I was able to do something like this: https://deploy-target-ci.vercel.app/api/graphql?query={post(id:1){title}}
Which made for an easy health check:
- graphql api ✅
- DB queries ✅
GET requests are handled by Yoga;
https://github.com/dotansimha/graphql-yoga/pull/959
I think event.path
(we used to create the Request
object) doesn't have search params.
About health check and readiness endpoints; https://github.com/dotansimha/graphql-yoga/pull/960
I think it is better them to configure them instead of endsWith
because endsWith
looks a bit unsafe.
GET requests are handled by Yoga
Ok, then there must be something going on elsewhere. I can confirm for some hosting providers GET is working:
- https://rwjs-deploy-target-ci.vercel.app/api/graphql?query={post(id:1){title}}
But not for others:
- https://rwjs-deploy-target-ci.netlify.app/.netlify/functions/graphql?query={post(id:1){title}}
I think I changed my mind and now I agree with you on using endsWith
. Now Yoga checks the path in this way. Let me know if that works for you, then we can close this issue.
@dthyresson
I think I changed my mind and now I agree with you on using
endsWith
. Now Yoga checks the path in this way. Let me know if that works for you, then we can close this issue. @dthyresson
It does - one question: will Yoga support custom health check functions in the future?
I'm working on that @dthyresson How would you imagine that?
I'm working on that @dthyresson How would you imagine that?
Ideally, it would work the same as documented here https://redwoodjs.com/docs/graphql#health-checks
This is how RedwoodJS declared one with Helix -- and keeping the same method means we wouldn't have to change docs or tell people for to do differently.
// api/src/functions/graphql.{ts,js}
const myCustomHealthCheck = async () => {
if (ok) {
// Implement your custom check, such as:
// * invoke an api
// * call a service
// * make a db request
// that ensures your GraphQL endpoint is healthy
return
}
throw Error('Health check failed')
}
export const handler = createGraphQLHandler({
onHealthCheck = await myCustomHealthCheck(),
// .. other config
getCurrentUser,
directives,
sdls,
services,
})
And then that function was used before like:
const healthcheckContext = createHealthcheckContext(
onHealthCheck,
corsContext
)
Where the createHealthcheckContext
was:
import type { APIGatewayProxyEvent } from 'aws-lambda'
import { Request } from 'graphql-helix'
import { CorsContext } from './cors'
const HEALTH_CHECK_PATH = '/health'
export type OnHealthcheckFn = (event: APIGatewayProxyEvent) => Promise<any>
export function createHealthcheckContext(
onHealthcheckFn?: OnHealthcheckFn,
corsContext?: CorsContext
) {
return {
isHealthcheckRequest(requestPath: string) {
return requestPath.endsWith(HEALTH_CHECK_PATH)
},
async handleHealthCheck(request: Request, event: APIGatewayProxyEvent) {
const corsHeaders = corsContext
? corsContext.getRequestHeaders(request)
: {}
if (onHealthcheckFn) {
try {
await onHealthcheckFn(event)
} catch (_) {
return {
body: JSON.stringify({ status: 'fail' }),
statusCode: 503,
headers: {
'Content-Type': 'application/json',
...corsHeaders,
},
}
}
}
return {
body: JSON.stringify({ status: 'pass' }),
statusCode: 200,
headers: {
'Content-Type': 'application/json',
...corsHeaders,
},
}
},
}
}
We are also using healthchecks but we are using them on separate endpoints that are not Yoga related. To add to the context we use express app and only mount yoga on relevant endpoints. I have seen the readiness checks in the source code and have thought to myself that it looks a bit odd to me, but it's not my use case, but it's hard coded strings and not very configurable.
But since it is part of Yoga's ambition to cater for healthchecks, then I would agree they should be more customizable. Perhaps
import { createServer } from '@graphql-yoga/node'
const yogaServer = createServer({
// default: `false`, no healthcheck added by default
healthcheck: {
// No default, how would you know what the user wants?
endpoint: '/readiness',
// async fn that resolves or rejects, default `async () => ({ "message": "ready" })`. If it resolves 200 OK is returned with the resolved value.
check: async () => { await checkDBConnection() },
},
})