<?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: SOG-web</title>
    <description>The latest articles on DEV Community by SOG-web (@sogweb).</description>
    <link>https://dev.to/sogweb</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%2F350404%2Fb40697f1-8b14-4851-8959-a14cf8565823.jpeg</url>
      <title>DEV Community: SOG-web</title>
      <link>https://dev.to/sogweb</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sogweb"/>
    <language>en</language>
    <item>
      <title>Breaking Up With ORMs – Part One: Reclaiming Control with Custom SQL Adapters</title>
      <dc:creator>SOG-web</dc:creator>
      <pubDate>Sat, 17 May 2025 12:59:42 +0000</pubDate>
      <link>https://dev.to/sogweb/breaking-up-with-orms-part-one-reclaiming-control-with-custom-sql-adapters-1gh7</link>
      <guid>https://dev.to/sogweb/breaking-up-with-orms-part-one-reclaiming-control-with-custom-sql-adapters-1gh7</guid>
      <description>&lt;h1&gt;
  
  
  Breaking Up With ORMs – Part One: Reclaiming Control with Custom SQL Adapters
&lt;/h1&gt;

&lt;p&gt;Why I built my own SQL adapter instead of using ORMs like Prisma and Drizzle — and how it gives me total control over querying from both the frontend and backend.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“It’s not you, it’s me. I just need more... control.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  ✋ This Is Just the Beginning
&lt;/h2&gt;

&lt;p&gt;This post is more of a &lt;strong&gt;breaker&lt;/strong&gt;—an introduction to &lt;em&gt;why&lt;/em&gt; I left ORMs behind and what motivated me to build my own database adapter.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;deeper, more technical dive&lt;/strong&gt; is coming soon, where I’ll break down:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How the entire SDK works&lt;/li&gt;
&lt;li&gt;How to build it from scratch&lt;/li&gt;
&lt;li&gt;The architectural design choices&lt;/li&gt;
&lt;li&gt;Live usage with real-world apps&lt;/li&gt;
&lt;li&gt;Open source links to both the &lt;strong&gt;frontend SDK&lt;/strong&gt; and the &lt;strong&gt;backend sdk&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;And more&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So if this resonates with you, stay tuned—&lt;strong&gt;the code will be open&lt;/strong&gt; and the full post will be linked shortly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction: Why I Broke Up With ORMs
&lt;/h2&gt;

&lt;p&gt;I’ll be honest: I love ORMs. I’ve used Prisma, I’ve used Drizzle, and I admire the engineering behind them. They offer a lot—developer productivity, type safety, and beautiful DX (developer experience).&lt;/p&gt;

&lt;p&gt;But after a while, I started running into walls:&lt;/p&gt;

&lt;p&gt;I wanted more flexibility—more control over my queries, more visibility into the SQL, and more freedom to design my API the way I wanted. ORMs began to feel like an abstraction I had to fight against rather than something working &lt;em&gt;with&lt;/em&gt; me.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ORMs abstract too much&lt;/li&gt;
&lt;li&gt;I wanted to see and control the actual SQL&lt;/li&gt;
&lt;li&gt;I wanted to dynamically generate complex queries from the client and still have authentication and authorization (like an RLS system)&lt;/li&gt;
&lt;li&gt;I wanted to define my schema through APIs, not only through files&lt;/li&gt;
&lt;li&gt;I needed a system that worked seamlessly with both frontend and backend&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And I just didn’t want to fight the abstraction anymore.&lt;/p&gt;

&lt;p&gt;So I did what any control-obsessed backend dev would do: I built my own SQL adapter.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem: When ORMs Start to Get in the Way
&lt;/h2&gt;

&lt;p&gt;Here are a few pain points I ran into while using ORMs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Performance Cliffs&lt;/strong&gt;: Some ORMs made poor choices under the hood (e.g. N+1 queries, unnecessary joins).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Migrations &amp;amp; Schema Control&lt;/strong&gt;: I wanted to create and modify tables dynamically via API, not just manually define them in migration files.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Query Transparency&lt;/strong&gt;: I wanted to see and shape the exact SQL being executed—not reverse-engineer it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vendor Lock-in&lt;/strong&gt;: Switching ORMs or DB engines became a large undertaking.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Solution: A KNEX Adapter
&lt;/h2&gt;

&lt;p&gt;To solve this, I built the A &lt;strong&gt;KNEX Adapter&lt;/strong&gt;—a lightweight, expressive query layer built on top of Knex.js, but designed for composability and API-first systems.&lt;/p&gt;

&lt;p&gt;Here’s what it supports and includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deeply nested filter groups (&lt;code&gt;AND&lt;/code&gt;, &lt;code&gt;OR&lt;/code&gt;, &lt;code&gt;EXISTS&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Aggregates, window functions, and recursive CTEs&lt;/li&gt;
&lt;li&gt;Grouped filters, complex pagination, raw joins (securely)&lt;/li&gt;
&lt;li&gt;Full control over the query lifecycle&lt;/li&gt;
&lt;li&gt;Dynamic schema creation via API (covered in Part 2)&lt;/li&gt;
&lt;li&gt;A powerful &lt;strong&gt;frontend SDK&lt;/strong&gt;: Think Prisma-style chaining, but built to run on the client.&lt;/li&gt;
&lt;li&gt;A flexible &lt;strong&gt;backend SDK&lt;/strong&gt;: Built on Knex with support for deeply structured queries, CTEs, RLS, and window functions.&lt;/li&gt;
&lt;li&gt;A standard &lt;strong&gt;QueryParams&lt;/strong&gt; format to allow structured, safe queries across the wire.&lt;/li&gt;
&lt;li&gt;A sync layer, auditing hooks, and dynamic table creation (covered in future posts).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every code in this post is from the &lt;strong&gt;frontend SDK&lt;/strong&gt;, showing how to build deeply structured queries &lt;strong&gt;directly from the client&lt;/strong&gt; to your API.&lt;/p&gt;




&lt;h2&gt;
  
  
  Example: Fluent Querying Without the ORM Bloat
&lt;/h2&gt;

&lt;p&gt;Let’s say you want to find all active users who are either admins or IT managers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;db.table&amp;lt;User&amp;gt;("users")
  .where("status", "active")
  .andWhere((query) =&amp;gt; {
    query.where("role", "admin").orWhere((subQuery) =&amp;gt; {
      subQuery.where("role", "manager").where("department", "IT");
    });
  })
  .query();

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or do grouped aggregates with &lt;code&gt;HAVING&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;db.table&amp;lt;Order&amp;gt;("orders")
  .groupBy("customer_id", "status")
  .having("total_amount", "&amp;gt;", 1000)
  .sum("amount", "total_amount")
  .count("id", "order_count")
  .query();

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All that without writing raw SQL or defining a separate model schema.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Not Just Stick With Knex?
&lt;/h2&gt;

&lt;p&gt;Great question. Knex is powerful—but also low-level. It’s great for quick prototyping, but hard to standardize or reuse across a multi-service architecture.&lt;/p&gt;

&lt;p&gt;The KNEX Adapter gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A structured &lt;code&gt;QueryParams&lt;/code&gt; interface&lt;/strong&gt;: everything from filters to CTEs in a predictable, typed shape.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Safe, composable patterns&lt;/strong&gt; for clients to generate queries.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Support for syncing, auditing, and RLS&lt;/strong&gt;—coming in Part 3.&lt;/li&gt;
&lt;li&gt;Secure query abstraction without losing power&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What’s Next
&lt;/h2&gt;

&lt;p&gt;👉 In the next post, I’ll show &lt;strong&gt;how I built it&lt;/strong&gt;, and how you can use the same architecture to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Build an API that can create and modify SQL tables dynamically&lt;/li&gt;
&lt;li&gt;Support row-level security and fine-grained access controls&lt;/li&gt;
&lt;li&gt;Use client SDKs to query with &lt;strong&gt;EXISTS&lt;/strong&gt;, &lt;strong&gt;GROUPED&lt;/strong&gt;, &lt;strong&gt;HAVING&lt;/strong&gt;, &lt;strong&gt;WINDOWS&lt;/strong&gt;, and more&lt;/li&gt;
&lt;li&gt;Drop-in-replacement to any SQL database&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🔗 Open Source Coming Soon
&lt;/h3&gt;

&lt;p&gt;The SDK will be open-sourced on &lt;a href="https://github.com/The-ForgeBase" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; — follow me there or at &lt;a href="https://github.com/The-ForgeBase/dynamic-db" rel="noopener noreferrer"&gt;Dynamic Database API (Experimental)&lt;/a&gt; to be the first to try it (this is an unclean and unpublished version.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;This isn’t a rant against ORMs. They’re amazing for many projects. But for API-first systems where flexibility, transparency, and control matter most—going adapter-first gives you an edge.&lt;/p&gt;

&lt;p&gt;In Part Two, we’ll dive into &lt;strong&gt;dynamic table creation through API&lt;/strong&gt;, including how to build and manage schemas without migration files or CLI tools.&lt;/p&gt;

&lt;p&gt;Stay tuned 👇&lt;/p&gt;

&lt;p&gt;Follow me for the next part, or subscribe if you're into flexible backends, system design, and self-hostable dev tools&lt;/p&gt;

&lt;h1&gt;
  
  
  orm, #sql, #knex, #sdk, #backend, #typescript, #api, #database, #open-source, #devtools
&lt;/h1&gt;

</description>
      <category>backenddevelopment</category>
      <category>webdev</category>
      <category>opensource</category>
      <category>database</category>
    </item>
  </channel>
</rss>
