Deploy a Node.js Stack (Express + PostgreSQL)
A minimal Express app that counts page visits in PostgreSQL, deployed as a Stack. The platform injects the database connection string into the web app and deploys both in order.
Result: visiting the app shows Hello from Kuploy Stacks! Visits: N, incrementing on each refresh.
1. The app
The app code is the same whichever build method you pick:
index.js
const express = require("express");
const { Pool } = require("pg");
const pool = new Pool({ connectionString: process.env.DATABASE_URL }); // injected by the stack
const app = express();
app.get("/", async (_req, res) => {
await pool.query("CREATE TABLE IF NOT EXISTS visits (count int)");
await pool.query(
"INSERT INTO visits (count) SELECT 0 WHERE NOT EXISTS (SELECT 1 FROM visits)",
);
const { rows } = await pool.query(
"UPDATE visits SET count = count + 1 RETURNING count",
);
res.send(`Hello from Kuploy Stacks! Visits: ${rows[0].count}`);
});
app.listen(3000, () => console.log("listening on 3000"));
package.json
{
"name": "visits",
"version": "1.0.0",
"main": "index.js",
"scripts": { "start": "node index.js" },
"dependencies": { "express": "^4.19.2", "pg": "^8.12.0" }
}
Grab this from kuploy/examples (subdir express-postgres), or create the files yourself and push them to a Git repo. First complete the tutorial Prerequisites — a connected Git provider and a build registry — then connect the repo and set the build path as in Deploy Your First App.
2. Choose a build method
Pick one — the choice syncs across these tutorials. See Build Methods for the full list.
- Dockerfile
- Nixpacks
Full control. The repo includes a Dockerfile:
FROM node:20-slim
WORKDIR /app
COPY package.json ./
RUN npm install --omit=dev
COPY . .
EXPOSE 3000
CMD ["npm", "start"]
When you create the app, set Build Type → Dockerfile and enter Dockerfile in the Docker File field (required; relative to the build path).
Zero-config — Nixpacks auto-detects Node.js, runs npm install, and starts the app with your package.json start script (node index.js). No Dockerfile needed.
When you create the app, set Build Type → Nixpacks.
3. Create the services
In your project's environment:
- Create Service → Application named
web. The Set up your application wizard opens — walk the steps in order (or close it and use the service's tabs manually):- Source — point it at your Git repo (set Build Path
express-postgresif usingkuploy/examples). - Build — pick the Build Type you chose above.
- Network — set the port to 3000; add a domain if you want it public.
- Registry — select the registry from the prerequisites; without it the build has nowhere to push and the deploy fails with Registry required. (Skipped automatically for Docker-image sources or single-server deploys.)
- Deploy — stays disabled until source and registry are set. Don't deploy it yet.
- Source — point it at your Git repo (set Build Path
- Create Service → Database → PostgreSQL named
db(defaults are fine). Its internal connection string — shown on the db's General tab — ispostgresql://<user>:<password>@<db-app-name>:5432/<database>(the host is the service's app name; see Databases). The stack injects exactly this string asDATABASE_URL.
4. Group them into a stack
- Environment toolbar → Stacks → Create stack, name it
visits, open it. - Add component → add
web, thendb.
5. Connect
Drag from db to web (or click Connect):
- Provider:
db - Source field:
connectionString - Consumer:
web - Env var name:
DATABASE_URL
The pg Pool reads DATABASE_URL directly.
6. Deploy
Click Deploy stack — db deploys before web. Open the app's domain and refresh; the counter climbs.
The web service's Environment tab shows a managed block with the resolved DATABASE_URL.
What you learned
- A stack component is a normal service.
- One connection injects the database URL; no manual credential wiring.
- Deploy order follows the connection automatically.
- The same app deploys via Dockerfile or Nixpacks — only the build config differs.