A ZenStack-backed API on Postgres, reached through a Hyperdrive binding, with database event listeners.
DatabaseModule.forRootAsync()with a named connection@InjectDB('main')and the typedDatabaseService<'main'>client- Declaring the schema shape by augmenting
StratalDatabase - Database lifecycle events —
@On('after.Task.create')and friends - A Hyperdrive binding with a
localConnectionStringfor local development
Start Postgres, generate the ZenStack client, and push the schema:
npm install
npm run setupsetup runs db:up, generate and db:push in order. Then:
npm run devStop the database with npm run db:down.
curl -X POST http://localhost:8787/api/v1/tasks \
-H 'Content-Type: application/json' \
-d '{"title":"Write migration","description":"Add the tasks table"}'{"data":{"id":"cmuab8owc000000iimrbb7o2u","title":"Write migration","description":"Add the tasks table","completed":false,"createdAt":"2026-09-20T21:10:23.244Z","updatedAt":"2026-09-20T21:10:23.246Z"}}curl http://localhost:8787/api/v1/tasks
curl -X PUT http://localhost:8787/api/v1/tasks/<id> \
-H 'Content-Type: application/json' -d '{"completed":true}'
curl -X DELETE http://localhost:8787/api/v1/tasks/<id>A missing row returns a 404:
curl -i http://localhost:8787/api/v1/tasks/nopeHTTP/1.1 404 Not Found
{"message":"Task nope not found","timestamp":"2026-09-20T21:10:23.375Z"}
Every write fires a database event, visible in the wrangler dev output:
[TaskListener] New task created: { … }
[TaskListener] Task updated: { … }
[TaskListener] Task deleted
db/schema.zmodel— the ZenStack modelsrc/database/database.config.ts— connection and dialectsrc/database/database.types.ts—StratalDatabaseaugmentationsrc/tasks/tasks.controller.ts— queries through@InjectDBsrc/listeners/task.listener.ts— database events
If this example helped, please star the Stratal repo — it is the simplest way to support the project and helps other developers find it.