db-connect

Patterns for connecting to PostgreSQL and MySQL databases in Node.js, including connection pooling, read-only users, and schema introspection queries for size and row count collection

7 stars

Best use case

db-connect is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Patterns for connecting to PostgreSQL and MySQL databases in Node.js, including connection pooling, read-only users, and schema introspection queries for size and row count collection

Teams using db-connect should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/db-connect/SKILL.md --create-dirs "https://raw.githubusercontent.com/heldernoid/agentic-build-templates/main/projects/data-analytics/database-size-monitor/skills/db-connect/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/db-connect/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How db-connect Compares

Feature / Agentdb-connectStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Patterns for connecting to PostgreSQL and MySQL databases in Node.js, including connection pooling, read-only users, and schema introspection queries for size and row count collection

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# db-connect Skill

## When to Use

Use this skill when implementing Node.js code that connects to PostgreSQL or MySQL databases to collect metadata (table sizes, row counts, schema info) without modifying data.

## PostgreSQL Connection with pg

```typescript
import { Pool } from 'pg';

export function createPostgresPool(connectionString: string): Pool {
  return new Pool({
    connectionString,
    max: 3,                  // small pool for read-only monitoring
    idleTimeoutMillis: 10_000,
    connectionTimeoutMillis: 5_000,
    ssl: connectionString.includes('ssl=true') ? { rejectUnauthorized: false } : undefined,
  });
}

export async function collectPostgres(connectionString: string): Promise<TableSizeRow[]> {
  const pool = createPostgresPool(connectionString);
  try {
    const result = await pool.query(`
      SELECT
        schemaname AS schema_name,
        relname AS table_name,
        n_live_tup AS row_count,
        pg_relation_size(quote_ident(schemaname) || '.' || quote_ident(relname)) AS size_bytes,
        pg_indexes_size(quote_ident(schemaname) || '.' || quote_ident(relname)) AS index_size_bytes,
        pg_total_relation_size(quote_ident(schemaname) || '.' || quote_ident(relname)) AS total_size_bytes
      FROM pg_stat_user_tables
      WHERE schemaname NOT IN ('pg_catalog', 'information_schema')
      ORDER BY total_size_bytes DESC
    `);
    return result.rows.map((row) => ({
      schemaName: row.schema_name,
      tableName: row.table_name,
      rowCount: parseInt(row.row_count, 10),
      sizeBytes: parseInt(row.size_bytes, 10),
      indexSizeBytes: parseInt(row.index_size_bytes, 10),
      totalSizeBytes: parseInt(row.total_size_bytes, 10),
    }));
  } finally {
    await pool.end(); // always close -- this is a one-shot collector
  }
}
```

Note: `pg_relation_size` and related functions may return `-1` for tables the current user cannot access. Filter those rows before inserting.

## MySQL Connection with mysql2

```typescript
import mysql from 'mysql2/promise';

export async function collectMysql(connectionString: string): Promise<TableSizeRow[]> {
  const connection = await mysql.createConnection(connectionString);
  try {
    const [rows] = await connection.execute<mysql.RowDataPacket[]>(`
      SELECT
        TABLE_SCHEMA AS schema_name,
        TABLE_NAME AS table_name,
        IFNULL(TABLE_ROWS, 0) AS row_count,
        IFNULL(DATA_LENGTH, 0) AS size_bytes,
        IFNULL(INDEX_LENGTH, 0) AS index_size_bytes,
        IFNULL(DATA_LENGTH + INDEX_LENGTH, 0) AS total_size_bytes
      FROM information_schema.TABLES
      WHERE TABLE_TYPE = 'BASE TABLE'
        AND TABLE_SCHEMA NOT IN ('information_schema', 'performance_schema', 'mysql', 'sys')
      ORDER BY total_size_bytes DESC
    `);
    return rows.map((row) => ({
      schemaName: String(row.schema_name),
      tableName: String(row.table_name),
      rowCount: Number(row.row_count),
      sizeBytes: Number(row.size_bytes),
      indexSizeBytes: Number(row.index_size_bytes),
      totalSizeBytes: Number(row.total_size_bytes),
    }));
  } finally {
    await connection.end();
  }
}
```

## Connection String Formats

### PostgreSQL

```
postgresql://user:password@hostname:5432/database
postgresql://user:password@hostname:5432/database?sslmode=require
postgres://user:password@/var/run/postgresql/database  (Unix socket)
```

The `pg` library also accepts an object config:
```typescript
const pool = new Pool({
  host: 'localhost',
  port: 5432,
  user: 'monitor',
  password: 'secret',
  database: 'mydb',
  ssl: { rejectUnauthorized: false },
});
```

### MySQL

mysql2 accepts a URL string or config object:
```
mysql://user:password@hostname:3306/database
mysql://user:password@hostname:3306/database?ssl=true
```

Config object:
```typescript
const connection = await mysql.createConnection({
  host: 'localhost',
  port: 3306,
  user: 'monitor',
  password: 'secret',
  database: 'mydb',
  ssl: { rejectUnauthorized: false },
});
```

## Creating a Read-Only Monitoring User

### PostgreSQL

```sql
CREATE USER db_monitor WITH PASSWORD 'secure_password';
GRANT CONNECT ON DATABASE mydb TO db_monitor;
GRANT USAGE ON SCHEMA public TO db_monitor;
-- pg_stat_user_tables is accessible by default to any connected user
-- but pg_relation_size requires table visibility
GRANT SELECT ON ALL TABLES IN SCHEMA public TO db_monitor;
-- For cross-schema monitoring:
GRANT USAGE ON SCHEMA analytics TO db_monitor;
GRANT SELECT ON ALL TABLES IN SCHEMA analytics TO db_monitor;
```

### MySQL

```sql
CREATE USER 'db_monitor'@'%' IDENTIFIED BY 'secure_password';
-- information_schema is readable without grants on regular schemas
GRANT SELECT ON information_schema.TABLES TO 'db_monitor'@'%';
GRANT SELECT ON information_schema.SCHEMATA TO 'db_monitor'@'%';
FLUSH PRIVILEGES;
```

## Testing a Connection

```typescript
export async function testConnection(
  type: 'postgres' | 'mysql',
  connectionString: string
): Promise<{ ok: boolean; latencyMs: number; error?: string }> {
  const start = Date.now();
  try {
    if (type === 'postgres') {
      const pool = createPostgresPool(connectionString);
      await pool.query('SELECT 1');
      await pool.end();
    } else {
      const conn = await mysql.createConnection(connectionString);
      await conn.execute('SELECT 1');
      await conn.end();
    }
    return { ok: true, latencyMs: Date.now() - start };
  } catch (err) {
    return {
      ok: false,
      latencyMs: Date.now() - start,
      error: err instanceof Error ? err.message : String(err),
    };
  }
}
```

## SSL / TLS Notes

For managed cloud databases (RDS, CloudSQL, PlanetScale), TLS is required. Set the SSL mode in the connection string or config:

- PostgreSQL: append `?sslmode=require` or `?sslmode=verify-full`
- MySQL: set `ssl: true` in the config object or `?ssl=true` in the URL

If the database uses a self-signed certificate and you trust the network, use `rejectUnauthorized: false`. For production, provide the CA certificate:

```typescript
const pool = new Pool({
  connectionString,
  ssl: {
    rejectUnauthorized: true,
    ca: fs.readFileSync('/path/to/ca-cert.pem').toString(),
  },
});
```

## Error Handling

Common errors and their causes:

| Error | Cause |
|-------|-------|
| `ECONNREFUSED` | Wrong host or port, or database not running |
| `ETIMEDOUT` | Network unreachable or firewall blocking the port |
| `ENOTFOUND` | DNS resolution failed for the hostname |
| `password authentication failed` | Wrong username or password |
| `database "mydb" does not exist` | Database name typo or not created |
| `SSL SYSCALL error` | TLS mismatch -- try adding `sslmode=require` or removing SSL config |

Always catch and log the full error message, and store it in `databases.last_error` so the dashboard can surface connection problems.

Related Skills

ssh-connection-test

7
from heldernoid/agentic-build-templates

Test SSH connectivity to hosts configured in ~/.ssh/config using sshm. Use when diagnosing connection issues, verifying hosts are reachable, or checking all hosts after a network change. Triggers include "test ssh", "ssh connectivity", "can I connect", "ssh reachable", "sshm test".

Skill: Uptime Monitoring

7
from heldernoid/agentic-build-templates

## Overview

Skill: Status Page

7
from heldernoid/agentic-build-templates

## Overview

Skill: unit-conversion

7
from heldernoid/agentic-build-templates

## Overview

Skill: recipe-scaler

7
from heldernoid/agentic-build-templates

## Overview

reading-list

7
from heldernoid/agentic-build-templates

Operate the reading-list API to save, manage, tag, search, and export articles.

email-digest

7
from heldernoid/agentic-build-templates

Configure, test, and troubleshoot the reading-list daily email digest delivered via nodemailer.

websocket-realtime

7
from heldernoid/agentic-build-templates

Use the WebSocket connection in poll-builder to receive live vote updates. Use when you need to stream real-time poll results, monitor a poll for new votes, or build a live dashboard. Triggers include "live results", "real-time updates", "stream votes", "watch poll", or "WebSocket".

poll-builder

7
from heldernoid/agentic-build-templates

Self-hosted poll creation tool with real-time results. Use when you need to create a poll, check vote counts, close a poll, export results, or get the shareable link for a poll. Triggers include "create poll", "vote", "poll results", "survey", "collect votes", "share poll", or any task involving polling or voting.

Skill: personal-finance

7
from heldernoid/agentic-build-templates

## Overview

Skill: csv-import

7
from heldernoid/agentic-build-templates

## Overview

Skill: Syntax Highlighting

7
from heldernoid/agentic-build-templates

## Purpose