neon-database
Use when working with Neon serverless PostgreSQL database. Covers database setup, CLI commands, Drizzle ORM integration, schema migrations, connection pooling, and user data storage patterns for authentication and chat history.
When & Why to Use This Skill
This Claude skill provides comprehensive patterns and best practices for working with Neon serverless PostgreSQL databases. It focuses on raw SQL implementation without an ORM, specifically optimized for high-performance environments using Hyperdrive connection pooling. The skill covers everything from initial database setup and schema migrations to complex query patterns and transaction management, ensuring secure, scalable, and efficient data handling for modern web applications.
Use Cases
- Configuring Neon serverless PostgreSQL with Hyperdrive connection pooling to ensure low-latency database access in edge computing environments like Cloudflare Workers.
- Developing secure and efficient raw SQL query patterns for CRUD operations and complex data relationships without the overhead of an ORM.
- Managing database schema evolution through structured migration scripts and implementing performance optimizations such as indexing and query plan analysis.
- Implementing robust data patterns for application-specific features, including user authentication, transaction-safe financial updates, and trending content algorithms.
| name | neon-database |
|---|---|
| description | Neon PostgreSQL patterns for Pitchey. Raw SQL only, no ORM. Uses Hyperdrive for connection pooling. Activates for database queries, migrations, or schema work. |
Neon Database Patterns for Pitchey
CRITICAL: Connection Pattern
ALWAYS use Hyperdrive in Workers. NEVER use direct Neon connection string.
import postgres from 'postgres';
export default {
async fetch(request: Request, env: Env) {
// ✅ CORRECT - Use Hyperdrive binding
const sql = postgres(env.HYPERDRIVE.connectionString);
// ❌ WRONG - Never use direct URL
// const sql = postgres(process.env.DATABASE_URL);
// ❌ WRONG - Never use Neon pooler with Hyperdrive
// const sql = postgres('postgres://...pooler.us-east-2.aws.neon.tech/...');
const result = await sql`SELECT * FROM users LIMIT 10`;
return Response.json(result);
}
}
Query Patterns (Raw SQL - No ORM)
Select with Parameters
// Safe parameterized query (prevents SQL injection)
const users = await sql`
SELECT * FROM users WHERE id = ${userId}
`;
// Multiple parameters
const pitches = await sql`
SELECT * FROM pitches
WHERE creator_id = ${creatorId}
AND status = ${status}
ORDER BY created_at DESC
LIMIT ${limit}
`;
Insert and Return
const [newPitch] = await sql`
INSERT INTO pitches (title, description, creator_id)
VALUES (${title}, ${description}, ${creatorId})
RETURNING *
`;
Update
const [updated] = await sql`
UPDATE pitches
SET title = ${title}, updated_at = NOW()
WHERE id = ${pitchId} AND creator_id = ${userId}
RETURNING *
`;
Delete
await sql`
DELETE FROM pitches
WHERE id = ${pitchId} AND creator_id = ${userId}
`;
Transactions
await sql.begin(async (tx) => {
await tx`UPDATE accounts SET balance = balance - ${amount} WHERE id = ${fromId}`;
await tx`UPDATE accounts SET balance = balance + ${amount} WHERE id = ${toId}`;
await tx`INSERT INTO transfers (from_id, to_id, amount) VALUES (${fromId}, ${toId}, ${amount})`;
});
Common Pitchey Queries
Users
-- Get user by ID
SELECT * FROM users WHERE id = ${userId};
-- Get user with profile
SELECT u.*, p.bio, p.avatar_url
FROM users u
LEFT JOIN profiles p ON u.id = p.user_id
WHERE u.id = ${userId};
-- Get user by email (for auth)
SELECT * FROM users WHERE email = ${email};
Pitches
-- Trending pitches (most views in 7 days)
SELECT p.*, u.name as creator_name, u.avatar_url as creator_avatar
FROM pitches p
JOIN users u ON p.creator_id = u.id
WHERE p.status = 'published'
AND p.created_at > NOW() - INTERVAL '7 days'
ORDER BY p.view_count DESC
LIMIT ${limit};
-- New releases (most recent)
SELECT p.*, u.name as creator_name, u.avatar_url as creator_avatar
FROM pitches p
JOIN users u ON p.creator_id = u.id
WHERE p.status = 'published'
ORDER BY p.created_at DESC
LIMIT ${limit};
-- Single pitch with creator
SELECT p.*, u.name as creator_name, u.email as creator_email
FROM pitches p
JOIN users u ON p.creator_id = u.id
WHERE p.id = ${pitchId};
NDAs
-- Get NDA with both parties
SELECT n.*,
req.name as requester_name, req.email as requester_email,
own.name as owner_name, own.email as owner_email,
p.title as pitch_title
FROM ndas n
JOIN users req ON n.requester_id = req.id
JOIN users own ON n.owner_id = own.id
JOIN pitches p ON n.pitch_id = p.id
WHERE n.id = ${ndaId};
-- Pending NDAs for owner
SELECT n.*, u.name as requester_name, p.title as pitch_title
FROM ndas n
JOIN users u ON n.requester_id = u.id
JOIN pitches p ON n.pitch_id = p.id
WHERE n.owner_id = ${ownerId} AND n.status = 'pending'
ORDER BY n.created_at DESC;
-- Update NDA status
UPDATE ndas
SET status = ${status},
updated_at = NOW(),
${status === 'approved' ? sql`approved_at = NOW()` : sql``}
WHERE id = ${ndaId}
RETURNING *;
Migrations
Store in /migrations/ with timestamp prefix. Run via Neon console or MCP.
-- migrations/20260102_001_add_nda_fields.sql
-- Add signature tracking
ALTER TABLE ndas ADD COLUMN IF NOT EXISTS signed_at TIMESTAMPTZ;
ALTER TABLE ndas ADD COLUMN IF NOT EXISTS signature_url TEXT;
ALTER TABLE ndas ADD COLUMN IF NOT EXISTS rejection_reason TEXT;
-- Add index for faster queries
CREATE INDEX IF NOT EXISTS idx_ndas_status ON ndas(status);
CREATE INDEX IF NOT EXISTS idx_ndas_owner ON ndas(owner_id, status);
CREATE INDEX IF NOT EXISTS idx_ndas_requester ON ndas(requester_id, status);
Performance Tips
- Always use LIMIT on list queries
- Add indexes for columns in WHERE and JOIN clauses
- Use EXPLAIN ANALYZE to check query plans
- Avoid SELECT * in production - select only needed columns
- Use transactions for multi-table updates
- Connection is managed by Hyperdrive - don't worry about pooling