Unlocking Native SQLite Access in Bun Using ES Module Imports
In recent updates, Bun has been gaining traction for its speed, simplicity, and first-class TypeScript support. But one feature that often flies under the radar—and frankly feels like magic—is Bun’s ability to import SQLite databases as native modules using the ES module import attribute.
This is not just syntactic sugar. It removes layers of boilerplate, simplifies local development, and encourages rapid prototyping for projects that need lightweight persistence without spinning up a full database server.
A Fresh Way to Load SQLite in Bun
Traditionally, accessing SQLite in Node.js requires setting up a client library like better-sqlite3
or sqlite3
, initializing a connection, and managing file paths. But with Bun, it’s this simple:
import db from "./mydb.sqlite" with { type: "sqlite" };
console.log(db.query("SELECT * FROM users LIMIT 1").get());
Let’s break it down:
import db from "./mydb.sqlite"
: This line tells Bun to treat the SQLite file as a module. The magic happens in thewith { type: "sqlite" }
attribute.db.query(...).get()
: This is a synchronous query interface, powered internally by better-sqlite3, one of the fastest SQLite drivers for JavaScript.
There’s no need to manually open
the database, configure clients, or manage concurrency. You just import and query. It feels natural—like working with a native JavaScript object.
Why This Matters
1. Zero Setup
No need to npm install
any database driver if you're working with SQLite. Bun bundles support out of the box.
2. Instant Read/Write Access
For quick data operations, prototyping, or even production apps with simple needs, this approach is immediate and frictionless.
3. First-Class TypeScript Support
Type safety applies just as well here. You can even wrap query results in custom utility functions or types if needed.
Use Cases Where This Shines
- CLI Tools: You can persist settings or logs to a
.sqlite
file without setting up external dependencies. - Prototypes or MVPs: Store users, sessions, or content locally during early stages.
- Offline-First Apps: Great for Electron/Bun desktop apps that need local persistence.
- ETL Pipelines: Use SQLite as a temporary processing stage before exporting to another service.
Things You Might Miss (But Shouldn’t)
✅ Transactions
Bun’s db
object supports transaction()
blocks:
const insertUser = db.transaction((user) => {
db.query("INSERT INTO users (name) VALUES (?)").run(user.name);
});
This guarantees atomic operations, which are especially important in batch inserts or updates.
✅ Parameterized Queries
Always use placeholders to prevent SQL injection—even in local apps:
db.query("SELECT * FROM users WHERE email = ?").get("[email protected]");
✅ Bundling Considerations
If you're bundling the project (e.g., for production), make sure your .sqlite
file is included in the deployment. Use bunfig.toml
to mark it as an asset:
[assets]
include = ["mydb.sqlite"]
✅ File Location and Portability
Relative imports like ./mydb.sqlite
work great for dev, but be cautious about file paths when moving to production or cross-platform environments. Consider using an absolute path if needed, or a fallback strategy like:
import path from "path";
const dbPath = path.resolve(process.cwd(), "data/mydb.sqlite");
Unfortunately, this strategy won't work with the ES import attribute—you'd fall back to better-sqlite3
in this case.
⚠️ Limitations to Be Aware Of
- Only works in Bun: This is not standard ECMAScript, so it won’t work in Node.js, Deno, or browsers.
- Read/Write Locking: SQLite still uses file-level locking. Concurrent writes can block, although Bun manages this well.
- Not ideal for large-scale multi-user systems: SQLite is file-based, so if you're scaling to many concurrent users across multiple servers, it’s not the best choice.
Conclusion
Bun’s support for importing SQLite databases using ES module attributes is a breath of fresh air for developers who value simplicity and speed. It aligns perfectly with Bun’s goals of reducing friction, minimizing dependencies, and embracing native performance.
Whether you're building a tiny CLI, an offline-first app, or just need a fast way to persist data without extra setup, this feature is a gem worth knowing. And best of all? It just works.
So go ahead—ditch the boilerplate and start querying.
Comments ()