Every monitoring tutorial starts the same way: "Create an endpoint that returns 200 OK." And every production outage eventually teaches you why that's not enough.
The 200 OK Lie
Your API server is running. It responds to requests. It returns HTTP 200. And yet, your users are seeing errors. What gives?
HTTP status codes describe the HTTP transaction, not your application's health. A 200 means "I received your request and sent a response." It says nothing about whether that response is correct, useful, or complete.
The Problem
Real Examples of Healthy 200s That Broke Production
- Database connection pool exhausted: The API returns
{ "users": [] }instead of actual user data. HTTP 200. Application broken. - Cache server unreachable: Responses take 8 seconds instead of 80ms. HTTP 200. Users leaving.
- Downstream service returning errors: Your API wraps the error in a 200 response with
{ "error": "service unavailable" }. HTTP 200. Features broken. - Misconfigured feature flag: Health endpoint works, but the main product returns the wrong version. HTTP 200. Users confused.
What to Check Instead
A proper health check validates that your application can actually serve its purpose. Here's what that looks like in practice.
1. Response Body Validation
Don't just check that you got a response. Check that the response contains expected data:
// Bad: Only checks status
expect(response.status).toBe(200);
// Good: Validates response structure
expect(response.status).toBe(200);
expect(response.body.data).toBeDefined();
expect(response.body.data.length).toBeGreaterThan(0);2. Response Time Thresholds
A slow response is often a precursor to no response. Set thresholds:
{
"assertions": [
{ "type": "status", "value": 200 },
{ "type": "responseTime", "operator": "lt", "value": 2000 }
]
}3. Deep Dependency Checks
Your health endpoint should verify critical dependencies:
export async function healthCheck() {
const checks = await Promise.all([
checkDatabase(),
checkCache(),
checkDownstreamAPI()
]);
const unhealthy = checks.filter(c => !c.healthy);
return {
status: unhealthy.length === 0 ? 'healthy' : 'degraded',
checks: checks,
timestamp: new Date().toISOString()
};
}Pro Tip
4. Functional Validation
The best health check exercises real functionality. Instead of a synthetic /health endpoint, monitor an actual API endpoint that represents core functionality:
- E-commerce API: Monitor the product listing endpoint
- SaaS app: Monitor the user authentication flow
- Data API: Monitor a representative query
Setting This Up in upti.my
You can configure all of this in the dashboard. Create a healthcheck and add assertions that go beyond status codes:
- Create an application for your service
- Add a healthcheck with your API endpoint
- Add assertions for status, response time, and JSON body validation
- Set up alerting to Slack, Discord, or PagerDuty
You can validate that specific JSON paths exist, match patterns, or contain expected values. If the response structure changes or data is missing, you get alerted.
📌Key Takeaways
- 1HTTP 200 means the server responded, not that the response is correct
- 2Always validate response bodies, not just status codes
- 3Set response time thresholds to catch degradation early
- 4Health endpoints should verify dependencies, not just return static responses
- 5The best health check exercises real application functionality
The goal of monitoring isn't to prove your server is running. It's to prove your service is delivering value to users. Design your health checks accordingly.