Skip to main content
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

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:// or postgresql:// protocols
  • No connection string is provided and environment variables point to PostgreSQL
db.ts

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.ts
MySQL accepts various URL formats for connection strings:
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:
SQLite accepts various URL formats for connection strings:
Simple filenames without a protocol (like "myapp.db") require explicitly specifying { adapter: "sqlite" } to avoid ambiguity with PostgreSQL.
SQLite databases support additional configuration options:
Query parameters in the URL are parsed to set these options:
  • ?mode=roreadonly: true
  • ?mode=rwreadonly: false, create: false
  • ?mode=rwcreadonly: false, create: true (default)

Inserting data

You can pass JavaScript values directly to the SQL template literal and escaping will be handled for you.

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.

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.

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.
This returns something like:
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.

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:

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:

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.

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.

sql.array helper

The sql.array helper creates PostgreSQL array literals from JavaScript arrays:
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():
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,1, 2, etc you can pass parameters to the query. If no parameters are used it can execute multiple commands per file.

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.

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.

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://... - MySQL protocol URLs
  • mysql2://... - MySQL2 protocol URLs (compatibility alias)

SQLite Auto-Detection

SQLite is automatically selected when the connection string matches these patterns:
  • :memory: - In-memory database
  • sqlite://... - SQLite protocol URLs
  • sqlite:... - SQLite protocol without slashes
  • file://... - File protocol URLs
  • file:... - File protocol without slashes

PostgreSQL Auto-Detection

PostgreSQL is the default for connection strings that don’t match MySQL or SQLite patterns:

MySQL Environment Variables

MySQL connections can be configured via environment variables:
If no connection URL is provided, MySQL checks these individual parameters:

PostgreSQL Environment Variables

The following environment variables can be used to define the PostgreSQL connection: If no connection URL is provided, the system checks for the following individual parameters:

SQLite Environment Variables

SQLite connections can be configured via DATABASE_URL when it contains a SQLite-compatible URL:
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.
The --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

PostgreSQL Options

SQLite Options

  • 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.

SQLite-Specific Features

Query Execution

SQLite executes queries synchronously, unlike PostgreSQL which uses asynchronous I/O. However, the API remains consistent using Promises:

SQLite Pragmas

You can use PRAGMA statements to configure SQLite behavior:

Data Type Differences

SQLite has a more flexible type system than PostgreSQL:

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

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:

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.

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.

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.

Using With Connection Strings

The SSL mode can also be specified in connection strings:

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.
No connection will be made until a query is made.

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.

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:
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

PostgreSQL Connection Errors

Authentication Errors

Query Errors

Data Type Errors

Protocol Errors

Transaction Errors

SQLite-Specific Errors

SQLite errors provide error codes and numbers that correspond to SQLite’s standard error codes:
Example error handling:

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:

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:

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:

Multiple Result Sets

MySQL can return multiple result sets from multi-statement queries:

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:

Type Handling

MySQL types are automatically converted to JavaScript types: 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:
  1. Parameter placeholders: MySQL uses ? internally but Bun converts $1, $2 style automatically
  2. RETURNING clause: MySQL doesn’t support RETURNING; use result.lastInsertRowid or a separate SELECT
  3. 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

MySQL Error Handling

Performance Tips for MySQL

  1. Use connection pooling: Set appropriate max pool size based on your workload
  2. Enable prepared statements: They’re enabled by default and improve performance
  3. Use transactions for bulk operations: Group related queries in transactions
  4. Index properly: MySQL relies heavily on indexes for query performance
  5. Use utf8mb4 charset: It’s set by default and handles all Unicode characters

Frequently Asked Questions

The plan was to add more database drivers in the future. Now with MySQL support added, this unified API supports PostgreSQL, MySQL, and SQLite.
The adapter is automatically detected from the connection string:
  • URLs starting with mysql:// or mysql2:// use MySQL
  • URLs matching SQLite patterns (:memory:, sqlite://, file://) use SQLite
  • Everything else defaults to PostgreSQL
Yes, stored procedures are fully supported including OUT parameters and multiple result sets:
Yes, you can use any MySQL-specific syntax:

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:
  1. 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.
  2. 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.