The "Roman Concrete" of Software: Building 100-Year Systems with SQLite and WASM

Hey everyone, Alex here. Welcome back to another edition of Coding with Alex on sysseder.com.

If you've been browsing the tech news today, you might have spotted a fascinating archaeological headline: scientists are studying a 1,900-year-old Roman latrine to understand how Roman concrete has survived for millennia while our modern Portland cement starts crumbling after a few decades. The secret, it turns out, lies in "hot mixing" with quicklime, which gives the concrete a self-healing capability. When a crack forms, rainwater dissolves the tiny mineral reservoirs (lime clasts) inside the concrete, recrystallizing them into the gaps and sealing the breach automatically.

As software engineers, this got me thinking. Our industry is notoriously obsessed with the "new." We rewrite frontends every three years, migration paths for cloud providers are a constant headache, and the libraries we wrote five years ago are often entirely deprecated today. But what if we wanted to build software that lasts? What is the digital equivalent of Roman concrete—a technology stack so durable, self-healing, and resilient that it could run, untouched and fully functional, for decades or even centuries?

Today, we are going to design and build a "100-year software architecture." We'll explore why the combination of SQLite and WebAssembly (WASM) is the closest thing we have to digital lime clasts, and write a practical, self-contained application designed to survive the digital ages.

The Anatomy of Software Decay

Before we build, we have to understand why modern software decays. Concrete degrades due to chemical weathering, physical stress, and poor foundational mixtures. Software degrades due to:

  • API Rot: External SaaS APIs change, deprecate endpoints, or go out of business.
  • Runtime Deprecation: Language runtimes (Node.js, Python, Ruby) move forward, breaking backwards compatibility.
  • Hardware Shifts: Operating systems and CPU architectures change (e.g., the shift from x86 to ARM, or future quantum/RISC architectures).
  • Network Dependence: If your app requires an active internet connection to a centralized server to function, its lifespan is tied directly to the financial survival of whoever pays that server bill.

To combat this, our digital Roman concrete needs three properties: it must be completely self-contained, compile to an architecture-agnostic virtual machine, and store its data in a single, universally readable file format. Enter WebAssembly and SQLite.

Our Self-Healing Stack: WASM & SQLite

Why these two? Let's look at their credentials:

1. SQLite: The Ultimate Storage Standard

SQLite is literally designed for longevity. The SQLite consortium has explicitly stated their commitment to keeping the file format readable and supported until at least the year 2050. The database is stored in a single, cross-platform file. Even if every SQL parser on Earth disappeared, the file format is so well-documented that a developer could write a parser from scratch using the specification in a weekend.

2. WebAssembly (WASM): The Universal Runtime

WASM is a sandboxed, hardware-independent instruction format. It is a W3C standard. Because it is designed to run in any browser or standalone runtime (like Wasmtime), we are no longer tethered to a specific operating system or CPU architecture. If a system can run a basic WASM interpreter, it can run our code—whether it's on a 2024 MacBook, a 2050 smart fridge, or a retrofitted legacy system 100 years from now.

Architecting the Durable Ledger

Let's build a practical example. We will create a local-first, self-contained "Durable Ledger" app. It will run entirely in the browser using WASM, write directly to an in-memory SQLite database, and serialize its state to an immutable file that the user can save locally. No servers, no external APIs, no npm-install hell on startup.

Here is how the architecture looks conceptually:

+--------------------------------------------------------+
|                      User Browser                      |
|                                                        |
|  +------------------+      +------------------------+  |
|  |     HTML5 UI     | <--> |  WASM Runtime (Go/C)   |  |
|  +------------------+      +------------------------+  |
|                                        |               |
|                                        v               |
|                            +------------------------+  |
|                            |   SQLite (In-Memory)   |  |
|                            +------------------------+  |
|                                        |               |
|                                        v               |
|                            +------------------------+  |
|                            |  Local Storage / File  |  |
|                            +------------------------+  |
+--------------------------------------------------------+

Step-by-Step Implementation

For this implementation, we'll write our core logic in Go, compile it to WASM, and use a pure Go port of SQLite (which doesn't require CGO, making WASM compilation incredibly straightforward).

Step 1: The Go Core Code (main.go)

First, we create a Go application that initializes an in-memory SQLite database, creates a table, inserts some data, and exposes these operations to JavaScript via WASM bindings.

package main

import (
	"database/sql"
	"fmt"
	"syscall/js"

	_ "modernc.org/sqlite" // Pure Go SQLite driver (CGO-free)
)

var db *sql.DB

// Initialize the self-healing SQLite database
func initDatabase() error {
	var err error
	db, err = sql.Open("sqlite", ":memory:")
	if err != nil {
		return err
	}

	// Create table
	query := `
	CREATE TABLE IF NOT EXISTS journal (
		id INTEGER PRIMARY KEY AUTOINCREMENT,
		timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
		entry TEXT
	);`
	_, err = db.Exec(query)
	return err
}

// Wrapper to expose entry creation to JavaScript
func addJournalEntry(this js.Value, args []js.Value) interface{} {
	if len(args) < 1 {
		return "Error: Missing entry text"
	}
	entryText := args[0].String()

	_, err := db.Exec("INSERT INTO journal (entry) VALUES (?)", entryText)
	if err != nil {
		return fmt.Sprintf("Error writing to SQLite: %v", err)
	}

	return "Entry successfully written to SQLite!"
}

// Wrapper to retrieve and format all entries
func getJournalEntries(this js.Value, args []js.Value) interface{} {
	rows, err := db.Query("SELECT id, timestamp, entry FROM journal ORDER BY id DESC")
	if err != nil {
		return fmt.Sprintf("Error reading from SQLite: %v", err)
	}
	defer rows.Close()

	var result string
	for rows.Next() {
		var id int
		var timestamp, entry string
		err = rows.Scan(&id, ×tamp, &entry)
		if err != nil {
			return fmt.Sprintf("Error scanning row: %v", err)
		}
		result += fmt.Sprintf("[%s] (#%d): %s\n", timestamp, id, entry)
	}
	return result
}

func main() {
	// Keep the Go runtime alive in WASM
	c := make(chan struct{}, 0)

	fmt.Println("Initializing digital concrete...")
	err := initDatabase()
	if err != nil {
		fmt.Printf("Database initialization failed: %v\n", err)
		return
	}
	fmt.Println("SQLite initialized successfully in-memory via WASM!")

	// Register functions to global JS scope
	js.Global().Set("addJournalEntry", js.FuncOf(addJournalEntry))
	js.Global().Set("getJournalEntries", js.FuncOf(getJournalEntries))

	<-c
}

Step 2: Compiling to WASM

To compile this Go code into a highly portable WASM binary, we run the following command in our terminal:

GOOS=js GOARCH=wasm go build -o main.wasm main.go

This generates a main.wasm file. Because WASM is sandboxed and highly optimized, this file contains the entire application logic, the database engine (SQLite), and the driver, compiled down to bytecode. No external system dependencies required!

Step 3: The Universal Frontend (index.html)

Now, let's create our HTML5 wrapper. Notice that we don't import heavy framework libraries. No React, no Tailwind CDN, no build pipelines. We use pure HTML, CSS, and native JavaScript to ensure this UI can render perfectly on any browser engine, today or thirty years from now.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>The 100-Year Journal</title>
    <style>
        body {
            font-family: monospace;
            background-color: #121212;
            color: #00ff00;
            padding: 2rem;
            max-width: 800px;
            margin: 0 auto;
        }
        h1 {
            border-bottom: 2px solid #00ff00;
            padding-bottom: 0.5rem;
        }
        textarea {
            width: 100%;
            height: 100px;
            background-color: #1e1e1e;
            border: 1px solid #00ff00;
            color: #00ff00;
            padding: 0.5rem;
            font-family: monospace;
            box-sizing: border-box;
        }
        button {
            background-color: #00ff00;
            color: #121212;
            border: none;
            padding: 0.5rem 1rem;
            font-weight: bold;
            cursor: pointer;
            margin-top: 0.5rem;
        }
        button:hover {
            background-color: #00cc00;
        }
        pre {
            background-color: #1e1e1e;
            padding: 1rem;
            border: 1px dashed #00ff00;
            white-space: pre-wrap;
        }
    </style>
    <!-- Include the standard Go WASM support file -->
    <script src="wasm_exec.js"></script>
</head>
<body>

    <h1>馃彌️ The 100-Year Journal (WASM + SQLite)</h1>
    <p>This application runs entirely in your browser memory. No tracking, no servers, completely localized.</p>

    <h3>New Entry</h3>
    <textarea id="entryText" placeholder="Write something that will last..."></textarea>
    <button onclick="saveEntry()">Commit to SQLite</button>

    <h3>Database Records</h3>
    <pre id="output">Loading digital concrete (WASM module)...</pre>

    <script>
        const go = new Go();
        WebAssembly.instantiateStreaming(fetch("main.wasm"), go.importObject).then((result) => {
            go.run(result.instance);
            refreshLogs();
        }).catch(err => {
            document.getElementById("output").innerText = "Failed to load WASM: " + err;
        });

        function saveEntry() {
            const text = document.getElementById("entryText").value;
            if (!text) return;
            
            const status = addJournalEntry(text);
            alert(status);
            document.getElementById("entryText").value = "";
            refreshLogs();
        }

        function refreshLogs() {
            const data = getJournalEntries();
            document.getElementById("output").innerText = data || "No records found in database.";
        }
    </script>
</body>
</html>

Why This Design is Self-Healing and Future-Proof

Think about the traditional way this app is built today: a React frontend, a Node.js Express API, a Dockerized PostgreSQL database, an ORM like Prisma, hosted on AWS ECS, with auth handled by Auth0. If any of those companies pivot, shut down, change their pricing, or push a breaking major version release, your app breaks.

Our WASM-SQLite architecture mimics Roman concrete in three distinct ways:

1. Structural Inertia (Offline-First)

Because there is no network layer, network latency is 0ms. There are no API endpoints to deprecate. The data is processed directly inside the user's browser sandbox. The only external requirement is HTTP-serving of three static files (index.html, wasm_exec.js, and main.wasm), which can easily be zipped, stored on a USB drive, or pinned to IPFS.

2. The "Lime Clast" Safe Fail

If the user's web browser crashes, or the computer loses power, the file system isn't corrupted. SQLite uses a rollback journal or Write-Ahead Log (WAL). If a transaction is interrupted, the database engine simply rolls back to the last known valid state on the next initialization. It literally self-heals from unexpected runtime failures.

3. Extreme Portability

If modern web browsers are replaced by some futuristic spatial computing OS in thirty years, you won't need to rewrite this application. You will only need a WASM runtime designed for that new OS to execute the compiled main.wasm bytecode. Your business logic and database state remain completely unchanged.

Conclusion: Designing for Decades

We shouldn't build every project this way. Fast-moving startups need rapid iteration, and heavily collaborative systems require robust cloud-native synchronizations. But next time you are building a personal productivity tool, a document store, a local configuration engine, or archiving critical historical data, ask yourself: "Am I building a modern concrete highway that will crack in ten years, or a Roman road that will endure for generations?"

By shifting critical application logic to compile-once WASM runtimes and anchoring state inside a single, standardized SQLite container, we can build digital artifacts that survive the shifting tides of the web ecosystem.

What are your thoughts? Have you experimented with compiling databases directly to the client side? How are you designing your applications to survive the next decade of tech shifts? Let me know in the comments below!

Until next time, keep coding smart.

— Alex

Post a Comment

Previous Post Next Post