Core toolchainData and storage

Data and storage

Choose Bun.SQL, SQLite, Redis, S3, and Drizzle while managing connections and production boundaries

Last updated on

Last verified: 2026-08-02 with Bun 1.3.14. Recheck changing driver capabilities and server requirements during upgrades.

Choose the data boundary first

RequirementStarting pointYou still own
PostgreSQL or MySQL business dataBun.SQLSchema, migrations, indexes, backups, and pool budgets
Local file databaseBun.SQL SQLite or bun:sqliteWAL, concurrent writes, durable volumes, and backups
Cache, counters, short-lived stateRedisClientTTLs, key design, degradation, and connection shutdown
Objects and large filesS3ClientBucket policy, lifecycle, validation, and CDN behavior
Typed queries and migrationsDrizzle + Bun.SQLORM compatibility and migration releases

An included client does not make schema evolution, data modeling, or managed-service operations the runtime's responsibility.

A minimal safe Bun.SQL query

src/users.ts
import { SQL } from 'bun';

const databaseUrl = Bun.env.DATABASE_URL;
if (!databaseUrl) throw new Error('DATABASE_URL is required');

const database = new SQL(databaseUrl, {
  max: 10,
  connectionTimeout: 10,
  idleTimeout: 30,
});

export async function findActiveUser(email: string) {
  const rows = await database`
    SELECT id, email
    FROM users
    WHERE email = ${email} AND active = ${true}
    LIMIT 1
  `;

  return rows[0] ?? null;
}

export function closeDatabase() {
  return database.close();
}

Interpolated values are parameterized. Do not insert unvalidated user input as table names, column names, or sort directions. max is a per-process pool limit, so multiply it by the maximum replica count when budgeting database connections.

One API does not erase database differences

MySQL does not have PostgreSQL arrays or identical RETURNING behavior. SQLite has different concurrency and typing. Run integration tests against the production database engine.

Bun.SQL's PostgreSQL client still lacks some capabilities, including COPY, LISTEN, NOTIFY, GSSAPI, and some PostGIS types. Keep a mature driver when the application depends on them.

What Drizzle adds

Drizzle can add schema definitions, query construction, relations, and migration tooling over Bun.SQL:

src/database/index.ts
import { SQL } from 'bun';
import { drizzle } from 'drizzle-orm/bun-sql';

const connectionString = Bun.env.DATABASE_URL;
if (!connectionString) throw new Error('DATABASE_URL is required');

const client = new SQL(connectionString);
export const database = drizzle({ client });

At this review date, Drizzle's Bun.SQL guide still shows RC package installation commands. Verify Drizzle, Drizzle Kit, Bun, and database versions together. Run migrations in one release step, not independently in every service replica.

Redis lifecycle

Bun's native Redis client currently supports Redis 7.2 and later:

import { RedisClient } from 'bun';

const redisUrl = Bun.env.REDIS_URL;
if (!redisUrl) throw new Error('REDIS_URL is required');

const cache = new RedisClient(redisUrl);

await cache.set('health:last-ok', new Date().toISOString());
const lastOk = await cache.get('health:last-ok');

cache.close();

The business must decide whether cache failure is fatal, degraded, or bypassed. Do not use a cache without a durability contract as the only source of truth, and do not put secrets or full prompts into keys or logs.

S3-compatible object storage

import { S3Client } from 'bun';

const storage = new S3Client({
  endpoint: Bun.env.S3_ENDPOINT,
  accessKeyId: Bun.env.S3_ACCESS_KEY_ID,
  secretAccessKey: Bun.env.S3_SECRET_ACCESS_KEY,
  bucket: Bun.env.S3_BUCKET,
});

await storage.file('reports/latest.json').write(JSON.stringify({ ok: true }));

S3Client works with AWS S3, Cloudflare R2, MinIO, and compatible services, but signing, regions, endpoints, public access, and lifecycle rules remain provider-specific. Limit upload size and type. Prefer short-lived authorization for sensitive downloads instead of a public bucket.

Production definition of done

  • Secrets come only from the runtime or a secret manager.
  • Query values are parameterized and dynamic identifiers use an allowlist.
  • Maximum replicas multiplied by pool size fit the database connection limit.
  • Migrations are reviewable, reversible, and run exactly once.
  • Integration tests use the target database version.
  • SIGTERM closes SQL, Redis, and other long-lived connections.
  • Backup, restore, and object lifecycle procedures have been exercised.

Official references: Bun.SQL, Redis, S3, and Drizzle Bun.SQL.