SQL - BunDocumentation Index Search...⌘KInstall Bun Search...Navigation Data & Storage SQLRuntimePackage ManagerBundlerTest RunnerGuidesReferenceBlogFeedback:first-child]:!hidden peer-[.is-custom]:[&>:first-child]:sm:!hidden peer-[.is-custom]:[&>:first-child]:md:!hidden peer-[.is-custom]:[&>:first-child]:lg:!hidden peer-[.is-custom]:[&>:first-child]:xl:!hidden">Get StartedWelcome to BunInstallationQuickstartTypeScriptTypeScript 6 and 7bun initbun createCore RuntimeBun RuntimeWatch ModeDebuggingREPLbunfig.tomlFile & Module SystemFile TypesModule ResolutionJSXAuto-installPluginsFile System RouterHTTP serverServerRoutingCookiesTLSError HandlingMetricsNetworkingFetchWebSocketsTCPUDPDNSData & StorageCookiesFile I/OStreamsBinary DataArchiveSQLSQLiteS3RedisConcurrencyWorkersProcess & SystemEnvironment VariablesShellSpawnWebViewCronInterop & ToolingNode-APIFFIC CompilerTranspilerUtilitiesCSRF ProtectionSecretsConsoleTOMLYAMLMarkdownJSON5JSONLHTMLRewriterImageHashingGlobSemverColorUtilsStandards & CompatibilityGlobalsBun APIsWeb APIsNode.js CompatibilityContributingRoadmapBenchmarkingContributingBuilding WindowsBindgenLicense On this pageFeaturesDatabase SupportPostgreSQLMySQLSQLiteInserting dataBulk InsertPicking columns to insertQuery Resultssql``.values() formatsql``.raw() formatSQL FragmentsDynamic Table NamesConditional QueriesDynamic columns in updatesDynamic values and where insql.array helpersql``.simple()Queries in filesUnsafe QueriesExecute and Cancelling QueriesDatabase Environment VariablesAutomatic Database DetectionMySQL Auto-DetectionSQLite Auto-DetectionPostgreSQL Auto-DetectionMySQL Environment VariablesPostgreSQL Environment VariablesSQLite Environment VariablesRuntime PreconnectionConnection OptionsMySQL OptionsPostgreSQL OptionsSQLite OptionsDynamic passwordsSQLite-Specific FeaturesQuery ExecutionSQLite PragmasData Type DifferencesTransactionsBasic TransactionsSavepointsDistributed TransactionsAuthenticationSSL Modes OverviewUsing With Connection StringsConnection PoolingReserved ConnectionsPrepared StatementsError HandlingError ClassesSQLite-Specific ErrorsNumbers and BigIntBigInt Instead of StringsRoadmapDatabase-Specific FeaturesAuthentication MethodsPrepared Statements & PerformanceMultiple Result SetsCharacter Sets & CollationsConnection AttributesType HandlingDifferences from PostgreSQLMySQL-Specific FeaturesPostgreSQL-Specific FeaturesCommon Patterns & Best PracticesWorking with MySQL Result SetsMySQL Error HandlingPerformance Tips for MySQLFrequently Asked QuestionsWhy not just use an existing library?CreditsData & StorageSQL Copy pagespan]:line-clamp-1 overflow-hidden group flex items-center py-0.5 gap-1 text-sm text-gray-950/50 dark:text-white/50 group-hover:text-gray-950/70 dark:group-hover:text-white/70 rounded-none rounded-r-xl border px-3 border-gray-200 aspect-square dark:border-white/[0.07] bg-background-light dark:bg-background-dark hover:bg-gray-600/5 dark:hover:bg-gray-200/5" aria-label="More actions" type="button" id="radix-_R_n4ctdbsnlht5lebsnpfdb_" aria-haspopup="menu" aria-expanded="false" data-state="closed"> *]:[overflow-wrap:anywhere]"> Copy pagespan]:line-clamp-1 overflow-hidden group flex items-center py-0.5 gap-1 text-sm text-gray-950/50 dark:text-white/50 group-hover:text-gray-950/70 dark:group-hover:text-white/70 rounded-none rounded-r-xl border px-3 border-gray-200 aspect-square dark:border-white/[0.07] bg-background-light dark:bg-background-dark hover:bg-gray-600/5 dark:hover:bg-gray-200/5" aria-label="More actions" type="button" id="radix-_R_1cctdbsnlht5lebsnpfdb_" aria-haspopup="menu" aria-expanded="false" data-state="closed"> The interface is designed to be performant, using tagged template literals for queries and offering features like connection pooling, transactions, and prepared statements.
db.ts Simple filenames without a protocol (like "myapp.db") require explicitly specifying { adapter: "sqlite" } to avoid ambiguity with PostgreSQL.
sql.array is PostgreSQL-only. Multi-dimensional arrays and NULL elements may not be supported yet.
sql``.simple()
The PostgreSQL wire protocol supports two types of queries: “simple” and “extended”. Simple queries can contain multiple statements but don’t support parameters, while extended queries (the default) support parameters but only allow one statement.
To run multiple statements in a single query, use sql``.simple():
1, 1,2, etc you can pass parameters to the query. If no parameters are used it can execute multiple commands per file.
Yes NoSuggest editsRaise issueArchivePreviousSQLiteNext⌘I xgithubdiscordyoutubePowered byThis documentation is built and hosted on Mintlify, a developer documentation platform
Fetch the complete documentation index at: /docs/llms.txt
Use this file to discover all available pages before exploring further.
Skip to main contentBun home pageBun provides native bindings for working with SQL databases through a unified Promise-based API that supports PostgreSQL, MySQL, and SQLite.
import { sql, SQL } from "bun";
// PostgreSQL (default)
const users = await sql`
SELECT * FROM users
WHERE active = ${true}
LIMIT ${10}
`;
// With MySQL
const mysql = new SQL("mysql:http://user:pass@localhost:3306/mydb");
const mysqlResults = await mysql`
SELECT * FROM users
WHERE active = ${true}
`;
// With SQLite
const sqlite = new SQL("sqlite:http://myapp.db");
const sqliteResults = await sqlite`
SELECT * FROM users
WHERE active = ${1}
`;
Features
Tagged template literals to protect against SQL injection
Transactions
Named & positional parameters
Connection pooling
BigInt support
SASL Auth support (SCRAM-SHA-256), MD5, and Clear Text
Connection timeouts
Returning rows as data objects, arrays of arrays, or Buffer
Binary protocol support makes it faster
TLS support (and auth mode)
Automatic configuration with environment variable
Database Support
Bun.SQL provides a unified API for multiple database systems:
PostgreSQL
PostgreSQL is used when:
The connection string doesn’t match SQLite or MySQL patterns (it’s the fallback adapter)
The connection string explicitly uses postgres:http:// or postgresql:http:// protocols
No connection string is provided and environment variables point to PostgreSQL
db.tsimport { sql } from "bun";
// Uses PostgreSQL if DATABASE_URL is not set or is a PostgreSQL URL
await sql`SELECT ...`;
import { SQL } from "bun";
const pg = new SQL("postgres:http://user:pass@localhost:5432/mydb");
await pg`SELECT ...`;
MySQL
MySQL support is built into Bun.SQL, providing the same tagged template literal interface with full compatibility for MySQL 5.7+ and MySQL 8.0+:
db.tsimport { SQL } from "bun";
// MySQL connection
const mysql = new SQL("mysql:http://user:password@localhost:3306/database");
const mysql2 = new SQL("mysql2:http://user:password@localhost:3306/database"); // mysql2 protocol also works
// Using options object
const mysql3 = new SQL({
adapter: "mysql",
hostname: "localhost",
port: 3306,
database: "myapp",
username: "dbuser",
password: "secretpass",
});
// Works with parameters - automatically uses prepared statements
const users = await mysql`SELECT * FROM users WHERE id = ${userId}`;
// Transactions work the same as PostgreSQL
await mysql.begin(async tx => {
await tx`INSERT INTO users (name) VALUES (${"Alice"})`;
await tx`UPDATE accounts SET balance = balance - 100 WHERE user_id = ${userId}`;
});
// Bulk inserts
const newUsers = [
{ name: "Alice", email: "alice@example.com" },
{ name: "Bob", email: "bob@example.com" },
];
await mysql`INSERT INTO users ${mysql(newUsers)}`;
MySQL Connection String Formats
MySQL accepts various URL formats for connection strings:// Standard mysql:// protocol
new SQL("mysql:http://user:pass@localhost:3306/database");
new SQL("mysql:http://user:pass@localhost/database"); // Default port 3306
// mysql2:// protocol (compatibility with mysql2 npm package)
new SQL("mysql2:http://user:pass@localhost:3306/database");
// With query parameters
new SQL("mysql:http://user:pass@localhost/db?ssl=true");
// Unix socket connection
new SQL("mysql:http://user:pass@/database?socket=/var/run/mysqld/mysqld.sock");
MySQL-Specific Features
MySQL databases support: Prepared statements: Automatically created for parameterized queries with statement caching Binary protocol: For better performance with prepared statements and accurate type handling Multiple result sets: Support for stored procedures returning multiple result sets Authentication plugins: Support for mysql_native_password, caching_sha2_password (MySQL 8.0 default), and sha256_password SSL/TLS connections: Configurable SSL modes similar to PostgreSQL Connection attributes: Client information sent to server for monitoring Query pipelining: Execute multiple prepared statements without waiting for responses SQLite SQLite support is built into Bun.SQL, providing the same tagged template literal interface:import { SQL } from "bun";
// In-memory database
const memory = new SQL(":memory:");
const memory2 = new SQL("sqlite:http://:memory:");
// File-based database
const sql1 = new SQL("sqlite:http://myapp.db");
// Using options object
const sql2 = new SQL({
adapter: "sqlite",
filename: "./data/app.db",
});
// For simple filenames, specify adapter explicitly
const sql3 = new SQL("myapp.db", { adapter: "sqlite" });
SQLite Connection String Formats
SQLite accepts various URL formats for connection strings:// Standard sqlite:// protocol
new SQL("sqlite:http://path/to/database.db");
new SQL("sqlite:path/to/database.db"); // Without slashes
// file:// protocol (also recognized as SQLite)
new SQL("file:http://path/to/database.db");
new SQL("file:path/to/database.db");
// Special :memory: database
new SQL(":memory:");
new SQL("sqlite:http://:memory:");
new SQL("file:http://:memory:");
// Relative and absolute paths
new SQL("sqlite:http://./local.db"); // Relative to current directory
new SQL("sqlite:http://../parent/db.db"); // Parent directory
new SQL("sqlite:http:///absolute/path.db"); // Absolute path
// With query parameters
new SQL("sqlite:http://data.db?mode=ro"); // Read-only mode
new SQL("sqlite:http://data.db?mode=rw"); // Read-write mode (no create)
new SQL("sqlite:http://data.db?mode=rwc"); // Read-write-create mode (default)
SQLite-Specific Options
SQLite databases support additional configuration options:const sql = new SQL({
adapter: "sqlite",
filename: "app.db",
// SQLite-specific options
readonly: false, // Open in read-only mode
create: true, // Create database if it doesn't exist
readwrite: true, // Open for reading and writing
// Additional Bun:sqlite options
strict: true, // Enable strict mode
safeIntegers: false, // Use JavaScript numbers for integers
});
Query parameters in the URL are parsed to set these options:
?mode=ro → readonly: true
?mode=rw → readonly: false, create: false
?mode=rwc → readonly: false, create: true (default)
Inserting data
You can pass JavaScript values directly to the SQL template literal and escaping will be handled for you.
import { sql } from "bun";
// Basic insert with direct values
const [user] = await sql`
INSERT INTO users (name, email)
VALUES (${name}, ${email})
RETURNING *
`;
// Using object helper for cleaner syntax
const userData = {
name: "Alice",
email: "alice@example.com",
};
const [newUser] = await sql`
INSERT INTO users ${sql(userData)}
RETURNING *
`;
// Expands to: INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com')
Bulk Insert
You can also pass arrays of objects to the SQL template literal and it will be expanded to a INSERT INTO ... VALUES ... statement.
const users = [
{ name: "Alice", email: "alice@example.com" },
{ name: "Bob", email: "bob@example.com" },
{ name: "Charlie", email: "charlie@example.com" },
];
await sql`INSERT INTO users ${sql(users)}`;
Picking columns to insert
You can use sql(object, ...string) to pick which columns to insert. Each of the columns must be defined on the object.
const user = {
name: "Alice",
email: "alice@example.com",
age: 25,
};
await sql`INSERT INTO users ${sql(user, "name", "email")}`;
// Only inserts name and email columns, ignoring other fields
Query Results
By default, Bun’s SQL client returns query results as arrays of objects, where each object represents a row with column names as keys. However, there are cases where you might want the data in a different format. The client provides two additional methods for this purpose.
sql``.values() format
The sql``.values() method returns rows as arrays of values rather than objects. Each row becomes an array where the values are in the same order as the columns in your query.
const rows = await sql`SELECT * FROM users`.values(); console.log(rows);This returns something like:
[ ["Alice", "alice@example.com"], ["Bob", "bob@example.com"], ];sql``.values() is especially useful if duplicate column names are returned in the query results. When using objects (the default), the last column name is used as the key in the object, which means duplicate column names overwrite each other — but when using sql``.values(), each column is present in the array so you can access the values of duplicate columns by index. sql``.raw() format The .raw() method returns rows as arrays of Buffer objects. This can be useful for working with binary data or for performance reasons.
const rows = await sql`SELECT * FROM users`.raw(); console.log(rows); // [[Buffer, Buffer], [Buffer, Buffer], [Buffer, Buffer]]SQL Fragments A common need in database applications is the ability to construct queries dynamically based on runtime conditions. Bun provides safe ways to do this without risking SQL injection. Dynamic Table Names When you need to reference tables or schemas dynamically, use the sql() helper to ensure proper escaping:
// Safely reference tables dynamically
await sql`SELECT * FROM ${sql("users")}`;
// With schema qualification
await sql`SELECT * FROM ${sql("public.users")}`;
Conditional Queries
You can use the sql() helper to build queries with conditional clauses. This allows you to create flexible queries that adapt to your application’s needs:
// Optional WHERE clauses
const filterAge = true;
const minAge = 21;
const ageFilter = sql`AND age > ${minAge}`;
await sql`
SELECT * FROM users
WHERE active = ${true}
${filterAge ? ageFilter : sql``}
`;
Dynamic columns in updates
You can use sql(object, ...string) to pick which columns to update. Each of the columns must be defined on the object. If the columns are not informed all keys will be used to update the row.
await sql`UPDATE users SET ${sql(user, "name", "email")} WHERE id = ${user.id}`;
// uses all keys from the object to update the row
await sql`UPDATE users SET ${sql(user)} WHERE id = ${user.id}`;
Dynamic values and where in
Value lists can also be created dynamically, making where in queries simple too. Optionally you can pass a array of objects and inform what key to use to create the list.
await sql`SELECT * FROM users WHERE id IN ${sql([1, 2, 3])}`;
const users = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
{ id: 3, name: "Charlie" },
];
await sql`SELECT * FROM users WHERE id IN ${sql(users, "id")}`;
sql.array helper
The sql.array helper creates PostgreSQL array literals from JavaScript arrays:
// Create array literals for PostgreSQL
await sql`INSERT INTO tags (items) VALUES (${sql.array(["red", "blue", "green"])})`;
// Generates: INSERT INTO tags (items) VALUES (ARRAY['red', 'blue', 'green'])
// Works with numeric arrays too
await sql`SELECT * FROM products WHERE ids = ANY(${sql.array([1, 2, 3])})`;
// Generates: SELECT * FROM products WHERE ids = ANY(ARRAY[1, 2, 3])
// Multiple statements in one query await sql` SELECT 1; SELECT 2; `.simple();Simple queries are often useful for database migrations and setup scripts. Note that simple queries cannot use parameters (${value}). If you need parameters, you must split your query into separate statements. Queries in files You can use the sql.file method to read a query from a file and execute it, if the file includes 1,
const result = await sql.file("query.sql", [1, 2, 3]);
Unsafe Queries
You can use the sql.unsafe function to execute raw SQL strings. Use this with caution, as it will not escape user input. Executing more than one command per query is allowed if no parameters are used.
// Multiple commands without parameters
const result = await sql.unsafe(`
SELECT ${userColumns} FROM users;
SELECT ${accountColumns} FROM accounts;
`);
// Using parameters (only one command is allowed)
const result = await sql.unsafe("SELECT " + dangerous + " FROM users WHERE id = $1", [id]);
Execute and Cancelling Queries
Bun’s SQL is lazy, which means it will only start executing when awaited or executed with .execute().
You can cancel a query that is currently executing by calling the cancel() method on the query object.
const query = sql`SELECT * FROM users`.execute(); setTimeout(() => query.cancel(), 100); await query;Database Environment Variables sql connection parameters can be configured using environment variables. The client checks these variables in a specific order of precedence and automatically detects the database type based on the connection string format. Automatic Database Detection When using Bun.sql() without arguments or new SQL() with a connection string, the adapter is automatically detected based on the URL format: MySQL Auto-Detection MySQL is automatically selected when the connection string matches these patterns: mysql:http://... - MySQL protocol URLs mysql2:http://... - MySQL2 protocol URLs (compatibility alias)
// These all use MySQL automatically (no adapter needed)
const sql1 = new SQL("mysql:http://user:pass@localhost/mydb");
const sql2 = new SQL("mysql2:http://user:pass@localhost:3306/mydb");
// Works with DATABASE_URL environment variable
DATABASE_URL="mysql:http://user:pass@localhost/mydb" bun run app.js
DATABASE_URL="mysql2:http://user:pass@localhost:3306/mydb" bun run app.js
SQLite Auto-Detection
SQLite is automatically selected when the connection string matches these patterns:
:memory: - In-memory database
sqlite:http://... - SQLite protocol URLs
sqlite:... - SQLite protocol without slashes
file:http://... - File protocol URLs
file:... - File protocol without slashes
// These all use SQLite automatically (no adapter needed)
const sql1 = new SQL(":memory:");
const sql2 = new SQL("sqlite:http://app.db");
const sql3 = new SQL("file:http://./database.db");
// Works with DATABASE_URL environment variable
DATABASE_URL=":memory:" bun run app.js
DATABASE_URL="sqlite:http://myapp.db" bun run app.js
DATABASE_URL="file:http://./data/app.db" bun run app.js
PostgreSQL Auto-Detection
PostgreSQL is the default for connection strings that don’t match MySQL or SQLite patterns:
# PostgreSQL is detected for these patterns DATABASE_URL="postgres:http://user:pass@localhost:5432/mydb" bun run app.js DATABASE_URL="postgresql:http://user:pass@localhost:5432/mydb" bun run app.js # Or any URL that doesn't match MySQL or SQLite patterns DATABASE_URL="localhost:5432/mydb" bun run app.jsMySQL Environment Variables MySQL connections can be configured via environment variables:
# Primary connection URL (checked first) MYSQL_URL="mysql:http://user:pass@localhost:3306/mydb" # Alternative: DATABASE_URL with MySQL protocol DATABASE_URL="mysql:http://user:pass@localhost:3306/mydb" DATABASE_URL="mysql2:http://user:pass@localhost:3306/mydb"If no connection URL is provided, MySQL checks these individual parameters: Environment VariableDefault ValueDescriptionMYSQL_HOSTlocalhostDatabase hostMYSQL_PORT3306Database portMYSQL_USERrootDatabase userMYSQL_PASSWORD(empty)Database passwordMYSQL_DATABASEmysqlDatabase nameMYSQL_URL(empty)Primary connection URL for MySQLTLS_MYSQL_DATABASE_URL(empty)SSL/TLS-enabled connection URL PostgreSQL Environment Variables The following environment variables can be used to define the PostgreSQL connection: Environment VariableDescriptionPOSTGRES_URLPrimary connection URL for PostgreSQLDATABASE_URLAlternative connection URL (auto-detected)PGURLAlternative connection URLPG_URLAlternative connection URLTLS_POSTGRES_DATABASE_URLSSL/TLS-enabled connection URLTLS_DATABASE_URLAlternative SSL/TLS-enabled connection URL If no connection URL is provided, the system checks for the following individual parameters: Environment VariableFallback VariablesDefault ValueDescriptionPGHOST-localhostDatabase hostPGPORT-5432Database portPGUSERNAMEPGUSER, USER, USERNAMEpostgresDatabase userPGPASSWORD-(empty)Database passwordPGDATABASE-usernameDatabase name SQLite Environment Variables SQLite connections can be configured via DATABASE_URL when it contains a SQLite-compatible URL:
# These are all recognized as SQLite DATABASE_URL=":memory:" DATABASE_URL="sqlite:http://./app.db" DATABASE_URL="file:http:///absolute/path/to/db.sqlite"Note: PostgreSQL-specific environment variables (POSTGRES_URL, PGHOST, etc.) are ignored when using SQLite. Runtime Preconnection Bun can preconnect to PostgreSQL at startup to improve performance by establishing database connections before your application code runs. This is useful for reducing connection latency on the first database query.
# Enable PostgreSQL preconnection bun --sql-preconnect index.js # Works with DATABASE_URL environment variable DATABASE_URL=postgres:http://user:pass@localhost:5432/db bun --sql-preconnect index.js # Can be combined with other runtime flags bun --sql-preconnect --hot index.jsThe --sql-preconnect flag will automatically establish a PostgreSQL connection using your configured environment variables at startup. If the connection fails, it won’t crash your application - the error will be handled gracefully. Connection Options You can configure your database connection manually by passing options to the SQL constructor. Options vary depending on the database adapter: MySQL Options
import { SQL } from "bun";
const sql = new SQL({
// Required for MySQL when using options object
adapter: "mysql",
// Connection details
hostname: "localhost",
port: 3306,
database: "myapp",
username: "dbuser",
password: "secretpass",
// Unix socket connection (alternative to hostname/port)
// socket: "/var/run/mysqld/mysqld.sock",
// Connection pool settings
max: 20, // Maximum connections in pool (default: 10)
idleTimeout: 30, // Close idle connections after 30s
maxLifetime: 0, // Connection lifetime in seconds (0 = forever)
connectionTimeout: 30, // Timeout when establishing new connections
// SSL/TLS options
ssl: "prefer", // or "disable", "require", "verify-ca", "verify-full"
// tls: {
// rejectUnauthorized: true,
// ca: "path/to/ca.pem",
// key: "path/to/key.pem",
// cert: "path/to/cert.pem",
// },
// Callbacks
onconnect: client => {
console.log("Connected to MySQL");
},
onclose: (client, err) => {
if (err) {
console.error("MySQL connection error:", err);
} else {
console.log("MySQL connection closed");
}
},
});
PostgreSQL Options
import { SQL } from "bun";
const sql = new SQL({
// Connection details (adapter is auto-detected as PostgreSQL)
url: "postgres:http://user:pass@localhost:5432/dbname",
// Alternative connection parameters
hostname: "localhost",
port: 5432,
database: "myapp",
username: "dbuser",
password: "secretpass",
// Connection pool settings
max: 20, // Maximum connections in pool
idleTimeout: 30, // Close idle connections after 30s
maxLifetime: 0, // Connection lifetime in seconds (0 = forever)
connectionTimeout: 30, // Timeout when establishing new connections
// SSL/TLS options
tls: true,
// tls: {
// rejectUnauthorized: true,
// requestCert: true,
// ca: "path/to/ca.pem",
// key: "path/to/key.pem",
// cert: "path/to/cert.pem",
// checkServerIdentity(hostname, cert) {
// ...
// },
// },
// Callbacks
onconnect: client => {
console.log("Connected to PostgreSQL");
},
onclose: client => {
console.log("PostgreSQL connection closed");
},
});
SQLite Options
import { SQL } from "bun";
const sql = new SQL({
// Required for SQLite
adapter: "sqlite",
filename: "./data/app.db", // or ":memory:" for in-memory database
// SQLite-specific access modes
readonly: false, // Open in read-only mode
create: true, // Create database if it doesn't exist
readwrite: true, // Allow read and write operations
// SQLite data handling
strict: true, // Enable strict mode for better type safety
safeIntegers: false, // Use BigInt for integers exceeding JS number range
// Callbacks
onconnect: client => {
console.log("SQLite database opened");
},
onclose: client => {
console.log("SQLite database closed");
},
});
SQLite Connection Notes
Connection Pooling: SQLite doesn’t use connection pooling as it’s a file-based database. Each SQL instance represents a single connection. Transactions: SQLite supports nested transactions through savepoints, similar to PostgreSQL. Concurrent Access: SQLite handles concurrent access through file locking. Use WAL mode for better concurrency. Memory Databases: Using :memory: creates a temporary database that exists only for the connection lifetime. Dynamic passwords When clients need to use alternative authentication schemes such as access tokens or connections to databases with rotating passwords, provide either a synchronous or asynchronous function that will resolve the dynamic password value at connection time.import { SQL } from "bun";
const sql = new SQL(url, {
// Other connection config
...
// Password function for the database user
password: async () => await signer.getAuthToken(),
});
SQLite-Specific Features
Query Execution
SQLite executes queries synchronously, unlike PostgreSQL which uses asynchronous I/O. However, the API remains consistent using Promises:
const sqlite = new SQL("sqlite:http://app.db");
// Works the same as PostgreSQL, but executes synchronously under the hood
const users = await sqlite`SELECT * FROM users`;
// Parameters work identically
const user = await sqlite`SELECT * FROM users WHERE id = ${userId}`;
SQLite Pragmas
You can use PRAGMA statements to configure SQLite behavior:
const sqlite = new SQL("sqlite:http://app.db");
// Enable foreign keys
await sqlite`PRAGMA foreign_keys = ON`;
// Set journal mode to WAL for better concurrency
await sqlite`PRAGMA journal_mode = WAL`;
// Check integrity
const integrity = await sqlite`PRAGMA integrity_check`;
Data Type Differences
SQLite has a more flexible type system than PostgreSQL:
// SQLite stores data in 5 storage classes: NULL, INTEGER, REAL, TEXT, BLOB
const sqlite = new SQL("sqlite:http://app.db");
// SQLite is more lenient with types
await sqlite`
CREATE TABLE flexible (
id INTEGER PRIMARY KEY,
data TEXT, -- Can store numbers as strings
value NUMERIC, -- Can store integers, reals, or text
blob BLOB -- Binary data
)
`;
// JavaScript values are automatically converted
await sqlite`INSERT INTO flexible VALUES (${1}, ${"text"}, ${123.45}, ${Buffer.from("binary")})`;
Transactions
To start a new transaction, use sql.begin. This method works for both PostgreSQL and SQLite. For PostgreSQL, it reserves a dedicated connection from the pool. For SQLite, it begins a transaction on the single connection.
The BEGIN command is sent automatically, including any optional configurations you specify. If an error occurs during the transaction, a ROLLBACK is triggered to ensure the process continues smoothly.
Basic Transactions
await sql.begin(async tx => {
// All queries in this function run in a transaction
await tx`INSERT INTO users (name) VALUES (${"Alice"})`;
await tx`UPDATE accounts SET balance = balance - 100 WHERE user_id = 1`;
// Transaction automatically commits if no errors are thrown
// Rolls back if any error occurs
});
It’s also possible to pipeline the requests in a transaction if needed by returning an array with queries from the callback function like this:
await sql.begin(async tx => {
return [
tx`INSERT INTO users (name) VALUES (${"Alice"})`,
tx`UPDATE accounts SET balance = balance - 100 WHERE user_id = 1`,
];
});
Savepoints
Savepoints in SQL create intermediate checkpoints within a transaction, enabling partial rollbacks without affecting the entire operation. They are useful in complex transactions, allowing error recovery and maintaining consistent results.
await sql.begin(async tx => {
await tx`INSERT INTO users (name) VALUES (${"Alice"})`;
await tx.savepoint(async sp => {
// This part can be rolled back separately
await sp`UPDATE users SET status = 'active'`;
if (someCondition) {
throw new Error("Rollback to savepoint");
}
});
// Continue with transaction even if savepoint rolled back
await tx`INSERT INTO audit_log (action) VALUES ('user_created')`;
});
Distributed Transactions
Two-Phase Commit (2PC) is a distributed transaction protocol where Phase 1 has the coordinator preparing nodes by ensuring data is written and ready to commit, while Phase 2 finalizes with nodes either committing or rolling back based on the coordinator’s decision. This process ensures data durability and proper lock management.
In PostgreSQL and MySQL, distributed transactions persist beyond their original session, allowing privileged users or coordinators to commit or rollback them later. This supports robust distributed transactions, recovery processes, and administrative operations.
Each database system implements distributed transactions differently:
PostgreSQL natively supports them through prepared transactions, while MySQL uses XA Transactions.
If any exceptions occur during the distributed transaction and aren’t caught, the system will automatically rollback all changes. When everything proceeds normally, you maintain the flexibility to either commit or rollback the transaction later.
// Begin a distributed transaction
await sql.beginDistributed("tx1", async tx => {
await tx`INSERT INTO users (name) VALUES (${"Alice"})`;
});
// Later, commit or rollback
await sql.commitDistributed("tx1");
// or
await sql.rollbackDistributed("tx1");
Authentication
Bun supports SCRAM-SHA-256 (SASL), MD5, and Clear Text authentication. SASL is recommended for better security. Check Postgres SASL Authentication for more information.
SSL Modes Overview
PostgreSQL supports different SSL/TLS modes to control how secure connections are established. These modes determine the behavior when connecting and the level of certificate verification performed.
const sql = new SQL({
hostname: "localhost",
username: "user",
password: "password",
ssl: "disable", // | "prefer" | "require" | "verify-ca" | "verify-full"
});
SSL ModeDescriptiondisableNo SSL/TLS used. Connections fail if server requires SSL.preferTries SSL first, falls back to non-SSL if SSL fails. Default mode if none specified.requireRequires SSL without certificate verification. Fails if SSL cannot be established.verify-caVerifies server certificate is signed by trusted CA. Fails if verification fails.verify-fullMost secure mode. Verifies certificate and hostname match. Protects against untrusted certificates and MITM attacks.
Using With Connection Strings
The SSL mode can also be specified in connection strings:
// Using prefer mode
const sql = new SQL("postgres:http://user:password@localhost/mydb?sslmode=prefer");
// Using verify-full mode
const sql = new SQL("postgres:http://user:password@localhost/mydb?sslmode=verify-full");
Connection Pooling
Bun’s SQL client automatically manages a connection pool, which is a pool of database connections that are reused for multiple queries. This helps to reduce the overhead of establishing and closing connections for each query, and it also helps to manage the number of concurrent connections to the database.
const sql = new SQL({
// Pool configuration
max: 20, // Maximum 20 concurrent connections
idleTimeout: 30, // Close idle connections after 30s
maxLifetime: 3600, // Max connection lifetime 1 hour
connectionTimeout: 10, // Connection timeout 10s
});
No connection will be made until a query is made.
const sql = Bun.SQL(); // no connection are created
await sql`...`; // pool is started until max is reached (if possible), first available connection is used
await sql`...`; // previous connection is reused
// two connections are used now at the same time
await Promise.all([
sql`INSERT INTO users ${sql({ name: "Alice" })}`,
sql`UPDATE users SET name = ${user.name} WHERE id = ${user.id}`,
]);
await sql.close(); // await all queries to finish and close all connections from the pool
await sql.close({ timeout: 5 }); // wait 5 seconds and close all connections from the pool
await sql.close({ timeout: 0 }); // close all connections from the pool immediately
Reserved Connections
Bun enables you to reserve a connection from the pool, and returns a client that wraps the single connection. This can be used for running queries on an isolated connection.
// Get exclusive connection from pool
const reserved = await sql.reserve();
try {
await reserved`INSERT INTO users (name) VALUES (${"Alice"})`;
} finally {
// Important: Release connection back to pool
reserved.release();
}
// Or using Symbol.dispose
{
using reserved = await sql.reserve();
await reserved`SELECT 1`;
} // Automatically released
Prepared Statements
By default, Bun’s SQL client automatically creates named prepared statements for queries where it can be inferred that the query is static. This provides better performance. However, you can change this behavior by setting prepare: false in the connection options:
const sql = new SQL({
// ... other options ...
prepare: false, // Disable persisting named prepared statements on the server
});
When prepare: false is set:
Queries are still executed using the “extended” protocol, but they are executed using unnamed prepared statements, an unnamed prepared statement lasts only until the next Parse statement specifying the unnamed statement as destination is issued.
Parameter binding is still safe against SQL injection
Each query is parsed and planned from scratch by the server
Queries will not be pipelined
You might want to use prepare: false when:
Using PGBouncer in transaction mode (though since PGBouncer 1.21.0, protocol-level named prepared statements are supported when configured properly)
Debugging query execution plans
Working with dynamic SQL where query plans need to be regenerated frequently
More than one command per query will not be supported (unless you use sql``.simple())
Note that disabling prepared statements may impact performance for queries that are executed frequently with different parameters, as the server needs to parse and plan each query from scratch.
Error Handling
The client provides typed errors for different failure scenarios. Errors are database-specific and extend from base error classes:
Error Classes
import { SQL } from "bun";
try {
await sql`SELECT * FROM users`;
} catch (error) {
if (error instanceof SQL.PostgresError) {
// PostgreSQL-specific error
console.log(error.code); // PostgreSQL error code
console.log(error.detail); // Detailed error message
console.log(error.hint); // Helpful hint from PostgreSQL
} else if (error instanceof SQL.SQLiteError) {
// SQLite-specific error
console.log(error.code); // SQLite error code (e.g., "SQLITE_CONSTRAINT")
console.log(error.errno); // SQLite error number
console.log(error.byteOffset); // Byte offset in SQL statement (if available)
} else if (error instanceof SQL.SQLError) {
// Generic SQL error (base class)
console.log(error.message);
}
}
PostgreSQL-Specific Error Codes
PostgreSQL Connection ErrorsConnection ErrorsDescriptionERR_POSTGRES_CONNECTION_CLOSEDConnection was terminated or never establishedERR_POSTGRES_CONNECTION_TIMEOUTFailed to establish connection within timeout periodERR_POSTGRES_IDLE_TIMEOUTConnection closed due to inactivityERR_POSTGRES_LIFETIME_TIMEOUTConnection exceeded maximum lifetimeERR_POSTGRES_TLS_NOT_AVAILABLESSL/TLS connection not availableERR_POSTGRES_TLS_UPGRADE_FAILEDFailed to upgrade connection to SSL/TLSAuthentication ErrorsAuthentication ErrorsDescriptionERR_POSTGRES_AUTHENTICATION_FAILED_PBKDF2Password authentication failedERR_POSTGRES_UNKNOWN_AUTHENTICATION_METHODServer requested unknown auth methodERR_POSTGRES_UNSUPPORTED_AUTHENTICATION_METHODServer requested unsupported auth methodERR_POSTGRES_INVALID_SERVER_KEYInvalid server key during authenticationERR_POSTGRES_INVALID_SERVER_SIGNATUREInvalid server signatureERR_POSTGRES_SASL_SIGNATURE_INVALID_BASE64Invalid SASL signature encodingERR_POSTGRES_SASL_SIGNATURE_MISMATCHSASL signature verification failedQuery ErrorsQuery ErrorsDescriptionERR_POSTGRES_SYNTAX_ERRORInvalid SQL syntax (extends SyntaxError)ERR_POSTGRES_SERVER_ERRORGeneral error from PostgreSQL serverERR_POSTGRES_INVALID_QUERY_BINDINGInvalid parameter bindingERR_POSTGRES_QUERY_CANCELLEDQuery was cancelledERR_POSTGRES_NOT_TAGGED_CALLQuery was called without a tagged callData Type ErrorsData Type ErrorsDescriptionERR_POSTGRES_INVALID_BINARY_DATAInvalid binary data formatERR_POSTGRES_INVALID_BYTE_SEQUENCEInvalid byte sequenceERR_POSTGRES_INVALID_BYTE_SEQUENCE_FOR_ENCODINGEncoding errorERR_POSTGRES_INVALID_CHARACTERInvalid character in dataERR_POSTGRES_OVERFLOWNumeric overflowERR_POSTGRES_UNSUPPORTED_BYTEA_FORMATUnsupported binary formatERR_POSTGRES_UNSUPPORTED_INTEGER_SIZEInteger size not supportedERR_POSTGRES_MULTIDIMENSIONAL_ARRAY_NOT_SUPPORTED_YETMultidimensional arrays not supportedERR_POSTGRES_NULLS_IN_ARRAY_NOT_SUPPORTED_YETNULL values in arrays not supportedProtocol ErrorsProtocol ErrorsDescriptionERR_POSTGRES_EXPECTED_REQUESTExpected client requestERR_POSTGRES_EXPECTED_STATEMENTExpected prepared statementERR_POSTGRES_INVALID_BACKEND_KEY_DATAInvalid backend key dataERR_POSTGRES_INVALID_MESSAGEInvalid protocol messageERR_POSTGRES_INVALID_MESSAGE_LENGTHInvalid message lengthERR_POSTGRES_UNEXPECTED_MESSAGEUnexpected message typeTransaction ErrorsTransaction ErrorsDescriptionERR_POSTGRES_UNSAFE_TRANSACTIONUnsafe transaction operation detectedERR_POSTGRES_INVALID_TRANSACTION_STATEInvalid transaction state SQLite-Specific Errors SQLite errors provide error codes and numbers that correspond to SQLite’s standard error codes:Common SQLite Error Codes
Error CodeerrnoDescriptionSQLITE_CONSTRAINT19Constraint violation (UNIQUE, CHECK, NOT NULL, etc.)SQLITE_BUSY5Database is lockedSQLITE_LOCKED6Table in the database is lockedSQLITE_READONLY8Attempt to write to a readonly databaseSQLITE_IOERR10Disk I/O errorSQLITE_CORRUPT11Database disk image is malformedSQLITE_FULL13Database or disk is fullSQLITE_CANTOPEN14Unable to open database fileSQLITE_PROTOCOL15Database lock protocol errorSQLITE_SCHEMA17Database schema has changedSQLITE_TOOBIG18String or BLOB exceeds size limitSQLITE_MISMATCH20Data type mismatchSQLITE_MISUSE21Library used incorrectlySQLITE_AUTH23Authorization deniedExample error handling:const sqlite = new SQL("sqlite:http://app.db");
try {
await sqlite`INSERT INTO users (id, name) VALUES (1, 'Alice')`;
await sqlite`INSERT INTO users (id, name) VALUES (1, 'Bob')`; // Duplicate ID
} catch (error) {
if (error instanceof SQL.SQLiteError) {
if (error.code === "SQLITE_CONSTRAINT") {
console.log("Constraint violation:", error.message);
// Handle unique constraint violation
}
}
}
Numbers and BigInt
Bun’s SQL client includes special handling for large numbers that exceed the range of a 53-bit integer. Here’s how it works:
import { sql } from "bun";
const [{ x, y }] = await sql`SELECT 9223372036854777 as x, 12345 as y`;
console.log(typeof x, x); // "string" "9223372036854777"
console.log(typeof y, y); // "number" 12345
BigInt Instead of Strings
If you need large numbers as BigInt instead of strings, you can enable this by setting the bigint option to true when initializing the SQL client:
const sql = new SQL({
bigint: true,
});
const [{ x }] = await sql`SELECT 9223372036854777 as x`;
console.log(typeof x, x); // "bigint" 9223372036854777n
Roadmap
There’s still some things we haven’t finished yet.
Connection preloading via --db-preconnect Bun CLI flag
Column name transforms (e.g. snake_case to camelCase). This is mostly blocked on a unicode-aware implementation of changing the case in C++ using WebKit’s WTF::String.
Column type transforms
Database-Specific Features
Authentication Methods
MySQL supports multiple authentication plugins that are automatically negotiated:
mysql_native_password - Traditional MySQL authentication, widely compatible
caching_sha2_password - Default in MySQL 8.0+, more secure with RSA key exchange
sha256_password - SHA-256 based authentication
The client automatically handles authentication plugin switching when requested by the server, including secure password exchange over non-SSL connections.
Prepared Statements & Performance
MySQL uses server-side prepared statements for all parameterized queries:
// This automatically creates a prepared statement on the server
const user = await mysql`SELECT * FROM users WHERE id = ${userId}`;
// Prepared statements are cached and reused for identical queries
for (const id of userIds) {
// Same prepared statement is reused
await mysql`SELECT * FROM users WHERE id = ${id}`;
}
// Query pipelining - multiple statements sent without waiting
const [users, orders, products] = await Promise.all([
mysql`SELECT * FROM users WHERE active = ${true}`,
mysql`SELECT * FROM orders WHERE status = ${"pending"}`,
mysql`SELECT * FROM products WHERE in_stock = ${true}`,
]);
Multiple Result Sets
MySQL can return multiple result sets from multi-statement queries:
const mysql = new SQL("mysql:http://user:pass@localhost/mydb");
// Multi-statement queries with simple() method
const multiResults = await mysql`
SELECT * FROM users WHERE id = 1;
SELECT * FROM orders WHERE user_id = 1;
`.simple();
Character Sets & Collations
Bun.SQL automatically uses utf8mb4 character set for MySQL connections, ensuring full Unicode support including emojis. This is the recommended character set for modern MySQL applications.
Connection Attributes
Bun automatically sends client information to MySQL for better monitoring:
// These attributes are sent automatically: // _client_name: "Bun" // _client_version: // You can see these in MySQL's performance_schema.session_connect_attrsType Handling MySQL types are automatically converted to JavaScript types: MySQL TypeJavaScript TypeNotesINT, TINYINT, MEDIUMINTnumberWithin safe integer rangeBIGINTstring, number or BigIntIf the value fits in i32/u32 size will be number otherwise string or BigInt Based on bigint optionDECIMAL, NUMERICstringTo preserve precisionFLOAT, DOUBLEnumberDATEDateJavaScript Date objectDATETIME, TIMESTAMPDateDecoded as UTC (see note below); 0000-00-00 becomes an Invalid DateTIMEnumberTotal of microsecondsYEARnumberCHAR, VARCHAR, VARSTRING, STRINGstringTINY TEXT, MEDIUM TEXT, TEXT, LONG TEXTstringTINY BLOB, MEDIUM BLOB, BLOG, LONG BLOBstringBLOB Types are alias for TEXT typesJSONobject/arrayAutomatically parsedBIT(1)booleanBIT(1) in MySQLGEOMETRYstringGeometry data DATETIME and TIMESTAMP values have no timezone on the wire, so Bun reads them back as UTC — the Date you get has the same UTC wall-clock that was stored, regardless of the machine’s timezone. This matches how values are written (a bound Date stores its UTC components). The same applies to PostgreSQL’s timestamp (without time zone); timestamptz carries an explicit offset and is unaffected. Differences from PostgreSQL While the API is unified, there are some behavioral differences: Parameter placeholders: MySQL uses ? internally but Bun converts $1, $2 style automatically RETURNING clause: MySQL doesn’t support RETURNING; use result.lastInsertRowid or a separate SELECT Array types: MySQL doesn’t have native array types like PostgreSQL MySQL-Specific Features We haven’t implemented LOAD DATA INFILE support yet PostgreSQL-Specific Features We haven’t implemented these yet: COPY support LISTEN support NOTIFY support We also haven’t implemented some of the more uncommon features like: GSSAPI authentication SCRAM-SHA-256-PLUS support Point & PostGIS types All the multi-dimensional integer array types (only a couple of the types are supported) Common Patterns & Best Practices Working with MySQL Result Sets
// Getting insert ID after INSERT
const result = await mysql`INSERT INTO users (name) VALUES (${"Alice"})`;
console.log(result.lastInsertRowid); // MySQL's LAST_INSERT_ID()
// Handling affected rows
const updated = await mysql`UPDATE users SET active = ${false} WHERE age ${18}`;
console.log(updated.affectedRows); // Number of rows updated
// Using MySQL-specific functions
const now = await mysql`SELECT NOW() as current_time`;
const uuid = await mysql`SELECT UUID() as id`;
MySQL Error Handling
try {
await mysql`INSERT INTO users (email) VALUES (${"duplicate@email.com"})`;
} catch (error) {
if (error.code === "ER_DUP_ENTRY") {
console.log("Duplicate entry detected");
} else if (error.code === "ER_ACCESS_DENIED_ERROR") {
console.log("Access denied");
} else if (error.code === "ER_BAD_DB_ERROR") {
console.log("Database does not exist");
}
// MySQL error codes are compatible with mysql/mysql2 packages
}
Performance Tips for MySQL
Use connection pooling: Set appropriate max pool size based on your workload
Enable prepared statements: They’re enabled by default and improve performance
Use transactions for bulk operations: Group related queries in transactions
Index properly: MySQL relies heavily on indexes for query performance
Use utf8mb4 charset: It’s set by default and handles all Unicode characters
Frequently Asked Questions
details]:border-0 [&>details]:rounded-none [&>details>summary]:rounded-none [&>details]:mb-0 overflow-hidden mt-0 mb-3 rounded-xl prose prose-gray dark:prose-invert divide-y divide-inherit border dark:border-gray-800/50">Why is this `Bun.sql` and not `Bun.postgres`?
The plan was to add more database drivers in the future. Now with MySQL support added, this unified API supports PostgreSQL, MySQL, and SQLite.How do I know which database adapter is being used?
The adapter is automatically detected from the connection string: URLs starting with mysql:http:// or mysql2:http:// use MySQL URLs matching SQLite patterns (:memory:, sqlite:http://, file:http://) use SQLite Everything else defaults to PostgreSQLAre MySQL stored procedures supported?
Yes, stored procedures are fully supported including OUT parameters and multiple result sets:// Call stored procedure
const results = await mysql`CALL GetUserStats(${userId}, @total_orders)`;
// Get OUT parameter
const outParam = await mysql`SELECT @total_orders as total`;
Can I use MySQL-specific SQL syntax?
Yes, you can use any MySQL-specific syntax:// MySQL-specific syntax works fine
await mysql`SET @user_id = ${userId}`;
await mysql`SHOW TABLES`;
await mysql`DESCRIBE users`;
await mysql`EXPLAIN SELECT * FROM users WHERE id = ${id}`;
Why not just use an existing library?
npm packages like postgres.js, pg, and node-postgres can be used in Bun too. They’re great options.
Two reasons why:
We think it’s simpler for developers to have a database driver built into Bun. The time you spend library shopping is time you could be building your app.
We leverage some JavaScriptCore engine internals to make it faster to create objects that would be difficult to implement in a library
Credits
Huge thanks to @porsager’s postgres.js for the inspiration for the API interface.Was this page helpful?
SQL - Bun,AI智能索引,全网链接索引,智能导航,网页索引
- Bun provides native bindings for working with SQL databases through a unified Promise-based API that supports PostgreSQL, MySQL, and SQLite.