# Node app recipes — the moves you'll make over and over

Every scaffolded app carries its own `CLAUDE.md` (the authoritative manual for
THAT app — read it first). These are the cross-app recipes, kept in the user's
plain language.

## Add a table / change the schema

Never edit an already-deployed migration. Add a new file:

```
migrations/002_add_customers.sql
```

```sql
CREATE TABLE IF NOT EXISTS customers (...);
-- or: ALTER TABLE records ADD COLUMN amount NUMERIC NOT NULL DEFAULT 0;
```

Push it — the deploy workflow runs `npm run migrate` before restarting the
app. Postgres templates apply each file in a transaction (a failure rolls
back and retries next deploy); MySQL applies statement-by-statement, so keep
MySQL migrations small.

## Add an endpoint

Copy the closest existing route in `src/index.ts` and keep its three habits:

1. **zod-validate the input** before touching the DB or doing work.
2. **Parameterized SQL only** — `$1`/`$2` (Postgres) or `?` (MySQL). Never
   template user input into a SQL string.
3. **Escape on render** — `textContent` in browser code, never `innerHTML`
   with user data.

## Connection strings / DB access

Provisioning writes the server-side `.env` per env: `DATABASE_URL` +
`DB_ENGINE`/`DB_HOST`/`DB_PORT`/`DB_NAME`/`DB_USER`/`DB_PASS`. The starters'
lazy pool means local dev works with a blank `.env` (DB routes return 503
with a clear reason). Postgres code should prefer `DATABASE_URL`.

## Env vars & secrets

- Runtime secret for the app → `awesomate_app_set_env` (encrypted in the hub,
  injected into the server `.env`, pm2 restarted). Per-env; `dev` default.
- Never commit `.env`, never echo a stored value, and per the
  awesomate-credentials skill: a key pasted in chat gets stored AND flagged
  for rotation.

## Attach n8n as the backend

Job needs email / a lookup in their connected apps / AI → build the webhook
workflow with the awesomate-n8n skill (through the user's own n8n MCP
server), then `awesomate_n8n_attach_to_app` with its production webhook URL
(stores `N8N_WEBHOOK_URL` + secret; returns the secret ONCE: add the
matching `X-Awesomate-Webhook-Secret` header check to the workflow). The app
calls `callWorkflow(payload)` from `src/lib/n8n.ts`; it no-ops when unset and
never throws — check `result.ok` and degrade gracefully.

## Deploy, promote, roll back

- **Deploy** = `git push origin dev` (that's the whole mechanism). Watch with
  `gh run watch`; verify with `awesomate_app_health`.
- **Promote** = merge forward: `dev → staging → main`. Prod is `main`; go
  through a PR from staging.
- **Roll back** = `git revert <bad-sha>` on the affected branch + push — the
  workflow redeploys the previous good state. Re-check health after.
- `awesomate_app_deploy_info` reports the branch→env map, last-deploy
  SHA/time, and last health per env. It reports how deploys work — it never
  deploys; `git push` is the deploy.

## Health semantics (don't mix these up)

- `/api/health` — lenient, always 200, DB state in the body. For humans.
- `/api/ready` — strict; 503 until the DB is reachable AND migrations ran
  (DB templates). CI's post-deploy gate and what `awesomate_app_health`
  probes. An app that was created but never pushed reports unreachable —
  that's expected, not broken.

## When something's off

1. `gh run view --log-failed` — the deploy step that broke.
2. `awesomate_app_health` — live per-env probe with a plain reason.
3. The app's own `CLAUDE.md` troubleshooting section — engine-specific
   diagnostics live there.
