Move requests into Postgres
The MCP server you build next needs real tables to read, and analytics needs request ids that persist, so the day starts with the database. Postgres holds the data, and the same SQL also runs on an embedded version of Postgres, for tests and for local runs without a database server.
Study first
- postgres.js and PGlite. postgres.js is a Postgres client library for Node, and PGlite is Postgres compiled to WebAssembly, so the SQL in this project runs on both.
Build
- Install a local Postgres. On macOS, run
brew install postgresql@18,brew services start postgresql@18andcreatedb workbot, and on Linux use your distribution's package. Then setDATABASE_URL=postgres://localhost/workbotin.env.local. - Write
src/lib/db.tswith one function,query(text, params), which runs one statement with$1to$nparameters and returns the rows as objects. With apostgres://URL it uses postgres.js. Without one, it opens an embedded PGlite database in.data/pg, and tests passmemory://for a database held in memory. At startup it runs the schema, a list of idempotent statements insrc/lib/schema.ts, which are statements that are safe to run again. - Split the request data model. Put the schemas, constants and labels in
src/lib/request-model.ts, which holds no database code, so Client Components may import it. Keep the queries insrc/lib/requests.ts. - Rewrite
createRequest,listRequests,getRequestandupdateRequestas SQL, and parse every row through theWorkRequestschema. - Make
/api/healthrunselect 1and answer 503 when the database is unreachable. Adderror.tsxandnot-found.tsx, which give the app an error boundary and a 404 page.
Done when
pnpm testpasses on the in-memory database, and a request filed in the app is still there after you restartpnpm dev./api/healthreports the database.

Technical notes
- Route bundles do not share module instances, so keep the connection on
globalThis, as the in-memory list was. An embedded database can only be open in one process. - Write
created_at as "createdAt"in the select list, so rows arrive in the shape the schema expects and you need no mapping code. - The id is a
uuidcolumn with a default, so WorkBot answers an id that is not a UUID as not found, before the query runs. - Each schema statement is idempotent, such as
create table if not exists, so the list can run on every start against any earlier state. Never edit an earlier statement, because databases that already ran it would not see the edit. Add each change as a new statement at the end. - The health test runs against the in-memory database, so it checks only the shape of the report. To check that the server can reach the database, open
/api/healthafter a deploy.
