The app that was supposed to make my work life easier and that I never used
I just wanted writing SQL to suck less. So I built my own language server, and then an entire desktop app around it. Over 1,000 commits and 24 releases later, it was done. I still didn't use it: while I was solving my problem, AI had changed my workflow. I barely wrote SQL myself anymore. My tool did eventually find a user: the AI, which now gets controlled database access through it as an MCP server. A story about falling in love with the engineering, vibe coding, and the uncomfortable question of whether you still need the thing you're building.
Let’s rewind three years. I got to work on a new project: a data warehouse. Data gets pulled from A to B, cleaned up, transformed, normalized. You get it.
Back then, TypeScript was my thing. I was a TypeScript wizard, as we used to call it: the more types, the better, and the more complex, the more fun. Did any of that matter in production? No. Was it beautiful? Oh boy, yes.
The project got off to a sobering start. There was barely anything to implement. Instead, I wrote one SQL query after another against legacy systems so badly designed that I started losing hair within the first few months. After eight years as a developer, countless databases, and pretty much every ORM out there, I thought I was pretty good at SQL. I was wrong.
So I locked myself away at home for a few weeks and learned MySQL from the ground up. I got so deep into it that I started questioning MySQL itself. As it turned out, I had a point.
I still couldn’t warm to SQL, though. The language has been around for over 50 years. Why the hell is decent IDE support still not a thing? All I wanted was the bare minimum: catch duplicate column names, spot typos in table names, warn me when I compare two incompatible column types. Instead, every single query was a pain, especially once it ran past a thousand lines. The hours I wasted on collation errors that wouldn’t even tell me which column was causing them.
On top of that, there was our ORM, Drizzle, adding a thin layer of abstraction, and occasionally a few bugs of its own.
Skill issue? Partly, absolutely. But eventually, an idea took root: I’ll build my own language server. And not just any language server. The deluxe edition. One that reads table and column metadata straight from the database. That warns me when I compare a DATE to a DATETIME. That shows me the lineage of every derived column: where does it come from, how is it used, what even is this thing? With autocomplete that knows the columns of a CTE in a WITH clause. It was my dream project.
Seven Rewrites and a Baby
In late 2024, I sat down and built a prototype. By hand, which feels almost unimaginable now. It could parse simple queries, check types and semantics, and produce useful error messages. Was the UI pretty? Not even close. Was I proud? You bet.
Then it went the way side projects always go. You hit a small problem and think: I’ll just rebuild the whole thing. Properly this time. Over seven months, that turned into two repos, roughly 22,000 lines of handwritten TypeScript between them, and countless fresh starts. Then my kid arrived, and the project went on hold.
Back from Parental Leave, Straight into Vibe Coding
Late last year, I had time again. AI had just grown out of its awkward early phase, and I thought: why not just vibe-code my project? A way to learn how to work with AI, with the quiet hope of finally getting the thing finished.
At first, I went through every line. I forced the AI to write code the way I wanted it and tried to understand everything. Then I put the AI aside and rewrote the whole thing by hand, untangling and cleaning up the code. Then I let the AI bolt on some more features. It promptly dropped a third implementation of the same logic into some random folder. Duplicate logic everywhere. My beautiful codebase was a mess again.
The numbers: in three and a half months, I ended up with almost 500 commits and roughly a quarter of a million lines of code. For comparison, by hand I’d managed 22,000 in seven months.
A side effect of building a type checker for SQL: you end up checking the database itself. During testing, I came across a query where MySQL simply returns the wrong result. WHERE col IS NULL OR col = 1 on a UNION-derived table swallows the NULL row, even though each condition filters correctly on its own. I reported it, and Oracle confirmed it (Bug #119799). Thirty years of MySQL, and the database still gets the filtering wrong.
Meanwhile, work was changing. More and more code was being generated, reviewed, and deployed with AI, until your main role was being the person on the hook when something went wrong. Your job, then, is to keep the failure rate as low as possible through thorough reviews and tests.
I kept building. The VS Code extension became a desktop app, built with Rust and Tauri: quick to load, light on memory. The language server became an afterthought as I built an entire ecosystem around it. You write a query, and a table opens up. You can group, aggregate, filter, and create charts in a few clicks, all type-safe and nicely presented. Then came dashboards, pivot tables, Excel export, and auto-updates. After countless nights of “just this one more feature, and wouldn’t that be nice too,” I was past 1,000 commits and 24 releases. In late summer, I told myself: it’s done.
And then I just didn’t use it.
The Problem Disappeared While I Was Solving It
While I was building the app, the way we work changed so drastically that the need for it vanished. I don’t write queries myself anymore. AI generates most of the SQL now. I review it, run it, check execution times, and put the results into a business context. The only time I still write it myself is when the optimization gets truly unholy.
I was disappointed. All that work, all those ideas, for nothing?
New User: The AI
So I sat down, took stock of what the technology could do today, and thought about what I could still do with this thing. The idea: put an MCP server on top. I want my AI to be able to read the database, but not every table and not every column. I want to configure what it’s allowed to see. And I already had exactly the right tool for that: a language server that understands every query down to the individual column.
The result was an MCP server that, for example, rejects queries asking for names from the customer table. The principle is default-deny: the AI only sees tables and columns I’ve explicitly allowed, and every query goes through the language server before it runs. As far as the language server is concerned, a hidden column simply doesn’t exist. So taking a detour through a CTE, an alias, or a subquery won’t help, and even a SELECT * only expands to the allowed columns. That protects against two things: an AI trying to access data that’s none of its business, whether out of overenthusiasm or because of a prompt injection, and ordinary AI mistakes, because a query that doesn’t type-check never reaches the database in the first place. Red-teaming my own server showed me just how necessary this is: through information_schema and the mysql system tables, an agent could have listed the hidden schema and even read password hashes. That hole is patched now, but it taught me something: letting an AI loose on a database without checking what it’s actually querying is really never a good idea.
That’s the only feature in the entire app I actually use today, and even then, only against my test database. But it illustrates something I only understood in hindsight: The interface for humans became redundant. The understanding underneath it didn’t. My tool’s user changed, from the developer to the AI.
Was It All for Nothing?
Time I’ll never get back? Some of it, definitely. But the project was my introduction to working productively with AI, and along the way I learned three things I would have disagreed with before.
1. These days, code needs to be maintainable for the AI, not for me. My biggest supposed problem was clean code. I always wanted my code to be abstract and flexible enough. In the end, that was never an issue. Even when the AI handed me a monolith, I never had to change it by hand. It just regenerated the entire file to meet the new requirements. The manual code editing I’d spent years optimizing for simply doesn’t happen anymore.
2. The AI didn’t fail. I failed to explain what I wanted. I can’t remember a task the AI genuinely failed at. The hard part was always understanding the problem myself and describing it so the AI would implement it the way I expected. Otherwise, a small UI change turns into five hours of agent babysitting, just waiting for it to do something completely different from what we’d agreed on. Again.
3. Before you build something, ask whether you’ll still need it in a year. I built a 2023-style solution to a 2023 problem and finished it in 2026. What I needed wasn’t more features. It was a regular, honest look at whether I was still opening the thing myself.
Was it worth the effort? As a product, no. As the most expensive and instructive AI training I’ve ever had? Yes.





