# Local developer testing
Run the Worker on your machine, publish with an API key, and list the inbox with a user Cognito token. No Supabase and no Cognito M2M required for the happy path.
Order: boot (§1) → API-key publish + user list (§2) → optional Cognito M2M (§3).
When you’re ready to deploy to Cloudflare: Setup guide.
Prerequisites: Node ≥ 20, pnpm ≥ 9, and a Cognito user pool you can sign into (for inbox reads). Publisher API keys are configured only on the Worker.
# 1. Boot the Worker
git clone <this repo> && cd org.inovus.notifications
pnpm install
pnpm test # everything should be green
cd packages/api
cp .dev.vars.example .dev.vars
pnpm dev # wrangler dev on http://localhost:8787
Open http://localhost:8787/docs — interactive Swagger UI (Try it out uses this host). Confirm:
curl.exe http://localhost:8787/health
# → {"status":"ok"}
Default store is memory (wiped on restart). You do not need Supabase. Comparison: Storage backends.
Useful loops:
pnpm test
pnpm --filter @inovus-medical/notifications-api exec vitest run test/routes.test.ts
pnpm test:coverage
pnpm lint && pnpm typecheck
pnpm openapi:generate # after changing routes/schemas
# 2. Send and receive with API keys (start here)
Publish without Cognito resource servers or M2M app clients. You still need AUTH_ISSUER so a signed-in user can read the inbox (API keys are publish-only).
More detail: api-keys.md.
# 2.1 Configure .dev.vars
Edit packages/api/.dev.vars (never commit this file):
AUTH_ISSUER=https://cognito-idp.<region>.amazonaws.com/<userPoolId>
DB_DRIVER=memory
PUBLISHER_API_KEYS=local:<long-random-secret>
# Leave PUBLISHER_SCOPES unset — not required for API-key publish
Generate a long random secret (password manager or openssl rand -hex 32). Restart pnpm dev so Wrangler reloads the vars.
| Var | Why |
|---|---|
AUTH_ISSUER |
User access tokens from your pool verify (needed for list) |
PUBLISHER_API_KEYS |
Backends publish with X-Api-Key |
DB_DRIVER=memory |
No Supabase; data vanishes on Worker restart |
# 2.2 Get a user access token (for receiving only)
Sign in as a normal user on that pool. Copy the access token and decode it at jwt.io:
$ApiKey = "<long-random-secret>" # secret half of PUBLISHER_API_KEYS (after the colon)
$UserToken = "eyJraWQiOiJ...." # paste user access token
$UserSub = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" # JWT "sub" claim
$UserSub is who you publish to — it must match that user’s sub or their inbox stays empty.
# 2.3 Publish with X-Api-Key
curl.exe -s -X POST http://localhost:8787/v2/notifications `
-H "X-Api-Key: $ApiKey" `
-H "Content-Type: application/json" `
-d "{`"userGuid`":`"$UserSub`",`"title`":`"Hello`",`"body`":`"Local API key test`",`"severity`":`"success`",`"dedupeKey`":`"local-setup-1`"}"
Expect 201 and { "notification": { …, "source": "local", … }, "duplicate": false }.
Same command again → 200 with "duplicate": true.
| Status | Meaning |
|---|---|
401 |
Wrong API key, or .dev.vars not reloaded (restart pnpm dev) |
400 |
Sent both X-Api-Key and Authorization: Bearer |
Do not use the API key on GET /v2/notifications — 403 (publish-only).
# 2.4 List the inbox with the user token
curl.exe -s http://localhost:8787/v2/notifications `
-H "Authorization: Bearer $UserToken"
Expect your notification in items and unreadCount ≥ 1. Optional:
curl.exe -s http://localhost:8787/v2/notifications/unread-count `
-H "Authorization: Bearer $UserToken"
curl.exe -s -X POST "http://localhost:8787/v2/notifications/$NotificationId/read" `
-H "Authorization: Bearer $UserToken"
# 2.5 Same flow in Swagger
- Open http://localhost:8787/docs
- Publish: set
X-Api-Key(API key auth) - List: authorize with
Bearer <user token> POST /v2/notificationsthenGET /v2/notifications
# 3. Optional — publish with Cognito M2M tokens
Prefer short-lived Cognito tokens instead of (or as well as) Worker API keys. Why/how: cognito-explained.md, cognito-setup.md, Cognito M2M reference.
# 3.1 Extra .dev.vars
Keep AUTH_ISSUER and DB_DRIVER=memory. Add:
PUBLISHER_SCOPES=notifications/publish
# You can keep PUBLISHER_API_KEYS — both paths work together
Create a Cognito resource server (notifications / publish) and a confidential app client with client credentials + that scope (cognito-setup.md). Restart pnpm dev.
# 3.2 Fetch an M2M access token
| Variable | Cognito console |
|---|---|
$ClientId |
App integration → App clients → confidential client → Client ID |
$ClientSecret |
Same page → Client secret |
| Cognito domain | App integration → Domain |
$ClientId = "paste-from-app-client-detail"
$ClientSecret = "paste-from-app-client-secret"
$CognitoDomain = "https://<prefix>.auth.<region>.amazoncognito.com"
curl.exe -X POST "$CognitoDomain/oauth2/token" `
-H "Content-Type: application/x-www-form-urlencoded" `
-u "${ClientId}:${ClientSecret}" `
-d "grant_type=client_credentials&scope=notifications/publish"
$M2MToken = "eyJraWQiOiJ...." # paste access_token
Reuse $UserSub / $UserToken from §2.
# 3.3 Publish with Bearer
curl.exe -s -X POST http://localhost:8787/v2/notifications `
-H "Authorization: Bearer $M2MToken" `
-H "Content-Type: application/json" `
-d "{`"userGuid`":`"$UserSub`",`"title`":`"Hello`",`"body`":`"Local M2M test`",`"severity`":`"success`",`"dedupeKey`":`"local-m2m-1`"}"
| Status | Meaning |
|---|---|
401 |
Wrong/expired token, or AUTH_ISSUER ≠ token iss |
403 |
Client missing notifications/publish, or PUBLISHER_SCOPES mismatch |
List with $UserToken as in §2.4.
# 4. Local troubleshooting
| Symptom | Likely cause |
|---|---|
Publish 401 with X-Api-Key |
Secret not in PUBLISHER_API_KEYS, or Worker not restarted |
| Publish 400 | Both X-Api-Key and Bearer sent |
| Publish 403 on list/inbox | API keys are publish-only — use a user token |
| Everything 401 on Bearer | AUTH_ISSUER ≠ token iss |
| Publish works but list empty | userGuid ≠ user’s sub |
| Data disappeared | Memory store — restart wiped it |
| Swagger hits production | Use / server (“This deployment”); refresh after pulling latest |
Optional: point local at a real Supabase project with DB_DRIVER=supabase + URL + service role in .dev.vars (never commit secrets).
# Next: deploy to Cloudflare
Setup guide — Supabase, Cognito (production), Worker deploy, GitHub Actions.