Dev Encyclopedia
ArticlesToolsAbout

Get notified when new content drops

No spam. Just new articles, tools, and updates straight to your inbox.

Dev Encyclopedia

A reference for builders

Content

  • Articles
  • Tools
  • About
  • Contact

Connect

  • support@devencyclopedia.com
  • RSS Feed

Legal

  • Privacy Policy
  • Terms of Service
  • Disclaimer

© 2026 Dev Encyclopedia

Back to top ↑
  1. Home
  2. /Blog
  3. /Bun 1.3's Built-in SQL and Redis Clients: Do You Still Need pg, mysql2, and ioredis?
javascript18 min read

Bun 1.3's Built-in SQL and Redis Clients: Do You Still Need pg, mysql2, and ioredis?

Bun 1.3 ships built-in Postgres, MySQL, SQLite, and Redis clients. Side-by-side code vs pg, mysql2, and ioredis, plus when migrating actually makes sense.

Zeeshan Tofiq
Zeeshan Tofiq
June 26, 2026
On this page

On this page

  • What Shipped in Bun 1.3
  • Bun.SQL vs pg (PostgreSQL)
  • Bun.SQL vs mysql2 (MySQL)
  • Bun.SQL and SQLite
  • Bun.redis vs ioredis
  • The Migration Decision Framework
  • A Real Example: Dev/Prod Database Switching
  • Quick Reference Table
  • Frequently Asked Questions

Bun 1.3 shipped with something that no other JavaScript runtime has attempted: built-in clients for PostgreSQL, MySQL, SQLite, and Redis. Zero npm dependencies, zero native bindings, zero configuration files. You write Bun.sql or Bun.redis and it works. For developers who have been installing pg, mysql2, and ioredis for years, the obvious question is whether these built-in clients are actually ready to replace them.

This article walks through real code comparisons for each database, covers the genuine gaps that still exist, and gives you a clear framework for deciding whether to migrate now or wait. If you want to convert your existing code interactively, the Bun Snippet Converter tool does the syntax translation automatically.

For context on JavaScript runtimes and how Bun fits into the broader ecosystem, the Node.js interview questions guide covers the runtime fundamentals that carry across both Node.js and Bun.

What Shipped in Bun 1.3

Bun's database story started with bun:sqlite, a built-in SQLite binding that shipped in the early Bun releases and has been stable for over a year. Bun 1.2 added Bun.sql for PostgreSQL. Bun 1.3 (October 2025) completed the picture by adding MySQL/MariaDB support to Bun.sql and introducing Bun.redis for Redis.

All four databases now work through the same general approach: a tagged template literal API for SQL databases, and a method-based API for Redis. The API is designed to feel native to JavaScript rather than wrapping a C library's calling conventions.

ℹ What Bun.SQL covers

<strong>PostgreSQL</strong>, <strong>MySQL/MariaDB</strong>, and <strong>SQLite</strong> through one unified tagged-template API. <strong>Bun.redis</strong> covers standard Redis operations. All four are zero-dependency, zero-install built-ins that ship with the Bun runtime.

The key difference from the npm packages is not just the syntax. Bun's clients are written in Zig (not JavaScript wrapping a C binding), which means they can bypass the event loop overhead that Node.js clients pay for every database round-trip. Bun's own benchmarks claim 7.9x throughput over ioredis for some Redis operations, though real-world applications with typical network latency will see more modest gains.

Bun.SQL vs pg (PostgreSQL)

pg (node-postgres) has been the standard PostgreSQL client for Node.js for over a decade. It is battle-tested, supports every PostgreSQL feature, and has a massive ecosystem of tools built on top of it. Here is how the most common operations compare.

Connecting

typescript — pg (node-postgres)
import { Pool } from "pg";

const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
  max: 20,
});

await pool.connect();
typescript — Bun.SQL
const sql = Bun.sql(process.env.DATABASE_URL);
// Connection pooling is automatic
// Lazy connect on first query, no explicit .connect() needed

Bun.SQL eliminates the import, the pool size configuration (it auto-scales), and the explicit connect call. The connection is established lazily on the first query.

Parameterized queries

typescript — pg
const result = await pool.query(
  'SELECT * FROM users WHERE id = $1 AND role = $2',
  [userId, 'admin']
);
const users = result.rows;
typescript — Bun.SQL
const users = await sql`
  SELECT * FROM users WHERE id = ${userId} AND role = ${'admin'}
`;
// Returns the rows array directly, no .rows needed

The tagged template literal looks like string interpolation, but Bun parameterizes the values automatically. There is no SQL injection risk: the template literals are parsed at compile time, and the interpolated values are sent as bound parameters, never concatenated into the query string.

The other difference: pg returns { rows, fields, rowCount }. Bun.SQL returns the rows array directly. If you need the row count, you can check .length on the result.

Transactions

typescript — pg
const client = await pool.connect();
try {
  await client.query('BEGIN');
  await client.query(
    'UPDATE accounts SET balance = balance - $1 WHERE id = $2',
    [amount, fromAccount]
  );
  await client.query(
    'UPDATE accounts SET balance = balance + $1 WHERE id = $2',
    [amount, toAccount]
  );
  await client.query('COMMIT');
} catch (e) {
  await client.query('ROLLBACK');
  throw e;
} finally {
  client.release();
}
typescript — Bun.SQL
await sql.begin(async (tx) => {
  await tx`
    UPDATE accounts SET balance = balance - ${amount} WHERE id = ${fromAccount}
  `;
  await tx`
    UPDATE accounts SET balance = balance + ${amount} WHERE id = ${toAccount}
  `;
});
// Auto-commits on success, auto-rolls back on exception

The pg version requires manual BEGIN/COMMIT/ROLLBACK, a try/catch/finally block, and explicit client release. Bun.SQL wraps all of this into a single sql.begin() call. If the callback throws, the transaction rolls back automatically. If it succeeds, it commits automatically.

💡 Tip

Want to convert your pg code interactively? Try the Bun Snippet Converter to see the transformation applied to your own queries.

Bun.SQL vs mysql2 (MySQL)

mysql2 is the standard MySQL client for Node.js, replacing the older mysql package with Promise support and better performance. Bun.SQL's MySQL support was the newest addition in 1.3, so it is slightly less battle-tested than the PostgreSQL side.

Connecting

typescript — mysql2
import mysql from "mysql2/promise";

const connection = await mysql.createConnection({
  host: "localhost",
  user: "root",
  password: "secret",
  database: "myapp",
});
typescript — Bun.SQL
const sql = Bun.sql({
  host: "localhost",
  user: "root",
  password: "secret",
  database: "myapp",
});

Parameterized queries

typescript — mysql2
const [rows] = await connection.execute(
  'SELECT * FROM products WHERE category = ? AND price < ?',
  [category, maxPrice]
);
typescript — Bun.SQL
const rows = await sql`
  SELECT * FROM products WHERE category = ${category} AND price < ${maxPrice}
`;

mysql2 uses ? as the placeholder character and returns a [rows, fields] tuple that you destructure. Bun.SQL uses the same tagged template syntax regardless of whether you are connecting to PostgreSQL or MySQL, and returns the rows directly.

This unified syntax is one of Bun.SQL's strongest design decisions: if you switch from MySQL to PostgreSQL (or vice versa), your query code does not change at all. Only the connection configuration changes.

What you might miss

  • <strong>Prepared statement caching.</strong> mysql2 has built-in prepared statement caching with .prepare(). Bun.SQL does not expose this directly yet.
  • <strong>Connection pool events.</strong> mysql2's pool emits events for acquire, release, and enqueue that some monitoring setups depend on.
  • <strong>Multiple result sets.</strong> Stored procedures that return multiple result sets work natively in mysql2 but are not yet documented for Bun.SQL's MySQL mode.

Bun.SQL and SQLite

SQLite is the most mature of Bun's database integrations. bun:sqlite has been available since Bun's early releases and is widely used for local development, testing, and lightweight production deployments.

typescript — bun:sqlite
import { Database } from "bun:sqlite";

const db = new Database("app.db");

const users = db.query("SELECT * FROM users WHERE role = ?").all("admin");

// Transactions
const insertMany = db.transaction((items) => {
  const insert = db.prepare("INSERT INTO logs (message) VALUES (?)");
  for (const item of items) {
    insert.run(item);
  }
});

insertMany(["event-a", "event-b", "event-c"]);

Unlike the PostgreSQL and MySQL clients, bun:sqlite uses a synchronous API because SQLite itself is synchronous (it reads and writes directly to a local file). This makes it extremely fast for local operations but means you cannot use the same tagged template syntax as Bun.sql.

A popular pattern for full-stack Bun applications: use bun:sqlite for local development and testing, then switch to PostgreSQL or MySQL in production. Since the SQL itself is standard, the migration usually requires only changing the connection setup, not rewriting queries.

Bun.redis vs ioredis

ioredis has been the go-to Redis client for Node.js since 2015, offering cluster support, Lua scripting, pub/sub, and pipelining. Bun.redis is a newer, leaner alternative that covers the most common Redis operations with a cleaner API.

Basic commands

typescript — ioredis
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL);

await redis.set("session:abc", JSON.stringify(data), "EX", 3600);
const cached = await redis.get("session:abc");

await redis.del("session:abc");
await redis.quit();
typescript — Bun.redis
const redis = Bun.redis();

await redis.set("session:abc", JSON.stringify(data), { ex: 3600 });
const cached = await redis.get("session:abc");

await redis.del("session:abc");
// No .quit() needed, Bun manages lifecycle

The biggest syntax difference is how Redis command options are passed. ioredis uses positional string arguments: "EX", 3600, "NX". Bun.redis uses an options object: { ex: 3600, nx: true }. The options object is less error-prone because the TypeScript compiler catches typos and wrong types, while positional strings silently pass invalid flags to Redis.

Pub/Sub

typescript — ioredis
const sub = new Redis(process.env.REDIS_URL);

sub.subscribe("notifications", (err) => {
  if (err) console.error(err);
});

sub.on("message", (channel, message) => {
  console.log(`Received on ${channel}: ${message}`);
});
typescript — Bun.redis
const redis = Bun.redis();

const sub = redis.subscribe("notifications");
for await (const message of sub) {
  console.log(`Received: ${message}`);
}

ioredis uses an event-emitter pattern for pub/sub, which requires a dedicated Redis connection (you cannot run commands on a client in subscribe mode). Bun.redis uses an async iterator, which is more idiomatic for modern JavaScript and integrates naturally with for await...of loops.

Performance claims

Bun's own benchmarks claim 7.9x higher throughput than ioredis for SET operations and similar improvements for GET. These numbers come from running both clients in a tight loop against a local Redis instance, which measures the client overhead in isolation.

In a real application with network latency, serialization, and business logic between Redis calls, the client overhead is a small fraction of total request time. You will likely see 10 to 30 percent improvement in Redis-heavy code paths, not 7.9x. The improvement is real but should not be the primary reason to migrate.

What ioredis has that Bun.redis does not (yet)

  • <strong>Cluster support.</strong> ioredis has built-in Redis Cluster support with automatic slot-based routing. Bun.redis does not support clusters yet. If you use Redis Cluster in production, this is a hard blocker.
  • <strong>Lua scripting.</strong> ioredis supports .defineCommand() and .eval() for running Lua scripts on the server. Bun.redis does not expose Lua scripting.
  • <strong>Sentinel support.</strong> ioredis connects to Redis Sentinel for high availability failover. Bun.redis requires a direct connection URL.
  • <strong>Offline queue.</strong> ioredis buffers commands while reconnecting and replays them automatically. Bun.redis's reconnection behavior is simpler.

The Migration Decision Framework

Not every project should migrate immediately. Here is a practical framework for deciding.

Migrate now if

  • You are starting a new project on Bun and do not need any npm package ecosystem features
  • Your application uses simple CRUD queries (SELECT, INSERT, UPDATE, DELETE) without advanced database features
  • You want to reduce your dependency count (pg alone pulls in 3 transitive dependencies, mysql2 pulls in 8)
  • Your Redis usage is limited to GET/SET/DEL and basic pub/sub (no clusters, no Lua)
  • You are building a prototype or internal tool where ecosystem maturity is less critical

Wait if

  • You rely on Redis Cluster or Sentinel for high availability
  • Your application uses Lua scripting on Redis
  • You depend on pg extensions like PostGIS, pg-cursor, or pg-copy-streams
  • Your team uses an ORM (Prisma, Drizzle, TypeORM) that has not added Bun.SQL adapter support
  • Your APM/monitoring tooling (Datadog, New Relic) instruments pg/ioredis specifically and does not support Bun's built-ins yet
  • You are running on Node.js and do not plan to migrate the runtime itself

⚠ Runtime lock-in

Bun.SQL and Bun.redis only work in the Bun runtime. If your application also needs to run on Node.js (for deployment flexibility, for CI that uses Node, or for team members who have not adopted Bun), the npm packages remain the only option. This is the single biggest factor in the decision.

A Real Example: Dev/Prod Database Switching

A common pattern in Bun full-stack applications: use SQLite locally for fast, zero-config development, then switch to PostgreSQL or MySQL in production. Here is how to implement this with a single DATABASE_URL environment variable.

typescript — db.ts
import { Database } from "bun:sqlite";

function createDb() {
  const url = process.env.DATABASE_URL ?? "sqlite://app.db";

  if (url.startsWith("sqlite://")) {
    const path = url.replace("sqlite://", "");
    return new Database(path);
  }

  // PostgreSQL or MySQL: Bun.sql auto-detects from the URL scheme
  return Bun.sql(url);
}

export const db = createDb();

In development, you run without DATABASE_URL set, and the app falls back to a local SQLite file. In production, you set DATABASE_URL=postgres://... or DATABASE_URL=mysql://..., and Bun.SQL detects the database type from the URL scheme automatically.

The caveat: your SQL must be compatible across both databases. Standard CRUD works everywhere, but database-specific features (PostgreSQL's RETURNING clause, MySQL's ON DUPLICATE KEY UPDATE) will not work in SQLite. For most applications, this is a non-issue because the dev environment only needs basic read/write functionality.

Quick Reference Table

Side-by-side command equivalents for the most common operations.

SQL operations

pg and mysql2 operations mapped to their Bun.SQL equivalents
Operationpg / mysql2Bun.SQL
Connectnew Pool(url) / createConnection(opts)Bun.sql(url)
Simple querypool.query('SELECT 1')sql`SELECT 1`
Parameterized (pg)query('...WHERE id=$1', [id])sql`...WHERE id=${id}`
Parameterized (mysql2)execute('...WHERE id=?', [id])sql`...WHERE id=${id}`
Insertquery('INSERT INTO t (a) VALUES ($1)', [v])sql`INSERT INTO t (a) VALUES (${v})`
Transaction startquery('BEGIN')sql.begin(async (tx) => { ... })
Transaction commitquery('COMMIT')(automatic on callback return)
Transaction rollbackquery('ROLLBACK') in catch(automatic on exception)
Closepool.end() / connection.end()(automatic, no call needed)
Result format{ rows: [...] } / [rows, fields]rows array directly

Redis operations

ioredis operations mapped to their Bun.redis equivalents
OperationioredisBun.redis
Connectnew Redis(url)Bun.redis()
GETredis.get(key)redis.get(key)
SETredis.set(key, val)redis.set(key, val)
SET with expiryredis.set(key, val, 'EX', 60)redis.set(key, val, { ex: 60 })
SET if not existsredis.set(key, val, 'EX', 30, 'NX')redis.set(key, val, { ex: 30, nx: true })
DELredis.del(key)redis.del(key)
HSETredis.hset(key, obj)redis.hset(key, obj)
HGETredis.hget(key, field)redis.hget(key, field)
Subscribesub.on('message', cb)for await (const msg of sub) { }
Disconnectredis.quit()(automatic, no call needed)

Frequently Asked Questions

Is Bun.SQL production-ready?

The PostgreSQL client is the most mature and is used in production by several companies (including Bun's own infrastructure). The MySQL client shipped in Bun 1.3 and is newer, so edge cases may still surface. SQLite support via bun:sqlite has been stable since early Bun releases.

For standard CRUD workloads (SELECT, INSERT, UPDATE, DELETE, transactions), Bun.SQL is production-ready. For workloads that depend on advanced pg features (cursors, LISTEN/NOTIFY, COPY streams), the npm packages are still the safer choice.

Can I still use pg, mysql2, and ioredis in Bun?

Yes. Bun maintains Node.js compatibility, so npm packages work alongside the built-in clients. You can migrate incrementally: start by using Bun.SQL for new queries while keeping existing pg/mysql2 code in place. There is no all-or-nothing requirement.

Does the tagged template syntax prevent SQL injection?

Yes. Tagged template literals in Bun.SQL are not string concatenation. The template is parsed at compile time into a static SQL string with parameter placeholders, and the interpolated values are sent as bound parameters using the database's native parameterized query protocol. This is the same level of protection as pg's $1 placeholders or mysql2's ? placeholders.

Does Bun.SQL work with Prisma, Drizzle, or TypeORM?

Not directly. ORMs generate their own SQL and manage their own database connections through their own driver adapters. Drizzle has a community adapter in progress. Prisma and TypeORM do not have official Bun.SQL adapters yet. If you use an ORM, the ORM manages the connection, and you would not see a benefit from Bun.SQL unless the ORM adds adapter support.

How much faster is Bun.SQL compared to pg?

Bun's own benchmarks show significant throughput improvements in synthetic tests (tight loops against a local database). In real applications with network latency and business logic, expect 10 to 30 percent improvement in database-heavy code paths. The improvement comes from Bun's Zig-based client bypassing the V8 event loop overhead that pg pays on every round-trip.

Performance should not be the primary reason to migrate. The real benefits are reduced dependency count, simpler API (no .rows, no manual transactions), and a unified syntax across PostgreSQL and MySQL.

Does Bun.redis support Redis Cluster?

No. As of Bun 1.3, Bun.redis only supports standalone Redis instances. If your production setup uses Redis Cluster (sharding across multiple nodes), you must continue using ioredis, which has built-in cluster support with automatic slot-based routing. Cluster support is on Bun's roadmap but has no announced timeline.

Is there a tool to convert my existing pg/mysql2/ioredis code?

Yes. The Bun Snippet Converter on this site lets you paste your existing code and get the Bun.SQL or Bun.redis equivalent instantly. It handles parameterized queries, transactions, Redis options, and connection setup. It runs entirely in your browser with no data sent to any server.

Where can I learn more about Redis data structures and patterns?

The 42 NoSQL Interview Questions guide covers Redis data structures (strings, hashes, sorted sets, streams), caching patterns, and when to use Redis versus other NoSQL databases. It applies regardless of whether you use ioredis or Bun.redis.

Zeeshan Tofiq

Zeeshan Tofiq

Full Stack Developer

Full stack developer with over 6 years of experience building production applications. Writes practical guides on JavaScript, TypeScript, React, Node.js, and cloud infrastructure. Focused on helping developers solve real problems with clean, maintainable code.

Enjoyed this article?

Get practical dev guides, tool updates, and new articles delivered straight to your inbox. No spam, unsubscribe anytime.

Related Articles

databases

42 NoSQL Database Interview Questions and Answers (2026)

42 NoSQL interview questions covering MongoDB, Redis, and DynamoDB: aggregation pipelines, data structures, GSI vs LSI, and CAP theorem. Updated for 2026.

Jun 10, 2026·37 min read
nodejs

30 Node.js Interview Questions and Answers (2026)

30 Node.js interview questions with full answers: event loop, streams, clustering, worker threads, memory leaks, and security. Updated for 2026.

Jun 8, 2026·26 min read

On this page

  • What Shipped in Bun 1.3
  • Bun.SQL vs pg (PostgreSQL)
  • Bun.SQL vs mysql2 (MySQL)
  • Bun.SQL and SQLite
  • Bun.redis vs ioredis
  • The Migration Decision Framework
  • A Real Example: Dev/Prod Database Switching
  • Quick Reference Table
  • Frequently Asked Questions
Advertisement