Hotdry.
application-security

受 Sivers 启发的轻量级 SQLite 驱动 Web 应用构建

借鉴 Derek Sivers 的个人数据库哲学,使用 SQLite 构建任务管理和内容 curation 的简约 web 应用,强调数据主权与零配置部署。

在数字时代,我们常常被复杂的框架和云服务淹没,但 Derek Sivers 的方法提供了一种回归本质的路径:用单一数据库驱动整个个人应用系统,确保数据完全在自己掌控中。Sivers 在他的 GitHub 仓库中展示了如何用 PostgreSQL 构建一个自给自足的 web 生态,但对于个人规模的任务管理和内容 curation,轻量级的 SQLite 更合适。它无需服务器,数据存于单个文件,支持事务和 SQL 查询,完美契合数据主权原则。本文将探讨如何受 Sivers 启发,构建 minimalist SQLite-driven web apps,使用 vanilla JS 前端和边缘函数,实现零配置部署。

首先,理解 Sivers 的核心理念。他的系统将数据库作为应用的核心,不仅存储数据,还生成 HTML 输出和处理 HTTP 响应。这减少了层级依赖,避免了框架的臃肿。在 Sivers 的仓库中,PostgreSQL 函数直接返回 head 和 body 文本,用于构建完整响应。这种方法强调简洁:代码在数据库中,应用逻辑封装在函数里。对于 SQLite,我们可以类似地设计:用 SQL 函数或简单查询驱动应用,但由于 SQLite 更嵌入式,我们将逻辑移到 JS 层,利用其零配置特性。

为什么选择 SQLite?它自包含、无服务器,文件大小小巧,适合个人使用。相比 Sivers 的 Postgres,SQLite 更易于本地开发和备份 —— 只需复制 .db 文件即可实现数据迁移。这确保了数据主权:你的任务列表或 curation 内容不会被第三方云锁定。根据 SQLite 官方文档,它支持高达 2TB 的数据库大小,足以应对个人需求,且在嵌入式场景中性能出色。Sivers 的哲学在这里适用:优先简单性和可移植性,而不是企业级扩展。

让我们从任务管理 app 开始设计 schema。创建一个 tasks.db 文件,使用 SQLite CLI 或工具如 DB Browser for SQLite 初始化:

CREATE TABLE tasks (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    title TEXT NOT NULL,
    description TEXT,
    status TEXT DEFAULT 'pending',  -- pending, in-progress, done
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX idx_status ON tasks(status);

这个 schema 简单明了,支持基本 CRUD 操作。受 Sivers 启发,我们添加触发器自动更新 timestamp:

CREATE TRIGGER update_timestamp
AFTER UPDATE ON tasks
FOR EACH ROW
BEGIN
    UPDATE tasks SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
END;

对于内容 curation,如书签或笔记,添加另一个表:

CREATE TABLE curations (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    title TEXT NOT NULL,
    url TEXT,
    content TEXT,
    tags TEXT,  -- comma-separated
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX idx_tags ON curations(tags);

这些表保持扁平,避免复杂关系,符合 Sivers 的 “少即是多” 原则。数据完整性通过约束和索引确保,无需额外 ORM。

前端使用 vanilla JS,无需 React 或 Vue。假设我们用一个单页应用 (SPA) 结构:index.html 加载 app.js,连接到 SQLite via sql.js (一个 WebAssembly 端口的 SQLite,允许浏览器直接操作 .db 文件)。但为数据主权,我们优先本地或边缘部署。

在 app.js 中,实现任务列表:

// 加载 sql.js
async function initDb() {
    const SQL = await initSqlJs({
        locateFile: file => `https://sql.js.org/dist/${file}`
    });
    const response = await fetch('tasks.db');
    const buffer = await response.arrayBuffer();
    const db = new SQL.Database(new Uint8Array(buffer));
    return { SQL, db };
}

// 显示任务
async function loadTasks() {
    const { db } = await initDb();
    const res = db.exec('SELECT * FROM tasks ORDER BY created_at DESC');
    const tasks = res[0]?.values || [];
    const list = document.getElementById('task-list');
    list.innerHTML = tasks.map(row => `
        <li>${row[1]} - ${row[2]} (${row[3]})</li>
    `).join('');
}

// 添加任务
document.getElementById('add-task').addEventListener('click', async () => {
    const { db } = await initDb();
    const title = document.getElementById('title').value;
    db.run('INSERT INTO tasks (title, description) VALUES (?, ?)', [title, '']);
    // 保存 db 到文件或发送到边缘函数更新
    loadTasks();
});

这个代码直接在浏览器中操作,但为持久化,我们用边缘函数处理更新。边缘函数如 Cloudflare Workers 或 Deno Deploy,提供零配置:上传脚本,无需服务器管理。

例如,在 Cloudflare Worker 中,实现 API:

// worker.js
addEventListener('fetch', event => {
    event.respondWith(handleRequest(event.request));
});

async function handleRequest(request) {
    if (request.method === 'POST') {
        const data = await request.json();
        // 这里连接到 KV 或 D1 (Cloudflare 的 SQLite),但为纯 SQLite,用文件系统模拟或外部服务
        // 简化:假设用 Durable Objects 或外部 SQLite 托管
        // 执行 INSERT 等
        return new Response('Task added', { status: 200 });
    }
    // GET 返回 JSON 数据,JS fetch 加载
}

为零配置部署:用 Vercel 或 Netlify 托管静态文件,边缘函数集成。Sivers 的方法在这里闪光:后端逻辑最小化,前端直接查询 DB 文件(via sql.js for read-only)或 API for writes。

参数和清单:

  • 部署清单

    1. 初始化 SQLite DB:用 sqlite3 CLI 创建 schema。
    2. 构建前端:HTML + vanilla JS + sql.js for client-side queries。
    3. 边缘函数:用 Wrangler (Cloudflare) 部署 API,处理 writes 到托管 DB (如 Turso 或本地文件 via edge)。
    4. 托管:Netlify drop 文件夹,自动构建。
    5. 备份:cron 脚本复制 .db 到 Git 或 S3。
  • 监控点

    • 查询性能:SQLite 默认 WAL 模式,提升并发读。
    • 数据安全:加密 .db 文件,用 passphrase。
    • 阈值:任务 >1000 时,考虑迁移到 Postgres,但保持简单。
  • 回滚策略:版本化 DB 文件,git commit 每个变更;测试环境用 db.copy ()。

这种方法确保应用轻量:总大小 <1MB,启动即用。Sivers 在他的 tech blog 中写道:“Simplify: move code into database functions”,这启发我们即使在 SQLite,也用视图和函数封装逻辑,如 CREATE VIEW active_tasks AS SELECT * FROM tasks WHERE status != 'done'。

风险:SQLite 单文件锁定可能在高写时瓶颈,但个人 app 罕见。相比云 DB,它避免订阅费和供应商锁。

最后,这种构建方式体现了 Sivers 的精神:拥有你的数据,构建你需要的工具,而非追逐潮流。

资料来源:

查看归档