Deploy a Node.js API and its PostgreSQL database from your AI agent, over MCP
Your AI agent already knows how to write your Node.js API. Connected to while over MCP, it deploys it too, with its PostgreSQL database backed up every day, and you never touch a server.
By Aloïs Oddon, co-founder of Nextmap. Facts checked on November 2, 2026.
Prerequisites
- A Node.js API.
- An AI agent that works in a terminal: Claude Code, Codex, Cursor or Gemini CLI.
Step 1: Give your agent these instructions
Open your agent in your API’s folder, and paste these instructions into it:
Connect to while with my account (while.sh/agents), then deploy my app.
Without an account, your agent deploys your API as a trial, in one request. To keep it, open the link it gives you and create your account.
With your account, your agent adds while’s MCP server itself. If it asks you to, restart it. On the first call to while, a page opens in your browser. In Claude Code, if it doesn’t open, type /mcp, choose while, then Authenticate. On that page, choose your app, or “A new app”, then click Authorize.
Step 2: Open your API
Your agent gives you your API’s address, for example https://mon-api.while.cloud.
What your agent does
It reads the rules for an app on while, like listening on PORT and answering 200 on /health, then writes while.yaml at the root of your API:
apiVersion: while/v1
project: mon-api
services:
- name: web
port: 3000
databases:
- name: main
The last two lines add a PostgreSQL database. Its address, password included, reaches your API in DATABASE_URL. node-postgres reads it as is1, as in this Express API tested on while:
import express from 'express';
import pg from 'pg';
const db = new pg.Pool({ connectionString: process.env.DATABASE_URL });
await db.query('CREATE TABLE IF NOT EXISTS visites (id serial PRIMARY KEY, cree_le timestamptz DEFAULT now())');
const app = express();
app.get('/health', (_req, res) => res.send('ok'));
app.get('/', async (_req, res) => {
await db.query('INSERT INTO visites DEFAULT VALUES');
const { rows } = await db.query('SELECT count(*)::int AS n FROM visites');
res.send(`visites : ${rows[0].n}`);
});
app.listen(process.env.PORT || 3000, '0.0.0.0');
It then sends the code, which is built, tested, then deployed.
Choose the PostgreSQL version
You, to your agent: Create the database on PostgreSQL 18.
You choose the version when the database is created: PostgreSQL 17 by default, 16 or 18 with version in while.yaml:
databases:
- name: main
version: "18"
Plan the disk space
You, to your agent: Create the database with 10 GiB of disk.
You choose the disk when the database is created, with size: 1 GiB by default. Ask for it before the first deployment.
databases:
- name: main
size: 10Gi
Use Prisma with PostgreSQL
You, to your agent: Use Prisma to create and migrate the tables.
Your agent writes the schema and runs the migration when the API starts. Prisma reads the database address from DATABASE_URL:
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
"type": "module",
"scripts": {
"build": "prisma generate",
"start": "prisma migrate deploy && node server.js"
},
"dependencies": {
"@prisma/client": "^6.1.0",
"prisma": "^6.1.0"
}
Prisma goes in dependencies: the deployed API is installed without its devDependencies. There’s no need to add ?pgbouncer=true to the address: PgBouncer handles prepared statements.
Your agent doesn’t declare a directUrl: the database’s direct address is only given to jobs, and Prisma would stop at startup with error P1012.
Query PostgreSQL from your agent, over MCP
You, to your agent: How many visits today?
Your agent reads your database in SQL through while’s MCP server, with your permissions on the data, and it can’t write to it. Every query is recorded in while’s activity log, with its values replaced by $1, $2…
Export a table to CSV
You, to your agent: Export the visites table to CSV.
Your agent writes the table to a CSV file, with the same permissions it has to read it.
Find slow queries
You, to your agent: Which queries are slowing down the database?
Your agent reads your database’s most frequent and slowest queries, as recorded by pg_stat_statements.
Change the database size
You, to your agent: Move the database to size M.
Your agent changes compute in while.yaml. Before the change, it sees how much more it costs per month. The database then restarts, in about fifteen seconds.
Size (compute) | CPU | Memory | Concurrent clients | Price, excl. VAT |
|---|---|---|---|---|
| S, by default | 0.1 vCPU | 256 MB, up to 1 GB | 200 | €5 per month |
| M | 0.5 vCPU | 1 GB | 400 | €17 per month |
| L | 1 vCPU | 4 GB | 600 | €65 per month |
| XL | 2 vCPU | 8 GB | 800 | €150 per month |
databases:
- name: main
compute: M
Add a standby instance
You, to your agent: Add a standby instance to the database.
With instances: 2, a second instance is kept up to date continuously, on its own disk, and takes over if the first one goes down. With the Enterprise plan, a production database has two instances by default.
Back up before a change
You, to your agent: Take a backup before the migration.
Your agent starts a backup, immediately followed by a test: a throwaway database is created from it, and its tables are read back. Every database is also backed up every day, and its write-ahead log is archived continuously.
Go back to a backup or a precise time
You, to your agent: Restore the database to yesterday at 6 pm.
Your database goes back to a backup or to a precise moment, within their retention period: 14 days. In production, your agent gives you a link: the database only goes back once the restore is approved, by you or by an administrator depending on your permissions. A safety backup is taken first, then the restored database replaces the old one.
Understand a failed deployment
You, to your agent: Why did the deployment fail?
Your agent receives the error and the last lines of the build log. Your tests run during the build: if they fail, your API keeps its previous version.
For example, connect ECONNREFUSED 127.0.0.1:5432: your tests are looking for a database, which isn’t connected during the build. Your agent writes them so they pass without it, or turns them off:
services:
- name: web
port: 3000
build:
skip_tests: true
Configure the session behind PgBouncer
DATABASE_URL goes through PgBouncer in transaction mode: each transaction can use a different connection. A SET outside a transaction only lasts for its query. To configure the session, use SET LOCAL inside a transaction:
const client = await db.connect();
try {
await client.query('BEGIN');
await client.query("SET LOCAL statement_timeout = '5s'");
await client.query('SELECT count(*) FROM visites');
await client.query('COMMIT');
} catch (e) {
await client.query('ROLLBACK');
throw e;
} finally {
client.release();
}
Anything that needs the whole session, like pg_restore or a migration that runs SET commands, runs in a scheduled job, which your agent can also start on demand. The job receives the database’s direct address in DB_MAIN_DIRECT_URL.
In the docs: PostgreSQL databases, Web service, Valkey cache (Redis), Scheduled jobs and Versions and rollback.
Next steps
Sources
- node-postgres, connecting with a connection string (connectionString), accessed November 1, 2026