SQLite is the most deployed database in the world — 1 trillion+ databases in production. It runs in-process, needs zero configuration, and it's completely free. In 2026, it's also becoming the go-to database for web apps.
Why SQLite in 2026?
The "SQLite Renaissance" is real:
- Turso — distributed SQLite at the edge
- LiteFS — replicated SQLite by Fly.io
- Litestream — streaming SQLite replication
- D1 — Cloudflare's SQLite-based database
- libSQL — open-source fork with more features
- Better-sqlite3 — synchronous, 5x faster than node-sqlite3
Quick Start (Node.js)
npm install better-sqlite3
import Database from "better-sqlite3";
const db = new Database("app.db");
// Enable WAL mode (concurrent reads while writing)
db.pragma("journal_mode = WAL");
// Create table
db.exec(`
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
created_at TEXT DEFAULT (datetime('now'))
)
`);
// Insert
const insert = db.prepare("INSERT INTO users (name, email) VALUES (?, ?)");
insert.run("Alice", "alice@example.com");
// Batch insert (transaction — 100x faster)
const insertMany = db.transaction((users) => {
for (const user of users) {
insert.run(user.name, user.email);
}
});
insertMany([
{ name: "Bob", email: "bob@example.com" },
{ name: "Charlie", email: "charlie@example.com" },
]);
// Query
const users = db.prepare("SELECT * FROM users WHERE name LIKE ?").all("%li%");
console.log(users);
// Single row
const user = db.prepare("SELECT * FROM users WHERE id = ?").get(1);
Full-Text Search (Built-in!)
// Create FTS5 virtual table
db.exec(`
CREATE VIRTUAL TABLE IF NOT EXISTS articles_fts USING fts5(
title, content, tags
)
`);
// Insert articles
const insertArticle = db.prepare(
"INSERT INTO articles_fts (title, content, tags) VALUES (?, ?, ?)"
);
insertArticle.run("Getting Started with SQLite", "SQLite is a lightweight database...", "sqlite database tutorial");
insertArticle.run("React State Management", "Managing state in React apps...", "react state zustand");
// Full-text search with ranking
const results = db.prepare(`
SELECT *, rank FROM articles_fts
WHERE articles_fts MATCH ?
ORDER BY rank
LIMIT 10
`).all("sqlite database");
No Elasticsearch. No Algolia. Built into SQLite.
JSON Support
// Store JSON data
db.exec(`
CREATE TABLE IF NOT EXISTS events (
id INTEGER PRIMARY KEY,
data JSON NOT NULL,
created_at TEXT DEFAULT (datetime('now'))
)
`);
db.prepare("INSERT INTO events (data) VALUES (json(?))").run(
JSON.stringify({
type: "page_view",
page: "/pricing",
user: { id: 123, plan: "pro" },
metadata: { referrer: "google.com" },
})
);
// Query JSON fields
const pageViews = db.prepare(`
SELECT
json_extract(data, '$.type') as event_type,
json_extract(data, '$.page') as page,
json_extract(data, '$.user.plan') as plan
FROM events
WHERE json_extract(data, '$.type') = 'page_view'
`).all();
Express.js API with SQLite
import express from "express";
import Database from "better-sqlite3";
const app = express();
app.use(express.json());
const db = new Database("api.db");
db.pragma("journal_mode = WAL");
db.exec(`
CREATE TABLE IF NOT EXISTS todos (
id INTEGER PRIMARY KEY AUTOINCREMENT,
text TEXT NOT NULL,
completed INTEGER DEFAULT 0,
created_at TEXT DEFAULT (datetime('now'))
)
`);
app.get("/api/todos", (req, res) => {
const todos = db.prepare("SELECT * FROM todos ORDER BY created_at DESC").all();
res.json(todos);
});
app.post("/api/todos", (req, res) => {
const { text } = req.body;
const result = db.prepare("INSERT INTO todos (text) VALUES (?)").run(text);
const todo = db.prepare("SELECT * FROM todos WHERE id = ?").get(result.lastInsertRowid);
res.status(201).json(todo);
});
app.patch("/api/todos/:id", (req, res) => {
const { completed } = req.body;
db.prepare("UPDATE todos SET completed = ? WHERE id = ?").run(completed ? 1 : 0, req.params.id);
const todo = db.prepare("SELECT * FROM todos WHERE id = ?").get(req.params.id);
res.json(todo);
});
app.delete("/api/todos/:id", (req, res) => {
db.prepare("DELETE FROM todos WHERE id = ?").run(req.params.id);
res.status(204).send();
});
app.listen(3000);
Migrations
const MIGRATIONS = [
`CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
email TEXT UNIQUE
)`,
`ALTER TABLE users ADD COLUMN role TEXT DEFAULT 'user'`,
`CREATE INDEX IF NOT EXISTS idx_users_email ON users(email)`,
`CREATE TABLE IF NOT EXISTS posts (
id INTEGER PRIMARY KEY,
user_id INTEGER REFERENCES users(id),
title TEXT NOT NULL,
content TEXT,
created_at TEXT DEFAULT (datetime('now'))
)`,
];
function migrate(db) {
db.exec("CREATE TABLE IF NOT EXISTS _migrations (id INTEGER PRIMARY KEY)");
const applied = db.prepare("SELECT MAX(id) as last FROM _migrations").get();
const lastId = applied?.last || 0;
const pending = MIGRATIONS.slice(lastId);
if (pending.length === 0) return;
const run = db.transaction(() => {
for (let i = 0; i < pending.length; i++) {
db.exec(pending[i]);
db.prepare("INSERT INTO _migrations (id) VALUES (?)").run(lastId + i + 1);
}
});
run();
console.log(`Applied ${pending.length} migrations`);
}
SQLite vs PostgreSQL vs MySQL vs MongoDB
| Feature | SQLite | PostgreSQL | MySQL | MongoDB |
|---|---|---|---|---|
| Setup | Zero (file) | Server | Server | Server |
| Concurrency | WAL (good reads) | Excellent | Good | Good |
| JSON support | Yes | Yes (JSONB) | Yes | Native |
| Full-text search | FTS5 (built-in) | tsvector | FULLTEXT | Atlas Search |
| Replication | Turso/LiteFS | Built-in | Built-in | Built-in |
| Best for | Single-server apps | Multi-server | Web apps | Documents |
| Max size | 281 TB | Unlimited | Unlimited | Unlimited |
Need to scrape data from any website and get it in structured JSON? Check out my web scraping tools on Apify — no coding required, results in minutes.
Have a custom data extraction project? Email me at spinov001@gmail.com — I build tailored scraping solutions for businesses.
Top comments (0)