feat(template): dependency-checking /health endpoint (#54)#92
Merged
PAMulligan merged 7 commits intoMay 14, 2026
Merged
Conversation
Captures the design decisions (isolate uptime on Workers, APP_VERSION env var, extracted routes/health.ts, 2s DB ping timeout, 503 on any dependency failure) and breaks implementation into seven task-scoped commits across both setup-project.sh templates and the todo-api-cloudflare example. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds src/db/client.ts with a postgres-js singleton and shared drizzle instance reading DATABASE_URL at module load. seed.ts now imports from client.ts instead of constructing its own connection. The client module is generated for both templates because seed.ts runs under tsx (Node) regardless of deploy target; the Workers runtime never imports it. First step toward the dependency-checking /health endpoint (#54). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Generates src/db/ping.ts with a platform-specific body. The Workers variant builds a per-request postgres client over the HYPERDRIVE binding's connection string and runs SELECT 1; the Node variant uses the shared client singleton from db/client.ts. Both return 'connected' | 'disconnected' and never throw — the caller is the /health endpoint, which must never 5xx the prober. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the inline app.get('/health', ...) in both Workers and Node
templates with a mounted routes/health.ts handler. The new handler:
- Pings the database via pingDatabase() with a configurable timeout
(HEALTH_DB_TIMEOUT_MS, default 2000 ms)
- Reports overall status as 'healthy' / 'unhealthy' and returns 503
when any dependency is disconnected
- Includes version (from APP_VERSION env), uptime (seconds since module
load — isolate uptime on Workers, process uptime on Node), timestamp,
requestId, and a checks.database field
- Never throws to the caller: any internal error is caught and reported
as 'disconnected' + 503, so load balancers never see a 5xx that wasn't
caused by an actual dependency outage
Workers Bindings now declare APP_VERSION and HEALTH_DB_TIMEOUT_MS so the
template type-checks against wrangler.toml [vars] entries (added next).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…lates The new /health endpoint reads these from c.env (Workers) or process.env (Node). Adds them to: - .dev.vars.example (Workers local dev) - .env.example (Node local dev) - wrangler.toml [vars], [env.staging.vars], [env.production.vars] APP_VERSION mirrors package.json so generated projects ship with a real value out of the box; HEALTH_DB_TIMEOUT_MS defaults to 2000 ms so the DB ping fails fast under network partition. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… error paths Replaces the standalone Hono fixture in tests/unit/health.test.ts with a test that mounts the real healthRoutes inside a Hono parent + requestId middleware (mirroring index.ts wiring). pingDatabase is mocked via vi.mock so no real postgres is needed in CI. Coverage (both Workers and Node test bodies): - 200 + healthy when DB connected - version sourced from APP_VERSION (c.env on Workers, process.env on Node) - numeric uptime >= 0 - 503 + unhealthy when DB disconnected - 503 on timeout via fake-timer-advanced Promise.race - 503 on thrown error (never 500) - requestId preserved in body and X-Request-Id header Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirrors the new health endpoint design in the example:
- examples/todo-api-cloudflare/api/src/db/ping.ts (D1: SELECT 1 via
c.env.DB.prepare('SELECT 1').first())
- examples/todo-api-cloudflare/api/src/routes/health.ts (same handler
shape as the Workers template, but typed for the example's D1 binding)
- src/index.ts: drop inline /health handler, mount healthRoutes
- wrangler.toml [vars] and .dev.vars.example: add APP_VERSION and
HEALTH_DB_TIMEOUT_MS
- tests/unit/health.test.ts: full mocked coverage matching the template
Also refactors pingDatabase in both the Workers template and the example
to take the binding directly (Hyperdrive | undefined / D1Database |
undefined) instead of the whole Hono Context — the Context<Bindings>
variance caused a typecheck error when the route's wider Bindings
included APP_VERSION/HEALTH_DB_TIMEOUT_MS. Decoupling db/ping.ts from
Hono's type also makes it easier to reuse outside HTTP handlers.
Example tests: 19/19 passing (7 health + 12 todos integration).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Replaces the static
{ status: 'ok' }health endpoint with a real liveness check that pings the database, reports version + uptime, and returns503when a dependency is down. Closes #54.Response shape (HTTP 200 healthy, 503 unhealthy):
{ "status": "healthy", "version": "0.0.1", "uptime": 3600, "timestamp": "2026-04-08T12:00:00Z", "requestId": "01HXYZ...", "checks": { "database": "connected" } }Changes:
setup-project.shWorkers + Node templates: extractsrc/routes/health.tsandsrc/db/ping.ts; mount viaapp.route('/health', healthRoutes); addAPP_VERSION+HEALTH_DB_TIMEOUT_MSto env templates andwrangler.toml [vars]src/db/client.ts(postgres singleton) soseed.tsand future code stop building their own connectionsexamples/todo-api-cloudflaremirrors the design with a D1-flavoredpingDatabasepingDatabase, no real Postgres needed in CI)Design decisions (full rationale in
docs/plans/2026-05-14-health-endpoint-dependency-checks.md):Date.now() - startTimeat module load (isolate uptime — honest about Workers semantics, zero cost)APP_VERSIONenv var (one pattern across Workers + Node; deploys override per env)livenessProbedefault; configurable viaHEALTH_DB_TIMEOUT_MS)503+database: 'disconnected', so load balancers don't treat them as "needs restart"pingDatabasetakes the binding directly (Hyperdrive | undefined,D1Database | undefined) instead of aContext— decouplesdb/ping.tsfrom Hono and avoidsContext<Bindings>variance issuesTest Plan
examples/todo-api-cloudflare: 19/19 tests pass, typecheck cleanbash scripts/setup-project.sh /tmp/x --node): 7/7 health tests passbash scripts/setup-project.sh /tmp/x --cloudflare): 7/7 health tests passbash -n scripts/setup-project.shclean after every editOut of scope (follow-up)
templates/shared/tsconfig.jsonis missing"types": ["node"], and the Cloudflare branch ofsetup-project.shdoesn't install@cloudflare/workers-types. The existing pre-changeindex.tsalready usedprocess/console/setTimeoutandD1Database/Hyperdrivewithout these types. Generated tests run fine (vitest doesn't typecheck by default); fixing the strict-typecheck path on generated projects is its own change./health/livevs/health/readysplit — single endpoint matches the issue spec.APP_VERSIONfrompackage.jsonat build time — env var pattern is the contract; build-time substitution is a follow-up.🤖 Generated with Claude Code