Node.js — Next.js, NestJS, Vite, Express
Before you begin, complete the Getting Started guide and familiarize yourself with the bootstrap pattern.
Step 1: Create .env.example
Commit a .env.example with every variable your project needs. This is the template the bootstrap script will copy and customize per worktree:
PORT=3000
DB_HOST=localhost
DB_USER=postgres
DB_PASSWORD=postgres
DB_NAME=myapp
Add more variables as needed — API_PORT, REDIS_PORT, etc. Add .env to .gitignore.
Step 2: Create the bootstrap script
This script runs on every rift open and rift jump. It copies .env.example to .env, derives a deterministic port from the worktree name (so the same worktree always gets the same port), and sets a worktree-specific database name. See The Bootstrap Pattern for how the hash formula works.
Add a bootstrap script to package.json:
{
"scripts": {
"bootstrap": "node scripts/bootstrap.mjs"
}
}
Create scripts/bootstrap.mjs:
import { createHash } from "crypto";
import { readFileSync, writeFileSync } from "fs";
const worktree = process.env.RIFT_WORKTREE;
// Derive a deterministic base port (range 3000–9999)
const hash = createHash("sha1").update(worktree).digest("hex").replace(/[a-f]/g, "").slice(0, 4);
const port = (Number(hash) % 7000) + 3000;
// Derive a worktree-specific database name
const dbName = `myapp_${worktree.replace(/-/g, "_")}`;
// Write .env from template, override PORT and DB_NAME
let env = readFileSync(".env.example", "utf-8");
env = env.replace(/^PORT=.*/m, `PORT=${port}`);
env = env.replace(/^DB_NAME=.*/m, `DB_NAME=${dbName}`);
writeFileSync(".env", env);
console.log(`Worktree '${worktree}': port=${port}, db=${dbName}`);
Prisma users:
npx prisma db pushcreates the database automatically. For Drizzle, Knex, and other ORMs that don’t, add database creation to the script — see Creating the database below.
Avoiding port collisions across services
Most projects run more than one service — an app server, a database, a cache. Each one binds a port, and every worktree needs its own set. Add more port variables derived from the same base so nothing collides. See multiple ports for details.
Add the extra variables to .env.example:
PORT=3000
API_PORT=3001
DB_PORT=5432
REDIS_PORT=6379
And to scripts/bootstrap.mjs, add more replacements:
env = env.replace(/^API_PORT=.*/m, `API_PORT=${port + 1}`);
env = env.replace(/^DB_PORT=.*/m, `DB_PORT=${port + 2}`);
env = env.replace(/^REDIS_PORT=.*/m, `REDIS_PORT=${port + 3}`);
Creating the database
If your ORM doesn’t auto-create the database (Drizzle, Knex), add this to the end of scripts/bootstrap.mjs:
PostgreSQL (using the pg package):
import pg from "pg";
const client = new pg.Client({
host: "localhost",
user: "postgres",
password: "postgres",
database: "postgres", // connect to default db first
});
await client.connect();
const res = await client.query("SELECT 1 FROM pg_database WHERE datname = $1", [dbName]);
if (res.rowCount === 0) {
await client.query(`CREATE DATABASE "${dbName}"`);
}
await client.end();
MySQL (using the mysql2 package):
import mysql from "mysql2/promise";
const conn = await mysql.createConnection({ host: "localhost", user: "root" });
await conn.query(`CREATE DATABASE IF NOT EXISTS \`${dbName}\``);
await conn.end();
Bash alternative
If you prefer a shell script, create scripts/bootstrap.sh:
#!/usr/bin/env bash
set -euo pipefail
hash=$(echo -n "$RIFT_WORKTREE" | shasum | tr -d 'a-f ' | cut -c1-4)
BASE=$(( (hash % 7000) + 3000 ))
DB_NAME="myapp_$(echo "$RIFT_WORKTREE" | tr '-' '_')"
sed -e "s/^PORT=.*/PORT=$BASE/" \
-e "s/^DB_NAME=.*/DB_NAME=$DB_NAME/" \
.env.example > .env
echo "Worktree '$RIFT_WORKTREE': port=$BASE, db=$DB_NAME"
Step 3: Configure your dev server
Configure your framework to read PORT from .env. The bootstrap script already wrote it there.
Next.js
Next.js reads PORT from .env automatically. No code changes needed:
npm run dev
NestJS
NestJS doesn’t read .env for the listen port by default. Update src/main.ts:
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(process.env.PORT || 3000);
}
bootstrap();
If you’re using @nestjs/config, process.env.PORT is already available after ConfigModule loads.
Vite
Vite doesn’t read PORT for server.port by default. Use the --port flag in your dev script:
{
"scripts": {
"dev": "vite --port ${PORT:-3000}"
}
}
Or update vite.config.ts:
import { defineConfig } from "vite";
export default defineConfig({
server: {
port: Number(process.env.PORT) || 3000,
},
});
Express / generic Node.js
require("dotenv").config();
const port = process.env.PORT || 3000;
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});
Step 4: Configure your database
The bootstrap script already writes DB_NAME to .env. Configure your ORM to read it.
Prisma
Update .env.example to use DB_NAME:
DATABASE_URL=postgresql://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:5432/${DB_NAME}
Or keep a full URL and have the bootstrap script replace the database name directly.
Prisma’s db push creates the database automatically — no database creation needed in the bootstrap.
Drizzle
Configure your drizzle.config.ts to read DB_NAME from the environment. Drizzle does not create the database — the bootstrap script’s database creation step (see Creating the database) handles that.
Knex
Configure knexfile.js to read DB_NAME from the environment. Knex does not create the database — the bootstrap script’s database creation step handles that.
SQLite
SQLite databases are just files. Since each worktree is its own directory, ./dev.db is already unique per worktree — no DB_NAME override needed. You can simplify the bootstrap to skip the database lines.
Step 5: Wire up rift.yaml
Add hooks to rift.yaml so the bootstrap runs automatically on worktree lifecycle events:
Prisma:
hooks:
open: "npm run bootstrap && npm install && npx prisma db push"
jump: "npm run bootstrap"
close: "npx prisma db push --force-reset"
Drizzle:
hooks:
open: "npm run bootstrap && npm install && npx drizzle-kit migrate"
jump: "npm run bootstrap"
Knex:
hooks:
open: "npm run bootstrap && npm install && npx knex migrate:latest"
jump: "npm run bootstrap"
Add seeding to open if needed:
hooks:
open: "npm run bootstrap && npm install && npx prisma db push && npm run seed"
With Docker Compose
If your services run in Docker Compose, add container lifecycle commands. See the Docker Compose guide for full details.
hooks:
open: "npm run bootstrap && npm install && docker compose up -d && npx prisma db push"
jump: "npm run bootstrap"
close: "docker compose down"
purge: "docker compose down -v"
Cleanup
When a worktree is closed, its database lingers on the shared server. Use the close hook to drop it:
| ORM | Drop command |
|---|---|
| Prisma | npx prisma db push --force-reset |
| Drizzle | npx drizzle-kit drop |
| Knex | npx knex migrate:rollback --all |