Skip to main content

Database Configuration

The database configuration is defined in db.ts in the project root.

Connection Settings

db.ts
import postgres from "postgres"

const DB_HOST = 'localhost'
const DB_PORT = 5432
const DB_NAME = 'appdb'
const DB_USER = 'appuser'
const DB_PASSWORD = 'secret'

export const sql = postgres({
    host: DB_HOST,
    port: DB_PORT,
    database: DB_NAME,
    username: DB_USER,
    password: DB_PASSWORD,
    publications: 'alltables', // Critical for change tracking
})

Configuration Options

DB_HOST
string
default:"localhost"
The hostname or IP address of the PostgreSQL server.Example for remote server:
const DB_HOST = 'postgres.example.com'
DB_PORT
number
default:"5432"
The port on which PostgreSQL is listening.Example for custom port:
const DB_PORT = 5433
DB_NAME
string
default:"appdb"
The name of the database to connect to.
This database must have logical replication enabled and contain the required publication.
DB_USER
string
default:"appuser"
The PostgreSQL user for authentication.Required privileges:
  • SELECT on all monitored tables
  • REPLICATION privilege for logical replication
DB_PASSWORD
string
default:"secret"
The password for the PostgreSQL user.
In production, use environment variables instead of hardcoded credentials.
publications
string
default:"alltables"
The PostgreSQL publication to subscribe to for change tracking.This must match the publication created in your database:
CREATE PUBLICATION alltables FOR ALL TABLES;

Server Configuration

The server configuration is located in src/index.ts.

Port and Development Settings

src/index.ts
const server = serve({
  port: 3000,
  // ... other options
  
  development: process.env.NODE_ENV !== "production" && {
    // Enable browser hot reloading in development
    hmr: true,

    // Echo console logs from the browser to the server
    console: true,
  },
})
port
number
default:"3000"
The port on which the HTTP/WebSocket server listens.To change the port:
const server = serve({
  port: 8080,
  // ...
})
development.hmr
boolean
default:"true"
Enables Hot Module Reloading for the React frontend.Automatically disabled in production (NODE_ENV=production).
development.console
boolean
default:"true"
When enabled, browser console logs are echoed to the server terminal.Useful for debugging frontend issues.

WebSocket Configuration

The WebSocket server is configured with handlers for connection lifecycle:
src/index.ts
websocket: {
  message(ws: any, message: string | Buffer) {
    console.log("Message received:", message);
  },
  open(ws: any) {
    clients.add(ws);
    console.log("WebSocket client connected. Total:", clients.size);

    // Send all existing changes to the new client
    if (allChanges.length > 0) {
      ws.send(
        JSON.stringify({
          type: "initial",
          data: allChanges,
        })
      );
    }
  },
  close(ws: any) {
    clients.delete(ws);
    console.log("WebSocket client disconnected. Total:", clients.size);
  },
}
Handles incoming messages from WebSocket clients.Currently logs messages to the console. Extend this to handle client commands:
message(ws: any, message: string | Buffer) {
  const data = JSON.parse(message.toString());
  if (data.type === 'clear') {
    allChanges.length = 0;
    broadcast({ type: 'cleared' });
  }
}
Called when a new WebSocket client connects.
  • Adds the client to the active clients set
  • Sends all accumulated changes to catch up the new client
  • Logs the total number of connected clients
Called when a WebSocket client disconnects.
  • Removes the client from the active clients set
  • Logs the remaining number of connected clients

PostgreSQL Subscription

The subscription configuration determines what changes are captured:
src/index.ts
const { unsubscribe } = await sql.subscribe(
  "*", // pattern: all (all operations and tables)
  (row, { command, relation }) => {
    // Change handler
    const tableName =
      typeof relation === "string"
        ? relation
        : relation?.table
          ? `${relation.schema || "public"}.${relation.table}`
          : JSON.stringify(relation);

    const rows = Array.isArray(row) ? row : [row];
    const newChanges = rows.map((r) => ({
      operation: command.toUpperCase(),
      table: tableName,
      ...r,
    }));

    allChanges.push(...newChanges);
    broadcast({
      type: "change",
      data: newChanges,
      total: allChanges.length,
    });

    console.log(
      `📊 Change detected: ${command.toUpperCase()} on ${tableName}`
    );
  },
  () => {
    console.log("✅ Realtime subscription ready (connected or reconnected)");
  }
);

Subscription Pattern

Monitor all tables in all schemas:
await sql.subscribe("*", changeHandler, readyHandler)

Captured Operations

The subscription captures the following operations:
  • INSERT: New rows added to tables
  • UPDATE: Existing rows modified
  • DELETE: Rows removed from tables
TRUNCATE operations may not be captured depending on your PostgreSQL version and publication settings.

API Endpoints

The server exposes two endpoints:

GET /api/changes

Returns all accumulated changes since server start.
src/index.ts
if (url.pathname === "/api/changes") {
  return Response.json(allChanges);
}
Response format:
[
  {
    "operation": "INSERT",
    "table": "public.todos",
    "id": 1,
    "title": "Test task",
    "done": false,
    "created_at": "2026-03-03T10:30:00Z"
  }
]

WS /ws

WebSocket endpoint for real-time change notifications. Message types:
{
  "type": "initial",
  "data": [/* all changes */]
}

Environment Variables

For production deployments, use environment variables instead of hardcoded values:
db.ts
const DB_HOST = process.env.DB_HOST || 'localhost'
const DB_PORT = parseInt(process.env.DB_PORT || '5432')
const DB_NAME = process.env.DB_NAME || 'appdb'
const DB_USER = process.env.DB_USER || 'appuser'
const DB_PASSWORD = process.env.DB_PASSWORD || 'secret'
Then run with:
DB_HOST=prod-db.example.com \
DB_USER=monitor_user \
DB_PASSWORD=secure_password \
bun run dev
Never commit credentials to version control. Use .env files (and add them to .gitignore) or a secrets management system.

TypeScript Configuration

The project uses strict TypeScript settings in tsconfig.json:
tsconfig.json
{
  "compilerOptions": {
    "module": "nodenext",
    "target": "esnext",
    "types": ["bun-types"],
    "strict": true,
    "jsx": "react-jsx",
    "verbatimModuleSyntax": true,
    "isolatedModules": true,
    "noUncheckedIndexedAccess": true,
    "exactOptionalPropertyTypes": true
  }
}
These settings ensure type safety and catch potential errors at compile time.

Next Steps

Setup

Return to the setup guide

Usage

Learn how to use the monitoring dashboard

Build docs developers (and LLMs) love