Does Neon Work With Turso?
You can use Neon and Turso together, but they serve different purposes and require architectural decisions about which database owns which data.
Quick Facts
How Neon Works With Turso
Neon and Turso are fundamentally different database systems—Neon is a managed PostgreSQL service optimized for traditional relational workloads with branching and autoscaling, while Turso is an edge-distributed SQLite fork designed for ultra-low latency reads across regions. Using them together requires deciding which data lives where: typically, you'd use Neon for your authoritative transactional data (user accounts, orders, business logic) and Turso for read-heavy, latency-sensitive data that can tolerate eventual consistency (caching, analytics, feature flags, user preferences). This approach mirrors a primary-replica architecture but with explicit semantic separation. The developer experience involves managing two separate client libraries and connection strings, implementing sync logic via webhooks or background jobs, and handling consistency concerns. Most teams choose one or the other rather than both—Turso if you need global edge performance for reads, or Neon if you need ACID transactions and complex queries.
Best Use Cases
Dual Database Query Pattern
npm install @neondatabase/serverless libsqlimport { Pool } from '@neondatabase/serverless';
import { createClient } from 'libsql';
const neon = new Pool({ connectionString: process.env.DATABASE_URL });
const turso = createClient({ url: process.env.TURSO_URL, authToken: process.env.TURSO_AUTH_TOKEN });
// Transactional writes go to Neon
export async function createUser(email: string, name: string) {
const result = await neon.query(
'INSERT INTO users (email, name) VALUES ($1, $2) RETURNING id',
[email, name]
);
const userId = result.rows[0].id;
// Then replicate to Turso for edge reads
await turso.execute('INSERT INTO users_cache (id, email, name) VALUES (?, ?, ?)', [userId, email, name]);
return userId;
}
// Reads for global audiences hit Turso
export async function getUserName(userId: number) {
const result = await turso.execute('SELECT name FROM users_cache WHERE id = ?', [userId]);
return result.rows[0]?.name;
}
// Complex queries stay on Neon
export async function getUserStats(userId: number) {
const result = await neon.query(
'SELECT COUNT(*) as orders FROM orders WHERE user_id = $1',
[userId]
);
return result.rows[0];
}Known Issues & Gotchas
Data consistency between two separate databases requires explicit sync logic
Fix: Implement event-driven replication using Neon's logical replication or webhooks to push changes to Turso; accept eventual consistency or use distributed transactions (complex)
Turso's SQLite lacks advanced PostgreSQL features (no JSON operators, no complex joins, no stored procedures)
Fix: Design your schema around Turso's limitations; use Neon for complex queries and push simplified data to Turso
Connection pooling and authentication management doubles complexity with two systems
Fix: Use environment variables for both connection strings; consider a thin abstraction layer to switch databases per query
Cost multiplies with two managed services plus replication infrastructure
Fix: Carefully measure which data actually needs edge distribution; Turso's free tier may offset costs if used strategically
Alternatives
- •Neon alone with read replicas for regional performance (simpler, PostgreSQL-native)
- •Turso with a separate Postgres instance on Railway/Render for transactional needs
- •Supabase (PostgreSQL) + Vercel KV (Redis) for a similar primary-cache pattern with simpler tooling
Resources
Related Compatibility Guides
Explore more compatibility guides