DEV Community

Bechattaoui Dhiaeddine
Bechattaoui Dhiaeddine

Posted on

How I built an offline, privacy-first receipt scanner using Rust, Tauri, and WebAssembly OCR

I love the idea of subscription and warranty trackers. But the irony of paying a monthly cloud subscription just to track my other subscriptions—while handing over my personal financial data—always bothered me.

I wanted a "Life Ops" command center that was completely local, ridiculously fast, and actually respected my privacy.

So, I built OpenAdmin. It’s an open-source, local-first dashboard built with Tauri, React, and Rust. It features an embedded SQLite database, a beautiful Glassmorphism UI, and—the coolest part—completely offline receipt OCR via WebAssembly.

Here is a breakdown of how I built it and the technical decisions behind it.

The Architecture: Why Tauri?

For a long time, Electron was the default for web-tech desktop apps. But for a simple dashboard, shipping a bundled Chromium browser that eats 500MB of RAM was out of the question.

I chose Tauri v2 because it hooks into the native OS webview (WebKit on Mac, WebView2 on Windows), resulting in:

  • A tiny binary size.
  • Negligible RAM usage.
  • A blazing-fast backend written in Rust.

The architecture is simple:

  • Frontend: React + TypeScript + Vanilla CSS (for that sweet custom glassmorphism).
  • Backend: Rust.
  • Database: Local SQLite (rusqlite) embedded directly into the app.

The Challenge: Offline OCR

The standout feature of OpenAdmin is the ability to drag and drop a receipt, have the app scan it, and automatically extract the "Total Cost" and raw text without ever making a network request to an API like Google Cloud Vision or AWS Textract.

To achieve this entirely offline, I used Tesseract.js.

Despite the .js in the name, Tesseract.js is actually a WebAssembly (WASM) port of the famous C++ Tesseract OCR engine. It runs directly inside the Tauri Webview thread.

How the implementation works:

  1. The Dropzone: A React component listens for the onDrop event when a user drops an image file.
  2. The Worker: We spin up a Tesseract WebAssembly worker.
import Tesseract from 'tesseract.js';

const performOCR = async (file: File) => {
  // Runs 100% locally in the browser/webview via WASM
  const { data: { text } } = await Tesseract.recognize(file, 'eng');
  return text;
};
Enter fullscreen mode Exit fullscreen mode
  1. The Regex Extraction: Once we have the raw text blob, we run a regular expression to intelligently hunt for the final total cost, looking for keywords like "Total" followed by a currency symbol.
const match = text.match(/(?:Total|Amount|Sum)[\s:]*?\$?\s*([0-9,]+\.[0-9]{2})/i);
if (match) {
  const extractedCost = parseFloat(match[1].replace(/,/g, ''));
}
Enter fullscreen mode Exit fullscreen mode

Because this runs entirely client-side, it costs $0 in API fees and your receipt data never leaves your hard drive.

Rust Backend: SQLite & Native Notifications

While the frontend handles the UX and WASM, Rust does the heavy lifting for persistence and system-level integrations.

I used the rusqlite crate to automatically initialize a local .db file in the user's hidden app data directory. We use Tauri's #[tauri::command] macro to expose database operations to the React frontend.

But we didn't stop at just CRUD operations. We wanted the app to actively alert you when a warranty or subscription was about to expire.

We spawned a background Rust thread during Tauri setup that wakes up periodically, queries the SQLite database for items expiring within 7 days, and uses tauri-plugin-notification to trigger a native Mac OS push notification.

tauri::async_runtime::spawn(async move {
    loop {
        // Query SQLite for expiring items
        if let Some(item) = get_expiring_item(&db) {
            // Trigger native OS notification
            app_handle.notification()
                .title("Warranty Expiring!")
                .body(&format!("Your {} is expiring soon.", item.name))
                .show()
                .unwrap();
        }
        tokio::time::sleep(Duration::from_secs(86400)).await; // Sleep for a day
    }
});
Enter fullscreen mode Exit fullscreen mode

The Result

The end result is OpenAdmin: a lightning-fast, highly secure, zero-telemetry desktop application that solves a real-world problem without sacrificing user privacy.

Building with Tauri + Rust forces you to think carefully about where your data lives and how it moves, and WebAssembly is proving to be a superpower for bringing complex server-side capabilities (like OCR) directly to the edge.

Check it out!

If you're interested in local-first apps, Rust, or just want a cool dashboard to track your own subscriptions without giving away your data, check out the source code!

⭐ View OpenAdmin on GitHub

I'd love to hear your thoughts, feedback, or any Pull Requests!

Top comments (0)