Serverless Databases
Expert guidance for using serverless databases like PlanetScale, Neon, and Turso in serverless applications
You are an expert in serverless databases (PlanetScale, Neon, Turso) for building serverless applications. You help teams choose the right serverless database for their consistency, latency, and compatibility requirements, and configure connection strategies that work correctly in ephemeral compute environments. ## Key Points - Use the HTTP/fetch-based query drivers (not TCP connection pools) for short-lived serverless functions — they avoid connection overhead and work in edge runtimes that lack TCP socket support. - Adopt database branching (Neon, PlanetScale) to give every preview deployment its own isolated database — run migrations safely without affecting production. - Opening a new TCP connection on every Lambda invocation exhausts connection limits fast — use HTTP-based drivers or external poolers (PgBouncer, Neon's built-in pooler) in serverless contexts. ## Quick Example ```bash npm install @neondatabase/serverless ``` ```bash npm install @planetscale/database ```
skilldb get serverless-skills/Serverless DatabasesFull skill: 177 linesServerless Databases — Serverless
You are an expert in serverless databases (PlanetScale, Neon, Turso) for building serverless applications. You help teams choose the right serverless database for their consistency, latency, and compatibility requirements, and configure connection strategies that work correctly in ephemeral compute environments.
Core Philosophy
The fundamental problem serverless databases solve is the impedance mismatch between stateless, ephemeral compute and stateful, persistent databases. Traditional databases expect long-lived TCP connections from a stable pool of application servers; serverless functions spin up thousands of short-lived instances that each try to open their own connection. Serverless databases address this with HTTP-based query protocols, built-in connection pooling, and scale-to-zero pricing that eliminates idle costs. Choosing the right approach to connectivity — HTTP driver vs. pooled TCP vs. direct connection — is the single most important decision.
Data locality determines user-perceived latency. A serverless function running at the edge that queries a database in a single region pays the round-trip latency on every query, negating the edge advantage. Match your database topology to your compute topology: Turso's embedded replicas for sub-millisecond edge reads, Neon's read replicas for regional distribution, or PlanetScale's Vitess-based sharding for horizontal scale. The database's replication model and consistency guarantees should drive the architectural decision, not just the SQL dialect.
Database branching is a workflow revolution for teams using serverless databases with preview deployments. Every pull request gets its own isolated database branch with production schema and optionally seeded data, making it safe to run migrations, test schema changes, and review data-dependent features without touching production. Adopt branching as a core part of the development workflow, not an afterthought.
Anti-Patterns
- Opening a new TCP connection per function invocation — Serverless functions can scale to thousands of concurrent instances. Each one opening a direct TCP connection quickly exhausts the database's connection limit. Use HTTP-based drivers or external connection poolers (PgBouncer, Neon's pooler) for serverless workloads.
- Using connection pools inside short-lived functions — A connection pool inside a Lambda function that runs for 200 ms and then freezes wastes connections that sit idle in the frozen execution environment. Use HTTP/fetch-based drivers for short-lived functions; reserve pooled connections for long-running servers.
- Assuming foreign key support on PlanetScale — PlanetScale uses Vitess, which does not support database-level foreign key constraints. Relying on foreign keys and discovering this after migration causes data integrity issues. Evaluate this constraint early and enforce referential integrity in the application layer if using PlanetScale.
- Ignoring consistency models — Neon read replicas and Turso edge replicas provide eventual consistency. Reading your own writes immediately after a write may return stale data unless you route read-after-write queries to the primary. Understand the replication lag and design read paths accordingly.
- Skipping database branching in CI/CD — Running migrations directly against production or a shared staging database is risky and blocks parallel development. With Neon and PlanetScale offering zero-cost branching, there is no reason not to give every preview deployment its own isolated database.
Overview
Traditional databases struggle in serverless environments due to connection limits and idle costs. Serverless databases solve these problems with HTTP-based query protocols, connection pooling, branching workflows, and scale-to-zero pricing. PlanetScale offers MySQL-compatible Vitess, Neon provides serverless Postgres with branching, and Turso delivers embedded SQLite at the edge via libSQL.
Setup & Configuration
Neon (Serverless Postgres)
npm install @neondatabase/serverless
import { neon } from '@neondatabase/serverless';
const sql = neon(process.env.DATABASE_URL!);
// Simple query over HTTP — no persistent connection needed
const users = await sql`SELECT * FROM users WHERE active = true`;
// Parameterized query
const user = await sql`SELECT * FROM users WHERE id = ${userId}`;
PlanetScale (Serverless MySQL)
npm install @planetscale/database
import { connect } from '@planetscale/database';
const conn = connect({
host: process.env.DATABASE_HOST,
username: process.env.DATABASE_USERNAME,
password: process.env.DATABASE_PASSWORD,
});
const results = await conn.execute('SELECT * FROM products WHERE category = ?', [category]);
Turso (Edge SQLite via libSQL)
npm install @libsql/client
import { createClient } from '@libsql/client';
const db = createClient({
url: process.env.TURSO_DATABASE_URL!,
authToken: process.env.TURSO_AUTH_TOKEN,
});
const result = await db.execute({
sql: 'SELECT * FROM posts WHERE author_id = ?',
args: [authorId],
});
Using Drizzle ORM across providers
// drizzle.config.ts — works with Neon, PlanetScale, or Turso
import { defineConfig } from 'drizzle-kit';
export default defineConfig({
schema: './src/db/schema.ts',
out: './drizzle',
dialect: 'postgresql', // or 'mysql' for PlanetScale, 'sqlite' for Turso
dbCredentials: {
url: process.env.DATABASE_URL!,
},
});
// src/db/schema.ts
import { pgTable, serial, text, timestamp } from 'drizzle-orm/pg-core';
export const users = pgTable('users', {
id: serial('id').primaryKey(),
name: text('name').notNull(),
email: text('email').notNull().unique(),
createdAt: timestamp('created_at').defaultNow(),
});
Core Patterns
Connection pooling with Neon
import { Pool } from '@neondatabase/serverless';
// Use Pool when you need transactions or multiple queries in sequence
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const client = await pool.connect();
try {
await client.query('BEGIN');
await client.query('UPDATE accounts SET balance = balance - $1 WHERE id = $2', [amount, fromId]);
await client.query('UPDATE accounts SET balance = balance + $1 WHERE id = $2', [amount, toId]);
await client.query('COMMIT');
} catch (e) {
await client.query('ROLLBACK');
throw e;
} finally {
client.release();
}
Database branching for preview deployments (Neon)
# Create a branch from production for a pull request
neonctl branches create --name pr-42 --parent main
# Each preview deployment gets its own isolated database copy
# Set DATABASE_URL in the preview environment to the branch's connection string
Turso embedded replicas for edge reads
import { createClient } from '@libsql/client';
const db = createClient({
url: 'file:local-replica.db', // local embedded replica
syncUrl: process.env.TURSO_DATABASE_URL, // remote primary
authToken: process.env.TURSO_AUTH_TOKEN,
syncInterval: 60, // sync every 60 seconds
});
// Reads from local replica — sub-millisecond latency
const posts = await db.execute('SELECT * FROM posts ORDER BY created_at DESC LIMIT 20');
Best Practices
- Use the HTTP/fetch-based query drivers (not TCP connection pools) for short-lived serverless functions — they avoid connection overhead and work in edge runtimes that lack TCP socket support.
- Adopt database branching (Neon, PlanetScale) to give every preview deployment its own isolated database — run migrations safely without affecting production.
- Choose the database that matches your latency requirements: Turso for globally distributed reads at the edge, Neon for Postgres compatibility with branching, PlanetScale for MySQL compatibility with horizontal sharding.
Common Pitfalls
- Opening a new TCP connection on every Lambda invocation exhausts connection limits fast — use HTTP-based drivers or external poolers (PgBouncer, Neon's built-in pooler) in serverless contexts.
- PlanetScale does not support foreign key constraints at the database level (Vitess limitation) — enforce referential integrity in your application layer or use Neon/Turso if foreign keys are critical.
Install this skill directly: skilldb add serverless-skills
Related Skills
AWS Lambda
Expert guidance for building, deploying, and optimizing AWS Lambda functions
AWS Step Functions
Expert guidance for orchestrating serverless workflows with AWS Step Functions
Cloudflare Workers
Expert guidance for building and deploying applications on Cloudflare Workers at the edge
Cold Start Optimization
Expert guidance for mitigating and optimizing cold start latency in serverless functions
Event Triggers
Expert guidance for building event-driven serverless architectures with S3, SQS, and EventBridge triggers
Serverless Testing
Expert guidance for testing serverless applications locally and in CI/CD pipelines