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
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
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/db-connect/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How db-connect Compares
| Feature / Agent | db-connect | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/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
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
## Overview
Skill: Status Page
## Overview
Skill: unit-conversion
## Overview
Skill: recipe-scaler
## Overview
reading-list
Operate the reading-list API to save, manage, tag, search, and export articles.
email-digest
Configure, test, and troubleshoot the reading-list daily email digest delivered via nodemailer.
websocket-realtime
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
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
## Overview
Skill: csv-import
## Overview
Skill: Syntax Highlighting
## Purpose