<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: networkingguru</title>
    <description>The latest articles on DEV Community by networkingguru (@networkingguru).</description>
    <link>https://dev.to/networkingguru</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3872224%2F883ff880-c0b8-4066-bea4-419b7d8dd155.jpeg</url>
      <title>DEV Community: networkingguru</title>
      <link>https://dev.to/networkingguru</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/networkingguru"/>
    <language>en</language>
    <item>
      <title>How I Built a Web Interface for 1.4 Million Government Documents with FastAPI, HTMX, and SQLite</title>
      <dc:creator>networkingguru</dc:creator>
      <pubDate>Fri, 10 Apr 2026 17:51:21 +0000</pubDate>
      <link>https://dev.to/networkingguru/how-i-built-a-web-interface-for-14-million-government-documents-with-fastapi-htmx-and-sqlite-1ofm</link>
      <guid>https://dev.to/networkingguru/how-i-built-a-web-interface-for-14-million-government-documents-with-fastapi-htmx-and-sqlite-1ofm</guid>
      <description>&lt;p&gt;When government agencies release the same document multiple times with different redaction patterns — which happens more often than you'd think across FOIA batches, Congressional releases, and litigation disclosures — it's possible to cross-reference the releases and algorithmically recover the hidden text. I built a  tool called Unobfuscator to do exactly this with the entire Epstein corpus.&lt;/p&gt;

&lt;p&gt;The problem is, Unobfuscator's output is a SQLite database. This is annoying but workable if you're a developer. It's useless if you're a journalist, investigator, or anyone else who actually needs to find things in 1.4 million documents.&lt;/p&gt;

&lt;p&gt;So I built TEREDACTA — a web interface that makes those recoveries searchable and explorable.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkv929dy9tki42p81xmwi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkv929dy9tki42p81xmwi.png" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Dataset
&lt;/h2&gt;

&lt;p&gt;The current deployment covers the Congressional Epstein/Maxwell releases: DOJ volumes, House Oversight releases. 1.4 million documents, 15,220 document match groups, 5,600+ substantive recovered passages.&lt;/p&gt;

&lt;p&gt;Some of what's been recovered is genuinely significant — internal BOP/MCC emails about Epstein's case, staff interview lists mapping shifts at the Manhattan Correctional Center, FBI evidence logs with 113 recovered passages, and Ghislaine Maxwell's own PR response drafts.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Stack
&lt;/h2&gt;

&lt;p&gt;Here's the part that might surprise you: FastAPI + HTMX + Jinja2 + SQLite. That's it. No React, no Vue, no webpack, no npm, no build step. The entire frontend is HTMX with vendored JS and server-rendered templates.&lt;/p&gt;

&lt;p&gt;It sounds like it shouldn't work for an interactive investigation tool, but it does.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why HTMX
&lt;/h2&gt;

&lt;p&gt;I'll be honest — when I started this project, I assumed I'd end up reaching for React or at least Alpine.js. The feature requirements looked like they needed a proper frontend framework: boolean search with real-time results, an entity relationship explorer, a document viewer with highlighted recovered passages, progress indicators for long-running operations.&lt;/p&gt;

&lt;p&gt;HTMX handles all of it. Partial page updates via &lt;code&gt;hx-get&lt;/code&gt; and &lt;code&gt;hx-swap&lt;/code&gt;. Server-sent events for real-time progress on operations that take more than a moment. The result is an interface that feels reactive without shipping a single line of application JavaScript.&lt;/p&gt;

&lt;p&gt;The advantage isn't just simplicity — it's debuggability. When something breaks, there's no component tree to inspect, no state management layer to untangle, no build pipeline to suspect. It's HTTP requests and HTML responses. The browser's network tab tells you everything.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Performance Problem
&lt;/h2&gt;

&lt;p&gt;Boolean search across 1.4 million documents needs to return results fast enough that investigators don't lose their train of thought. "Fast enough" in this context means under 2 seconds, and ideally under half a second.&lt;/p&gt;

&lt;p&gt;SQLite is the database, and SQLite is single-writer. For a read-heavy investigation tool, this is actually fine — reads are concurrent and the dataset is static (new documents get added in batches, not continuously). But the query planning needed careful attention.&lt;/p&gt;

&lt;p&gt;The entity index — people, organizations, locations, emails, phone numbers extracted from recovered text — lives in a separate SQLite database. This was a deliberate choice for query isolation. The entity queries (which involve relationship traversal) have very different access patterns than the document search queries, and separating them means neither workload contaminates the other's page cache.&lt;/p&gt;

&lt;p&gt;Cold-cache performance was the real challenge. First query after a restart could take 10+ seconds as SQLite populated its page cache. The fix was careful index design and strategic &lt;code&gt;PRAGMA&lt;/code&gt; tuning — &lt;code&gt;mmap_size&lt;/code&gt; for memory-mapped I/O, &lt;code&gt;cache_size&lt;/code&gt; for the page cache — rather than adding an external caching layer. Adding Redis or Memcached to what is otherwise a zero-dependency Python app would've been architectural vandalism.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security Considerations
&lt;/h2&gt;

&lt;p&gt;This is a tool that lets people search through government documents. The security model needs to be airtight not because the data is secret (it's publicly released), but because the tool could be a target.&lt;/p&gt;

&lt;p&gt;Authentication uses signed cookies with CSRF tokens. The Unobfuscator database is read-only — the application has no write access to the source data. Input validation includes regex backtracking prevention (a real attack vector against search tools that accept user-supplied patterns). The whole thing runs behind Caddy with automatic TLS.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd Do Differently
&lt;/h2&gt;

&lt;p&gt;The SSE implementation for real-time progress went through three iterations before I got the connection management right. Server-sent events sound simple, but handling client disconnection, reconnection, and the inevitable proxy buffering issues (Caddy, Cloudflare, etc.) required more thought than I expected. I'd document this pattern better from the start.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;Everything is MIT licensed. The methodology is transparent and auditable — this is algorithmically recovered text from publicly released documents, not guessed or AI-generated content.&lt;/p&gt;

&lt;p&gt;[&lt;a href="https://teredacta.counting-to-infinity.com/" rel="noopener noreferrer"&gt;Live site&lt;/a&gt;] | [&lt;a href="https://github.com/networkingguru/TEREDACTA" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;]&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>python</category>
      <category>webdev</category>
      <category>opensource</category>
    </item>
    <item>
      <title>How I Built a Tinder-Style Group Decision App with React Native and Firebase</title>
      <dc:creator>networkingguru</dc:creator>
      <pubDate>Fri, 10 Apr 2026 17:39:13 +0000</pubDate>
      <link>https://dev.to/networkingguru/how-i-built-a-tinder-style-group-decision-app-with-react-native-and-firebase-1p37</link>
      <guid>https://dev.to/networkingguru/how-i-built-a-tinder-style-group-decision-app-with-react-native-and-firebase-1p37</guid>
      <description>&lt;p&gt;My wife and I have a problem (no, not THAT kind of problem). &lt;/p&gt;

&lt;p&gt;It's the same problem every couple has: nobody can decide where to eat. Or what movie to watch. Or what show to binge next. &lt;/p&gt;

&lt;p&gt;The conversation follows a depressingly predictable script — "I don't care, what do you want?" repeated ad infinitum until someone either picks something out of frustration or you just stay home.&lt;/p&gt;

&lt;p&gt;So I built an app to solve it. WhaTo lets a group of up to 8 people join a session with a 4-letter code, swipe through options (restaurants, movies, or TV shows), and find out what they agree on. Like Tinder, but for dinner.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8dxvh2cad1vyua3kf39g.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8dxvh2cad1vyua3kf39g.gif" alt=" " width="350" height="757"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Framework:&lt;/strong&gt; React Native with Expo (cross-platform iOS, Android, web)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real-time sync:&lt;/strong&gt; Firebase Realtime Database&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API proxy:&lt;/strong&gt; Cloudflare Worker (routes calls to Yelp, TMDB, Google Places)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Animations:&lt;/strong&gt; React Native Gesture Handler + Reanimated&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Testing:&lt;/strong&gt; Jest + React Native Testing Library + Maestro (E2E)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Interesting Problems
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Real-Time Sync for 8 Concurrent Users
&lt;/h3&gt;

&lt;p&gt;The core requirement was that everyone swipes simultaneously and sees results the instant the last person finishes. Firebase Realtime Database handles presence tracking and swipe state broadcast, but the matching algorithm runs client-side. Each client independently computes matches as swipe data arrives from other users. The server just broadcasts state changes.&lt;/p&gt;

&lt;p&gt;This was a deliberate choice. Running the matching algorithm server-side would add a round-trip penalty on every swipe completion, and the algorithm itself is lightweight — it's just set intersection. The tradeoff is that every client computes the same result independently, which is redundant work, but the latency improvement is worth it. Results appear instantly as the last person finishes swiping, with no perceptible delay.&lt;/p&gt;

&lt;p&gt;Session management was its own challenge. Sessions auto-expire after 24 hours via Firebase TTL rules. The 4-letter codes need to be unique within the active session window but recyclable after expiry — I didn't want to slowly exhaust the namespace.&lt;/p&gt;

&lt;h3&gt;
  
  
  Gesture Handling and Card Physics
&lt;/h3&gt;

&lt;p&gt;This was the rabbit hole I didn't expect. React Native Gesture Handler + Reanimated handle the swipe animations, but getting the card physics to "feel right" took more iteration than any other feature.&lt;/p&gt;

&lt;p&gt;The problem is that Tinder has trained everyone's muscle memory for how a swipe card should behave — the acceleration curve, the rotation on drag, the snap-back animation on an incomplete swipe, the way the card flies off screen on completion. If any of those are slightly off, the whole experience feels wrong, even if the user can't articulate why.&lt;/p&gt;

&lt;p&gt;I ended up studying Tinder's actual animation curves by screen-recording the app and stepping through the footage frame by frame. Probably overkill, but the result is that WhaTo's swipe feels natural to anyone who's used a dating app.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keeping API Keys Out of the Client
&lt;/h3&gt;

&lt;p&gt;The app pulls restaurant data from Yelp, movie/show data from TMDB, and maps from Google Places. Shipping those API keys in the client binary is a non-starter — anyone with a decompiler gets your keys.&lt;/p&gt;

&lt;p&gt;The solution is a Cloudflare Worker that acts as an API proxy. The client calls the Worker, the Worker calls the external API with the real key, and the response gets passed through. The Worker also handles rate limiting and request validation, so even if someone figures out the Worker endpoint, they can't abuse the upstream APIs through it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd Do Differently
&lt;/h2&gt;

&lt;p&gt;If I were starting over, I'd skip Firebase Realtime Database and use something with better offline support. Firebase RTDB works fine when everyone has a connection, but handling the edge case where someone's phone drops to airplane mode mid-session and reconnects later is awkward. Firestore would've been a better choice for this, but by the time I realized it, migrating wasn't worth the effort.&lt;/p&gt;

&lt;p&gt;I'd also invest more in E2E testing earlier. I added Maestro late in the process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;Free, no ads, no account required. Sessions auto-expire after 24 hours and I don't store your data beyond that.&lt;/p&gt;

&lt;p&gt;[&lt;a href="https://apps.apple.com/us/app/whato/id6760729938" rel="noopener noreferrer"&gt;App Store&lt;/a&gt;]&lt;/p&gt;

&lt;p&gt;Feedback welcome — especially if you've tried to solve this problem before and have opinions about what works and what doesn't. &lt;/p&gt;

</description>
      <category>showdev</category>
      <category>react</category>
      <category>mobile</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
