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
| Requirement | Starting point | You still own |
|---|---|---|
| PostgreSQL or MySQL business data | Bun.SQL | Schema, migrations, indexes, backups, and pool budgets |
| Local file database | Bun.SQL SQLite or bun:sqlite | WAL, concurrent writes, durable volumes, and backups |
| Cache, counters, short-lived state | RedisClient | TTLs, key design, degradation, and connection shutdown |
| Objects and large files | S3Client | Bucket policy, lifecycle, validation, and CDN behavior |
| Typed queries and migrations | Drizzle + Bun.SQL | ORM 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
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:
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.