# Add Health Checks to an Express API

Yes. An agent adds liveness and readiness routes to your Express API, checks Prisma in the ready route, tests both with Jest and opens a pull request.

[Run this on Rush](https://cloud.getrush.ai/run/add-health-checks/express/), in a Project that has your repository.

## What the agent does

The agent clones the repository into its own cloud sandbox and reads how the API is put together before adding a route. In an Express API like gothinkster/node-express-realworld-example-app the project is an Nx 17.2.6 workspace: `project.json` has `build`, `serve`, `lint`, `test` and `docker-build` targets, `express` is pinned at `~4.18.1`, `@prisma/client` at `^4.16.1` with a `postgresql` datasource reading `DATABASE_URL` in `src/prisma/schema.prisma`, and tests run on Jest 29 with `ts-jest`. `src/main.ts` configures `cors`, `body-parser`, mounts `routes` under `/api`, serves `src/assets`, answers `GET /` with `{ status: 'API is running on /api' }`, installs an error handler and listens on `PORT` or 3000. `src/app/routes/routes.ts` composes the tag, article, profile and auth controllers. A search for `health`, `ping` or `readiness` finds nothing.

It reads two things that shape the result. `src/prisma/prisma-client.ts` exports one `PrismaClient` and caches it on `global` in development, so a readiness check can import it rather than open a second connection. And `src/tests/prisma-mock.ts` uses `jest-mock-extended` to replace that module with `mockDeep<PrismaClient>()` and reset it before each test, which is how `auth.service.test.ts` and the other service tests run without a database. The new tests follow that pattern instead of needing Postgres.

The agent adds `src/app/routes/health/health.controller.ts` with two routes. `GET /health/live` answers 200 with `{ status: 'ok' }` and touches nothing, so a process that is up but has lost its database still passes liveness and is not restarted into a crash loop. `GET /health/ready` runs `prisma.$queryRaw` with `SELECT 1` under a short timeout and answers 200 with `{ status: 'ok', checks: { database: 'up' } }`, or 503 with `database: 'down'` and the error message left out of the body. Both are mounted in `src/main.ts` before `routes`, outside the `/api` prefix and outside `express-jwt`, so a load balancer needs no token. The existing `GET /` stays as it is. `src/tests/routes/health.controller.test.ts` uses `prismaMock.$queryRaw.mockResolvedValue` for the 200 and `mockRejectedValue` for the 503, calling the handlers through `supertest` against a small app that mounts only the health router.

Because the `Dockerfile` is Nx-generated from `node:lts-alpine` with `CMD ["node", "api"]` and no `HEALTHCHECK`, it adds one that uses BusyBox `wget`, which the Alpine image has, against `/health/live` on the `PORT` the image sets. One thing it finds along the way goes in the pull request rather than the diff: `e2e/src/server/server.spec.ts`, the Nx scaffold's end-to-end test, expects `GET /` to return `{ message: 'Hello API' }`, which `src/main.ts` has never sent. The agent leaves that file alone and notes it. It runs `npx nx lint api`, `npx nx test api` and `npx nx build api`, then opens the pull request.

## Start it with one API call

`POST https://api.prix.dev/o/{org}/sessions`

```json
{
  "agent": "claude",
  "input": "Add health checks to this Express API. Read src/main.ts, src/app/routes/routes.ts, src/prisma/prisma-client.ts, src/tests/prisma-mock.ts and the Dockerfile first. Add GET /health/live, which answers 200 with no dependencies, and GET /health/ready, which runs SELECT 1 through the existing Prisma client with a timeout and answers 200 or 503 with a checks object; mount both in src/main.ts before the /api routes and outside the JWT middleware, and keep the existing GET /. Write Jest tests that use the existing prisma-mock pattern for both outcomes. Add a HEALTHCHECK to the Dockerfile using wget against /health/live. Change nothing under e2e/, but report anything there that disagrees with the app. Run npx nx lint api, npx nx test api and npx nx build api, and open a pull request.",
  "resources": [
    {
      "url": "https://github.com/your-org/your-app"
    }
  ]
}
```

1. **Get an API key.** In the console, open Settings, then API keys, and create a key for your org. Send it in the Authorization header as a Bearer token.
2. **Send the request.** POST the body in request.json to https://api.prix.dev/o/{org}/sessions, with your repository as the resources URL. The run starts in its own cloud sandbox.
3. **Read what came back.** Follow the run on its live stream or wait for the run.completed webhook, then read the result and its trajectory.

## What comes back

What comes back is a pull request on its own branch that adds `src/app/routes/health/health.controller.ts` and `src/tests/routes/health.controller.test.ts`, adds two lines to `src/main.ts` to mount the router, adds a `HEALTHCHECK` line to the `Dockerfile`, and adds `supertest` to the dev dependencies in `package.json` and `package-lock.json`. No existing route, service or Prisma file changes. The description shows the JSON each endpoint returns in both states, quotes the Jest summary from `npx nx test api`, and has a short note about the stale expectation in `e2e/src/server/server.spec.ts`.

A reviewer checks that `/health/live` imports nothing from Prisma, so a database outage cannot fail liveness; that the ready check's timeout is shorter than the probe interval a Kubernetes or compose health check would use; and that the 503 body names the failing check without echoing the database error, which can carry a connection string. The session's transcript keeps the lint, test and build output in order.

If the repository's error handler in `src/main.ts` would turn a rejected ready check into a 500 before the route answers, the agent handles the rejection inside the route and says so, rather than relying on the handler's shape.

## Run it on every new service without a health route

When a new API repository is created, a handler on your server receives GitHub's repository event and sends `POST /o/{org}/sessions` with this prompt and the repository as the `resources` URL. A script can do the same across existing services by listing them through `GET /o/{org}/github` and checking each for a health route. Each run works in its own sandbox and opens its own pull request.

Set a `webhook_url` on the API key and Rush POSTs `run.completed` when the turn finishes, with the `session_url` to read the pull request from.

## Why run it on Rush?

Health endpoints are small and the decisions around them are not: what liveness may depend on, how long readiness may wait, where the route sits relative to auth, what the container image can run as a probe. The agent reads those answers out of the repository, writes the routes and the tests in its own sandbox with the Nx targets the project already has, and your checkout is untouched.

The pull request arrives with the test run, the lint and the build in the transcript, and a note about the stale end-to-end test the agent found but did not change. A reviewer reads a focused diff with evidence attached, and several services can get the same treatment at once.

## Frequently asked questions

### Why two endpoints instead of one /health?

Because they answer different questions. Liveness asks whether the process should be restarted, and a lost database is not a reason to restart Node; readiness asks whether traffic should be routed here, and a lost database is exactly that. Kubernetes, ECS and most load balancers take the two separately, and a single endpoint forces one answer for both.

### Does the ready check open a new database connection?

No. It imports the `PrismaClient` from `src/prisma/prisma-client.ts`, the same instance every service uses, and runs `SELECT 1` through `$queryRaw`. In tests that module is already replaced by `jest-mock-extended`, so the new tests run without Postgres like the rest of the suite.

### Are the endpoints behind the JWT middleware?

No. They are mounted in `src/main.ts` before the `/api` router, outside the `express-jwt` middleware the auth controller applies, so a probe sends no token. They expose a status word and a check name, nothing about users or data.

### Will the Docker HEALTHCHECK work on the Alpine image?

Yes. The image is `node:lts-alpine`, which has no `curl` but ships BusyBox `wget`, and the instruction uses `wget -qO-` against `/health/live` on the port the image sets with `ENV PORT=3000`. The agent builds the image with `npx nx docker-build api` and checks `docker inspect` reports the container healthy before it opens the pull request.
