Web Development · Backend
Bun 1.3's Built-In SQL and Redis Clients: When to Drop pg and ioredis
Bun 1.3 ships Bun.sql, a unified client for Postgres, MySQL, MariaDB, and SQLite, plus a native Redis client Bun claims is 7.9x faster than ioredis. Here's the real API, working code, and when it's actually worth dropping your existing driver.
Abhishek Gupta
5 min read
Sponsored
Bun 1.3 quietly removes two dependencies most Bun projects have been carrying since day one: pg (or postgres) for SQL, and ioredis for Redis. Both now ship built into the runtime, with no install step. The SQL client works across four database engines through one API. The Redis client, per Bun’s own benchmark, is meaningfully faster than the JavaScript client most Node and Bun projects have defaulted to for years. Neither replaces the ecosystem tools outright, but both are worth knowing before you reach for npm install out of habit.
Bun.sql: one API, four databases
Bun.sql (and its exported SQL class) gives you a tagged-template query interface that works the same way regardless of which database sits behind it:
import { sql } from "bun";
// Postgres, using the default connection from BUN's env / config
const activeUsers = await sql`
SELECT * FROM users WHERE active = ${true} LIMIT ${10}
`;
Pointing the same syntax at a different engine is a matter of constructing a client against a different connection string, not learning a new query builder:
import { SQL } from "bun";
const sqlite = new SQL("sqlite://myapp.db");
const sqliteUsers = await sqlite`SELECT * FROM users WHERE active = ${1}`;
const mysql = new SQL("mysql://user:pass@localhost:3306/mydb");
const mysqlUsers = await mysql`SELECT * FROM users WHERE active = ${true}`;
Inserts and transactions follow the same tagged-template pattern:
const [created] = await sql`
INSERT INTO users (name, email)
VALUES (${name}, ${email})
RETURNING *
`;
await sql.transaction(async (tx) => {
await tx`UPDATE accounts SET balance = balance - ${amount} WHERE id = ${from}`;
await tx`UPDATE accounts SET balance = balance + ${amount} WHERE id = ${to}`;
});
The values inside ${} are parameterized automatically, the same protection pg’s parameterized queries give you, not raw string interpolation. That matters because tagged templates look, at a glance, like the kind of string-building that causes SQL injection; here, the interpolation is intercepted and bound as a parameter before the query reaches the database.
The practical win is consistency. A project that runs Postgres in production and SQLite in tests, or that’s migrating from MySQL to Postgres, keeps the same query syntax through the whole transition. That’s a real reduction in code to maintain, even before performance enters the conversation.
Bun.redis: a native client, not a wrapped package
The Redis client ships the same way, built in rather than installed:
import { redis } from "bun";
await redis.set("greeting", "Hello from Bun!");
const greeting = await redis.get("greeting");
console.log(greeting); // "Hello from Bun!"
For anything beyond the default connection, RedisClient takes a connection URL directly:
import { RedisClient } from "bun";
const client = new RedisClient("redis://username:password@localhost:6379");
await client.set("session:abc123", JSON.stringify({ userId: 42 }));
It covers the 66 commands most applications actually use: strings, hashes (HSET/HGET), lists (LPUSH/LRANGE), and sets, with automatic reconnection (including exponential backoff) and command pipelining, sending multiple commands without waiting on each reply individually, handled by the client rather than left to the caller. Cluster support, Redis Streams, and Lua scripting are listed as in progress, so a project leaning on those today still needs ioredis.
The performance claim, and how to read it
Bun’s own published benchmark for the 1.3 release puts the native Redis client at more than 7.9x the throughput of ioredis on standard operations. The architectural reason is straightforward: ioredis is a JavaScript client built on Node’s networking stack, while Bun’s client is implemented natively in Zig, speaking the RESP3 protocol directly without a JavaScript-level abstraction layer in between.
Source: Bun 1.3 release blog
A vendor’s own multiplier is a starting point, not a guarantee. It tells you the architecture has real headroom over a wrapped JS client, not what your specific access pattern will see. A service issuing a handful of cache reads per request will not feel a 7.9x difference in end-to-end latency, most of which is dominated by other work. A service doing thousands of Redis operations per second, session storage at scale, a real-time leaderboard, rate limiting on a busy API, is exactly the workload where this kind of native-client speedup compounds into something you’d notice on a dashboard.
When to actually make the switch
The honest tradeoff isn’t speed, it’s portability. Bun.sql and Bun.redis are Bun-specific APIs. Code written against them doesn’t run on Node or Deno without a shim. That’s a real constraint if you’re maintaining a shared package consumed by services on different runtimes, or if there’s a live possibility your team moves off Bun later.
If you’re building on Bun with no near-term plan to change that, and especially if Redis throughput shows up as a bottleneck in profiling, both built-ins are a reasonable default: fewer dependencies, one consistent SQL syntax across engines if you use more than one database, and a Redis client with real architectural headroom over the JS-native option. If you’re writing a library meant to run anywhere, or your team’s runtime choice isn’t fully settled the way we discuss when choosing a stack for a new project, stick with pg and ioredis for now. They work everywhere, which is worth more than a throughput multiplier you may never fully realize in practice.
Frequently asked questions
- What is Bun.sql and which databases does it support?
- Bun.sql is a built-in, dependency-free SQL client shipped in Bun 1.3. It provides one consistent tagged-template query API across PostgreSQL, MySQL, MariaDB, and SQLite, so switching the underlying database is mostly a matter of changing the connection string rather than swapping libraries and rewriting query code.
- How much faster is Bun's Redis client than ioredis, really?
- Bun's own published benchmark claims more than 7.9x the throughput of ioredis on standard operations, attributed to a native Zig implementation talking RESP3 directly rather than a JavaScript client wrapping node's networking stack. Treat vendor-published multipliers as a directional signal, not a guarantee your specific workload will see the same ratio, and benchmark your own hot path before treating it as a reason to migrate a production service.
- Are Bun's SQL and Redis clients safe from SQL injection?
- The tagged-template syntax (sql`SELECT * FROM users WHERE id = ${id}`) parameterizes values automatically rather than interpolating raw strings, which is the same protection you get from parameterized queries in pg or prepared statements elsewhere. Naive string concatenation with backticks and no interpolation would still be unsafe; using the interpolation syntax as intended is what provides the protection.
- Should I migrate an existing pg or ioredis codebase to Bun's built-ins?
- Only if you're already committed to Bun as your runtime and don't need portability to Node or Deno. The built-in clients remove a dependency and, per Bun's benchmarks, add real performance for Redis-heavy workloads, but they're Bun-specific APIs. A library, a shared package used across services on different runtimes, or a team that might switch runtimes later should stick with pg and ioredis, which work everywhere.
Sources
Sponsored
More from this category
More from Web Development
R.01 MySQL Now Has a Native VECTOR Type. Should You Drop pgvector For It?
R.02 React 19.2 vs 'React 20': There Is No React 20, and Here's What's Actually New
R.03 Next.js's First Scheduled Security Release Shipped: 9 Advisories, What to Patch
Sponsored
Discussion
Join the conversation.
Comments are powered by GitHub Discussions. Sign in with your GitHub account to leave a comment.
Sponsored