Bun JS SQLite Crash Course
If you want to see the completed source code for this post, check it out on GitHub: https://github.com/bytemyke/bun_sqlite_tutorial.
What is Bun?
Bun is a modern JavaScript runtime designed to be fast, simple, and all‑in‑one. It replaces (or complements) tools like Node.js, npm, and Webpack by bundling:
- A JavaScript runtime
- A package manager
- A bundler
- Built‑in APIs (like SQLite)
One of the coolest things about Bun is that SQLite support is built in. You don’t need to install third‑party libraries. You can just import it and start using it.
What is SQLite?
What is SQLite?
SQLite is a serverless, file‑based SQL database. Instead of running a separate database server (like MySQL or PostgreSQL), SQLite stores everything in a single .sqlite file.
SQLite is perfect for:
- Small projects
- Local development
- Prototypes
- Desktop apps
- Embedded systems
And because Bun supports SQLite natively, the setup is incredibly simple.
Environment Notes (Windows + WSL)
In the video, I’m running Windows 11 and using WSL (Windows Subsystem for Linux) to run Bun.
If you’re on Windows and don’t already have Bun working, I recommend:
- Installing WSL
- Installing Bun inside your Linux subsystem
I have a separate video tutorial that walks through this setup step‑by‑step. Check the video description if you need help getting Bun installed.
Project Setup
Create a new JavaScript file. You can name it whatever you want. I'll name mine bun-sql.js.
Importing SQLite from Bun
The first thing we need to do is import Bun’s SQLite database module.
import Database from "bun:sqlite";
This import works without installing anything. That’s one of the biggest advantages of Bun. In Node JS You'd have to install and manage a package to utilize SQLite.
Connecting to the Database
Now we can create a new database connection.
const db = new Database("db.sqlite");
A few important things to know here:
- If
db.sqlitedoes not exist, Bun will create it automatically - If it already exists, Bun will connect to it
- You do not need to manually create the file
To confirm everything is working, you can log the database and the run the script:
console.log(db);
Running the Script
From inside your Linux subsystem (WSL), run:
bun bun-sql.js
After running this:
- The script should execute without errors
- You should see a new
db.sqlitefile appear in your project directory
That confirms the database connection is working correctly.
Creating a Table
Next, we’ll create a simple test table.
const query = db.query(`CREATE TABLE test (val1 TEXT,val2 TEXT);`);query.run();
Make sure you include the semicolon (;) at the end of the SQL statement.
What Happens When You Run This?
- The first run creates the table successfully
- Running it again throws an error saying the table already exists
That error confirms the table was created correctly the first time. Once confirmed, you can safely comment this code out to avoid the error.
Inserting Data (Simple Insert)
Now let’s insert some data into the table using a basic query.
let query = db.query(`INSERT INTO test (val1, val2)VALUES ("simple test val", "another simple test val");`);query.run();
At this point, we don’t see any output, but since there were no errors, we can assume the data is inserted. We’ll verify that later when we query the database.
Inserting Data (Prepared Statements + Transactions)
Next, we’ll do a more advanced and scalable insert using prepared statements and transactions.
Preparing the Insert Statement
const insert = db.prepare(`INSERT INTO test (val1, val2)VALUES ($val1, $val2)`);
Notice the $val1 and $val2 placeholders. These allow us to pass objects instead of positional values.
Using a Transaction
const insertData = db.transaction((dataArray) => {for (const data of dataArray) {insert.run(data);}});
Transactions are important because they:
- Improve performance
- Ensure all inserts succeed or fail together
- Prevent partial writes
Running the Transaction
insertData([{ val1: "transaction val 1", val2: "transaction val 2" },{ val1: "transaction val 3", val2: "transaction val 4" },{ val1: "transaction val 5", val2: "transaction val 6" }]);
Important: The object keys must match the variable names in the prepared statement ($val1, $val2). If they don’t, the insert will fail.
At this point, we should now have four rows total in the database.
Selecting Data (Objects)
Now let’s query the database to confirm everything worked.
const query = db.query("SELECT * FROM test");const result = query.all();console.log(result);
This returns all rows as objects, which is usually the most convenient format when working with JavaScript.
Selecting Data (Arrays)
Sometimes you may want the data as arrays instead. Especially if you’re migrating code from Node.js.
const query = db.query("SELECT * FROM test");const result = query.values();console.log(result);
This returns the same data, but as arrays instead of objects.
Selecting a Single Row
If you only need one row, you can use get().
const query = db.query("SELECT * FROM test");const result = query.get();console.log(result);
This returns the first row in the result set. In this case, the first value we inserted.
Closing the Database
Finally, don’t forget to close the database connection.
db.close();
Even though this script is short lived, closing the database is a best practice, especially for long running applications like servers. Not closing a database connection can lead to a wide variety of errors like memory/resource waste, connection leaks, production bugs, and many others.
Overview
Bun + SQLite is an incredibly powerful combination:
- Zero setup
- No external dependencies
- Fast execution
- Clean APIs
If you’re building small services, tools, or local apps, this stack is absolutely worth considering.
If you found this helpful, be sure to subscribe to my Youtube channel for a wide variety of JavaScript tutorials, tips, and tricks!