Neon is serverless PostgreSQL that scales to zero — you only pay for what you use. The free tier gives you 0.5 GB storage and 190 compute hours per month. Its API lets you manage databases, branches, and endpoints programmatically.
Why Use Neon?
- Scales to zero — no cost when idle
- Database branching — instant copies for development
- Standard PostgreSQL — use any Postgres client or ORM
- Free tier generous for side projects and MVPs
Getting Started
Get your API key from console.neon.tech > Account Settings > API Keys:
export NEON_API_KEY="your-api-key"
# List projects
curl -s -H "Authorization: Bearer $NEON_API_KEY" \
"https://console.neon.tech/api/v2/projects" | jq '.projects[] | {id: .id, name: .name, region_id: .region_id}'
# Create a new project
curl -s -H "Authorization: Bearer $NEON_API_KEY" \
-H "Content-Type: application/json" \
-X POST "https://console.neon.tech/api/v2/projects" \
-d '{"project": {"name": "my-app"}}' | jq '{project_id: .project.id, connection_uri: .connection_uris[0].connection_uri}'
Python Client
import requests
import psycopg2
class NeonClient:
def __init__(self, api_key):
self.url = "https://console.neon.tech/api/v2"
self.headers = {'Authorization': f'Bearer {api_key}', 'Content-Type': 'application/json'}
def list_projects(self):
resp = requests.get(f"{self.url}/projects", headers=self.headers)
return resp.json()['projects']
def create_project(self, name, region='aws-us-east-1'):
resp = requests.post(f"{self.url}/projects", json={'project': {'name': name, 'region_id': region}}, headers=self.headers)
return resp.json()
def create_branch(self, project_id, branch_name, parent_id=None):
payload = {'branch': {'name': branch_name}}
if parent_id:
payload['branch']['parent_id'] = parent_id
resp = requests.post(f"{self.url}/projects/{project_id}/branches", json=payload, headers=self.headers)
return resp.json()
def list_branches(self, project_id):
resp = requests.get(f"{self.url}/projects/{project_id}/branches", headers=self.headers)
return resp.json()['branches']
def delete_branch(self, project_id, branch_id):
resp = requests.delete(f"{self.url}/projects/{project_id}/branches/{branch_id}", headers=self.headers)
return resp.status_code == 200
def get_connection_uri(self, project_id, branch_id=None):
params = {}
if branch_id:
params['branch_id'] = branch_id
resp = requests.get(f"{self.url}/projects/{project_id}/connection_uri", params=params, headers=self.headers)
return resp.json()['uri']
# Usage
neon = NeonClient('your-api-key')
for project in neon.list_projects():
print(f"{project['name']:20s} Region: {project['region_id']}")
Database Branching for Development
def create_dev_environment(neon, project_id, developer_name):
branch_name = f"dev-{developer_name}"
# Create a branch (instant copy of production data)
result = neon.create_branch(project_id, branch_name)
branch_id = result['branch']['id']
# Get connection string for this branch
uri = neon.get_connection_uri(project_id, branch_id)
print(f"Dev environment ready for {developer_name}:")
print(f" Branch: {branch_name}")
print(f" Connection: {uri[:50]}...")
return branch_id, uri
def cleanup_dev_environment(neon, project_id, branch_id):
neon.delete_branch(project_id, branch_id)
print("Dev environment cleaned up")
# Each developer gets their own database copy
branch_id, uri = create_dev_environment(neon, 'proj_xxx', 'alice')
# When done with feature
cleanup_dev_environment(neon, 'proj_xxx', branch_id)
Safe Schema Migrations
def test_migration(neon, project_id, migration_sql):
# Create a test branch
result = neon.create_branch(project_id, 'migration-test')
branch_id = result['branch']['id']
uri = neon.get_connection_uri(project_id, branch_id)
try:
conn = psycopg2.connect(uri)
cur = conn.cursor()
# Run migration on test branch
cur.execute(migration_sql)
conn.commit()
# Verify
cur.execute("SELECT COUNT(*) FROM information_schema.columns WHERE table_name = 'users'")
print(f"Migration successful! Columns: {cur.fetchone()[0]}")
cur.close()
conn.close()
return True
except Exception as e:
print(f"Migration failed: {e}")
return False
finally:
neon.delete_branch(project_id, branch_id)
print("Test branch cleaned up")
test_migration(neon, 'proj_xxx', 'ALTER TABLE users ADD COLUMN avatar_url TEXT')
Serverless Connection
# Neon's serverless driver works great with edge functions
# No connection pooling needed!
import psycopg2
def query(connection_uri, sql, params=None):
conn = psycopg2.connect(connection_uri, sslmode='require')
cur = conn.cursor()
cur.execute(sql, params)
if cur.description:
columns = [desc[0] for desc in cur.description]
results = [dict(zip(columns, row)) for row in cur.fetchall()]
else:
results = None
conn.commit()
cur.close()
conn.close()
return results
# Usage
users = query(CONNECTION_URI, 'SELECT * FROM users WHERE active = %s LIMIT %s', (True, 10))
for user in users:
print(f"{user['name']:20s} {user['email']}")
CI/CD Integration
def ci_pipeline(neon, project_id, pr_number):
branch_name = f"pr-{pr_number}"
# Create branch for this PR
result = neon.create_branch(project_id, branch_name)
branch_id = result['branch']['id']
uri = neon.get_connection_uri(project_id, branch_id)
print(f"DATABASE_URL={uri}")
print(f"Branch '{branch_name}' ready for CI tests")
# CI runs tests against this branch
# When PR is merged or closed:
# neon.delete_branch(project_id, branch_id)
return uri
ci_pipeline(neon, 'proj_xxx', 42)
Real-World Use Case
A startup used Neon branching in their CI/CD pipeline. Every pull request automatically gets a database branch with production data. Tests run against real data, not fixtures. When the PR is merged, the branch is deleted. They caught 3 migration bugs in the first week that would have caused production outages.
What You Can Build
- Dev environment manager with instant database copies
- Migration tester validating schema changes safely
- CI pipeline with real data for every PR
- Multi-tenant SaaS with project-per-tenant
- Analytics sandbox querying production data safely
Need custom database solutions? I build backends, data pipelines, and DevOps tools.
Email me: spinov001@gmail.com
Check out my developer tools: https://apify.com/spinov001
Top comments (0)