Uptime Monitoring for GraphQL APIs (Free, Multi-Region)
Here's the problem with monitoring GraphQL APIs: they always return 200 OK.
Send a malformed query? 200 OK with an errors array. Your resolver crashed? 200 OK with a partial data response and errors. The introspection schema changed and your client is sending invalid field names? Still 200 OK.
Standard HTTP uptime monitors that check for status 200 will tell you your GraphQL API is healthy when it's returning nothing but errors. This guide covers how to write real GraphQL health checks and set up monitoring that actually detects failures.
Why standard HTTP monitoring misses GraphQL failures
REST APIs communicate errors through HTTP status codes: 400, 404, 500. Monitoring tools look for these.
GraphQL has a different contract: the HTTP layer always returns 200. Errors live in the response body:
{
"data": null,
"errors": [
{
"message": "Cannot query field 'userId' on type 'User'",
"locations": [{ "line": 2, "column": 3 }],
"path": ["user"]
}
]
}
A monitor checking status == 200 thinks this is healthy. Your users are seeing nothing but error messages.
Step 1: Add a dedicated health query to your schema
Add a _healthCheck query that your monitoring uses:
# schema.graphql
type HealthStatus {
status: String!
version: String
latencyMs: Int
database: DependencyStatus
}
type DependencyStatus {
status: String!
latencyMs: Int
error: String
}
type Query {
# ... your actual queries
_healthCheck: HealthStatus!
}
Implement the resolver:
// resolvers/health.ts
import { db } from '../db'
export const healthCheckResolver = async () => {
const start = Date.now()
let dbStatus = { status: 'ok', latencyMs: 0 }
try {
const dbStart = Date.now()
await db.raw('SELECT 1')
dbStatus = { status: 'ok', latencyMs: Date.now() - dbStart }
} catch (err: any) {
dbStatus = { status: 'error', error: err.message }
}
return {
status: dbStatus.status === 'ok' ? 'ok' : 'degraded',
version: process.env.APP_VERSION ?? 'unknown',
latencyMs: Date.now() - start,
database: dbStatus,
}
}
Step 2: Add a REST health endpoint alongside GraphQL
GraphQL is great, but your uptime monitor needs an endpoint that communicates health via HTTP status codes. Add a /health REST route even if the rest of your API is GraphQL:
// In your Express/Fastify/etc. setup
app.get('/health', async (req, res) => {
// Run the same health check logic
const result = await healthCheckResolver()
const isOk = result.status === 'ok'
res.status(isOk ? 200 : 503).json({
status: result.status,
checks: { database: result.database },
})
})
This is the endpoint your uptime monitor hits. The _healthCheck query is for internal tooling and dashboards.
Step 3: Monitor for errors in GraphQL responses
If you can't add a REST endpoint, you can configure your monitor to send a GraphQL request and check the response body. Here's how to do it with a custom HTTP monitor that sends a POST:
Configure the monitor to POST:
POST https://api.example.com/graphql
Content-Type: application/json
{"query":"{ _healthCheck { status } }"}
Expected response body should contain "status":"ok" and NOT contain "errors".
Many monitoring tools support body assertions. With Vigilmon, you can:
- Set the request method to
POST - Set the request body to the introspection/health query
- Add a body contains assertion:
"status":"ok" - Add a body not contains assertion:
"errors"
Step 4: Monitor schema integrity
Schema breaking changes are a silent killer. If you deploy a schema change that removes a field a client was querying, those clients start getting errors. Detect this early with a dedicated operation:
// scripts/schema-smoke-test.ts
import fetch from 'node-fetch'
async function smokeTestSchema() {
const criticalOperations = [
{ name: 'UserQuery', query: '{ user(id: "test") { id email createdAt } }' },
{ name: 'ProductList', query: '{ products(first: 1) { edges { node { id title } } } }' },
{ name: 'HealthCheck', query: '{ _healthCheck { status } }' },
]
const results = []
for (const op of criticalOperations) {
const res = await fetch(process.env.GRAPHQL_URL!, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query: op.query }),
})
const body = await res.json()
results.push({
name: op.name,
ok: !body.errors,
errors: body.errors?.map((e: any) => e.message),
})
}
const failures = results.filter(r => !r.ok)
if (failures.length > 0) {
console.error('Schema smoke test FAILED:', failures)
process.exit(1)
} else {
console.log('Schema smoke test passed:', results.map(r => r.name).join(', '))
}
}
smokeTestSchema()
Run this in your CI/CD pipeline before deploying.
Step 5: Add subscription health (if using GraphQL subscriptions)
GraphQL subscriptions use WebSockets. Add a separate health check for the WebSocket endpoint:
// ws-health.ts
import WebSocket from 'ws'
export function checkSubscriptionEndpoint(url: string): Promise<{ status: string; latencyMs?: number; error?: string }> {
return new Promise((resolve) => {
const start = Date.now()
const timeout = setTimeout(() => {
ws.terminate()
resolve({ status: 'error', error: 'WebSocket connection timeout' })
}, 5000)
const ws = new WebSocket(url, 'graphql-ws')
ws.on('open', () => {
clearTimeout(timeout)
ws.close()
resolve({ status: 'ok', latencyMs: Date.now() - start })
})
ws.on('error', (err) => {
clearTimeout(timeout)
resolve({ status: 'error', error: err.message })
})
})
}
Step 6: Set up external monitoring
- Go to vigilmon.online — free tier.
- Create an HTTP(S) monitor for
/health(REST endpoint). - Or create a POST monitor targeting your GraphQL endpoint with body assertions.
- Interval: 60s
- Regions: 2+
- Alert on 503 status OR on body containing
"errors".
Recap
- GraphQL always returns
200— don't monitor the status code alone. - Add a
_healthCheckquery to your schema for internal tooling. - Add a
/healthREST endpoint that returns200/503based on dependency health — this is what your uptime monitor hits. - Use body assertions if you must monitor the GraphQL endpoint directly.
- Run schema smoke tests in CI to catch breaking changes before deploy.
- vigilmon.online supports POST monitors with body assertions for GraphQL-first APIs.
GraphQL is powerful and flexible. That flexibility requires you to be explicit about what "healthy" means. Now you are.
Top comments (0)