<?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: Mindinu Ariyawansha</title>
    <description>The latest articles on DEV Community by Mindinu Ariyawansha (@mindinu).</description>
    <link>https://dev.to/mindinu</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3755924%2F2fb5d34f-cd8c-4042-afeb-0a8775a5ce2e.jpg</url>
      <title>DEV Community: Mindinu Ariyawansha</title>
      <link>https://dev.to/mindinu</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mindinu"/>
    <language>en</language>
    <item>
      <title>⚡ Stop Defaulting to WebSockets: Why Server-Sent Events (SSE) are Usually Better</title>
      <dc:creator>Mindinu Ariyawansha</dc:creator>
      <pubDate>Fri, 04 Sep 2026 04:56:51 +0000</pubDate>
      <link>https://dev.to/mindinu/stop-defaulting-to-websockets-why-server-sent-events-sse-are-usually-better-3k2g</link>
      <guid>https://dev.to/mindinu/stop-defaulting-to-websockets-why-server-sent-events-sse-are-usually-better-3k2g</guid>
      <description>&lt;p&gt;There is a moment in every web developer's career when a client asks: &lt;em&gt;"Can we make this update in real time?"&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;Your mind immediately jumps to WebSockets. It is the industry buzzword. It sounds fast. You spin up &lt;code&gt;socket.io&lt;/code&gt; or &lt;code&gt;Reverb&lt;/code&gt;, spend two days fighting with your load balancer, and finally get it working.&lt;/p&gt;

&lt;p&gt;But here is the harsh truth: for about 90% of modern web applications—including AI chat streaming, live dashboards, and notification feeds—&lt;strong&gt;WebSockets are massive overkill&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Instead, you should probably be using &lt;strong&gt;Server-Sent Events (SSE)&lt;/strong&gt;. Here is why SSE is often the cleaner, cheaper, and more pragmatic choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Core Difference
&lt;/h2&gt;

&lt;p&gt;Both WebSockets and SSE exist to push data from the server to the client without the client needing to constantly poll the server. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;WebSockets&lt;/strong&gt; create a full-duplex, persistent TCP connection. Both the client and the server can shout at each other simultaneously.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;SSE&lt;/strong&gt; is a unidirectional, HTTP-based stream. The server keeps a standard HTTP connection open and pushes text-based events down to the client.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why WebSockets Are a Headache in Production
&lt;/h2&gt;

&lt;p&gt;WebSockets are amazing for multiplayer games or collaborative tools like Google Docs where clients are constantly sending high-frequency data &lt;em&gt;back&lt;/em&gt; to the server. But that power comes with a heavy infrastructure tax.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Stateful Scaling:&lt;/strong&gt; WebSockets are stateful. If you scale horizontally, your load balancer needs connection-aware routing (sticky sessions) to ensure a client's subsequent messages go to the specific server holding their connection.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Proxy Nightmares:&lt;/strong&gt; Aggressive corporate proxies and firewalls frequently drop WebSocket protocol upgrades, leaving connections in failure modes that are notoriously hard to debug. &lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Memory Hogs:&lt;/strong&gt; Maintaining bidirectional frame buffers and tracking protocol state means every single WebSocket connection consumes significantly more server memory than an equivalent HTTP connection.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;No Native Reconnect:&lt;/strong&gt; If a WebSocket connection drops (and it will), the browser does not care. You have to write all the custom logic to detect the drop, backoff, retry, and resynchronize state.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why SSE is the Underdog You Need
&lt;/h2&gt;

&lt;p&gt;SSE leans on the mature, battle-tested HTTP ecosystem. It doesn't require a protocol upgrade, it doesn't need a custom server, and it works flawlessly with standard load balancers.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Native Auto-Reconnect:&lt;/strong&gt; The &lt;code&gt;EventSource&lt;/code&gt; API in the browser is brilliant. If the connection drops, the browser automatically attempts to reconnect on its own. It even sends a &lt;code&gt;Last-Event-ID&lt;/code&gt; header so your server knows exactly where to resume the stream.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Standard HTTP Routing:&lt;/strong&gt; Because SSE is just a long-lived HTTP request, it scales like any other HTTP endpoint. &lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Perfect for AI and Dashboards:&lt;/strong&gt; If you are streaming an LLM response or pushing live price feeds to a dashboard, the client isn't sending data &lt;em&gt;back&lt;/em&gt; through that channel (they just make a standard POST request to trigger the event). SSE perfectly models this server-push architecture.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Talk is Cheap. Look at the Code.
&lt;/h2&gt;

&lt;p&gt;Here is how simple it is to implement SSE. No massive libraries, no custom protocols. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Backend (Node/Express):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/stream&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// 1. Set the headers to keep the connection open&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text/event-stream&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Cache-Control&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;no-cache&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Connection&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;keep-alive&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// 2. Push data whenever you want&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;intervalId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setInterval&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`data: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Processing...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;})}&lt;/span&gt;&lt;span class="s2"&gt;\n\n`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// 3. Clean up on disconnect&lt;/span&gt;
  &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;close&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;clearInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;intervalId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Frontend (Vanilla JS):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// The browser handles connection, streaming, and auto-reconnecting!&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;EventSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/stream&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onmessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;New update:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Decision Framework
&lt;/h2&gt;

&lt;p&gt;WebSockets and SSE aren't competitors; they solve different shapes of problems. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Choose WebSockets if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  You are building a chat app, multiplayer game, or real-time collaborative canvas.&lt;/li&gt;
&lt;li&gt;  The client needs to push data to the server at high frequencies (10+ times per second).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Choose SSE if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  You are streaming AI responses, live notifications, news feeds, or financial tickers.&lt;/li&gt;
&lt;li&gt;  The communication is primarily one-way (Server → Client).&lt;/li&gt;
&lt;li&gt;  You want to avoid managing custom reconnections and complex load balancing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next time someone asks for real-time updates, don't immediately reach for the heaviest tool in the box. Give SSE a try. &lt;/p&gt;




&lt;p&gt;&lt;em&gt;Have you struggled with WebSocket scaling in production? Let's talk about it in the comments! 👇&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>performance</category>
    </item>
    <item>
      <title>🚨 3 PostgreSQL Anti-Patterns That Are Silently Killing Your App's Performance</title>
      <dc:creator>Mindinu Ariyawansha</dc:creator>
      <pubDate>Thu, 03 Sep 2026 17:40:23 +0000</pubDate>
      <link>https://dev.to/mindinu/3-postgresql-anti-patterns-that-are-silently-killing-your-apps-performance-1e5k</link>
      <guid>https://dev.to/mindinu/3-postgresql-anti-patterns-that-are-silently-killing-your-apps-performance-1e5k</guid>
      <description>&lt;p&gt;PostgreSQL is one of the most powerful relational databases on the planet. Out of the box, it can handle massive workloads. But as your application scales, the way you write queries and structure your schema matters more than the database engine itself.&lt;/p&gt;

&lt;p&gt;If your app is starting to feel sluggish, it might not be a lack of resources. You might be falling into one of these three common PostgreSQL anti-patterns. &lt;/p&gt;

&lt;p&gt;Here is how to spot them and how to fix them.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The &lt;code&gt;SELECT *&lt;/code&gt; Trap (and why it ruins memory)
&lt;/h2&gt;

&lt;p&gt;When we are iterating quickly, it is incredibly tempting to just write &lt;code&gt;SELECT * FROM users&lt;/code&gt; and let the backend filter out the fields it doesn't need. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it’s an anti-pattern:&lt;/strong&gt;&lt;br&gt;
PostgreSQL has to read the data from the disk, load it into memory, and send it over the network to your application. If your &lt;code&gt;users&lt;/code&gt; table has 30 columns (including heavy &lt;code&gt;JSONB&lt;/code&gt; blobs or large &lt;code&gt;TEXT&lt;/code&gt; fields) and you only need the &lt;code&gt;id&lt;/code&gt; and &lt;code&gt;email&lt;/code&gt;, you are forcing the database to do 10x the I/O work for no reason. &lt;/p&gt;

&lt;p&gt;Furthermore, &lt;code&gt;SELECT *&lt;/code&gt; breaks index-only scans. If you have an index on &lt;code&gt;email&lt;/code&gt;, a query like &lt;code&gt;SELECT email FROM users WHERE email = 'x'&lt;/code&gt; can be resolved purely from the index without even touching the main table. &lt;code&gt;SELECT *&lt;/code&gt; forces Postgres to fetch the whole row.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Fix:&lt;/strong&gt;&lt;br&gt;
Always explicitly define your columns, even if you are using an ORM.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- ❌ Bad&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'pending'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- ✅ Good&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;total_amount&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'pending'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. Over-Indexing (The "Just Add an Index" Fallacy)
&lt;/h2&gt;

&lt;p&gt;When a query is slow, the immediate reaction is usually: &lt;em&gt;"Let's just throw an index on that column!"&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it’s an anti-pattern:&lt;/strong&gt;&lt;br&gt;
Indexes are not free. Every time you &lt;code&gt;INSERT&lt;/code&gt;, &lt;code&gt;UPDATE&lt;/code&gt;, or &lt;code&gt;DELETE&lt;/code&gt; a row, PostgreSQL has to update the main table &lt;em&gt;and&lt;/em&gt; every single index associated with that table. If you have a write-heavy table (like an event logger or analytics tracker) with 10 different indexes, your write latency will skyrocket.&lt;/p&gt;

&lt;p&gt;Additionally, Postgres query planners are smart. If an index isn't highly selective (e.g., a boolean column like &lt;code&gt;is_active&lt;/code&gt; where 95% of users are active), Postgres will likely ignore the index entirely and do a sequential scan anyway. You are paying the write penalty for an index that never gets used!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Fix:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Periodically check for unused indexes. Postgres tracks this for you! You can run this query to find indexes that the database is ignoring:
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;relname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indexrelname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;idx_scan&lt;/span&gt; 
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pg_stat_user_indexes&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;idx_scan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;Drop unused indexes and lean into &lt;strong&gt;composite indexes&lt;/strong&gt; for queries that frequently filter by the same multiple columns.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  3. The ORM N+1 Query Disaster
&lt;/h2&gt;

&lt;p&gt;If you are using Prisma, TypeORM, Hibernate, or Eloquent, you have probably written this exact bug without realizing it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it’s an anti-pattern:&lt;/strong&gt;&lt;br&gt;
The N+1 problem occurs when your code fetches a list of records, and then loops through that list to fetch related data for &lt;em&gt;each&lt;/em&gt; record.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ The N+1 Disaster in action&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// 1 query&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// This runs a NEW query for every single user! (N queries)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;authorId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt; 
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you have 1,000 users, you just hit your database 1,001 times over the network for something that should have been a single round trip. This is the #1 cause of API latency.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Fix:&lt;/strong&gt;&lt;br&gt;
Use &lt;code&gt;JOIN&lt;/code&gt;s or rely on your ORM's eager-loading capabilities to fetch everything in a single optimized query.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ✅ Good: Fetches users and their posts in a single round trip&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;usersWithPosts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Note: Under the hood, this translates to either a &lt;code&gt;LEFT JOIN&lt;/code&gt; or exactly two queries (one for users, one for all posts matching those user IDs via an &lt;code&gt;IN&lt;/code&gt; clause).&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Scaling a database isn't just about throwing more RAM and CPU at your cloud provider. It is about respecting the network boundary, understanding your indexes, and keeping a close eye on the SQL your ORM is actually generating. &lt;/p&gt;




&lt;p&gt;&lt;em&gt;What is the worst database performance bug you've ever had to debug? Let me know in the comments! 👇&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>productivity</category>
      <category>sql</category>
    </item>
    <item>
      <title>🛑 Stop Wasting API Calls: How to Build a Dead-Simple Caching Layer for AI Apps</title>
      <dc:creator>Mindinu Ariyawansha</dc:creator>
      <pubDate>Thu, 03 Sep 2026 07:02:59 +0000</pubDate>
      <link>https://dev.to/mindinu/stop-wasting-api-calls-how-to-build-a-dead-simple-caching-layer-for-ai-apps-ngi</link>
      <guid>https://dev.to/mindinu/stop-wasting-api-calls-how-to-build-a-dead-simple-caching-layer-for-ai-apps-ngi</guid>
      <description>&lt;p&gt;Building AI applications is incredibly fun right up until you check your API dashboard and realize you've been burning through credits by sending the exact same prompts during development and testing. &lt;/p&gt;

&lt;p&gt;Whether you are using Claude, Gemini, or OpenAI, rate limits and latency are real bottlenecks. If you are building wrappers, agents, or generation tools, &lt;strong&gt;you need a caching layer.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here is a highly effective, zero-dependency caching wrapper in TypeScript that you can drop into any project today.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Concept
&lt;/h2&gt;

&lt;p&gt;Instead of calling the LLM directly, we pass the prompt through a caching function. We hash the prompt (or use it as a key) and check if we already have a response stored. If yes, we return the cached string instantly. If no, we make the expensive network call, save the result, and return it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Code (TypeScript)
&lt;/h2&gt;

&lt;p&gt;This uses a simple in-memory &lt;code&gt;Map&lt;/code&gt;, which is perfect for local development or single-instance edge functions.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
typescript
// Define an in-memory cache
const responseCache = new Map&amp;lt;string, string&amp;gt;();

async function fetchWithCache(prompt: string): Promise&amp;lt;string&amp;gt; {
  // 1. Check if we already have the exact prompt cached
  if (responseCache.has(prompt)) {
    console.log("⚡ Returning from Cache (0ms)");
    return responseCache.get(prompt)!;
  }

  // 2. If not, make the actual API call (using a generic fetch as an example)
  console.log("☁️ Fetching from API...");
  const response = await fetch("https://dev.to/[https://api.your-llm-provider.com/v1/generate](https://api.your-llm-provider.com/v1/generate)", {
    method: "POST",
    headers: { "Authorization": `Bearer ${process.env.API_KEY}` },
    body: JSON.stringify({ prompt })
  });

  const data = await response.json();
  const textResult = data.choices[0].text;

  // 3. Store the result in the cache for next time
  responseCache.set(prompt, textResult);

  return textResult;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>typescript</category>
    </item>
    <item>
      <title>🔌 The 'USB-C of AI': Make Your Own MCP</title>
      <dc:creator>Mindinu Ariyawansha</dc:creator>
      <pubDate>Wed, 02 Sep 2026 14:41:48 +0000</pubDate>
      <link>https://dev.to/mindinu/the-usb-c-of-ai-make-your-own-mcp-568i</link>
      <guid>https://dev.to/mindinu/the-usb-c-of-ai-make-your-own-mcp-568i</guid>
      <description>&lt;p&gt;If you've been building AI-integrated apps recently, you know the pain: every single LLM, agent, and coding assistant needs a custom integration to read your database, check your GitHub repo, or pull Jira tickets. It's an endless cycle of writing custom API glue code.&lt;/p&gt;

&lt;p&gt;Enter the &lt;strong&gt;Model Context Protocol (MCP)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Originally open-sourced by Anthropic, MCP is rapidly becoming the universal standard for how AI agents talk to data sources. It is quite literally the USB-C of the AI world. &lt;/p&gt;

&lt;p&gt;Here is why MCP is completely changing the modern developer stack—and how you can start using it to turbocharge your workflow today.&lt;/p&gt;

&lt;h2&gt;
  
  
  🤔 What exactly is MCP?
&lt;/h2&gt;

&lt;p&gt;In simple terms, MCP is an open standard that standardizes how AI models access external context. Instead of building a custom plugin for Claude, a different one for Cursor, and another for your custom Python agent, you build &lt;strong&gt;one MCP Server&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Any MCP-compatible client (like Claude Desktop, Cursor, or your own app) can instantly connect to that server and understand what tools and data are available.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Architecture is simple:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;MCP Hosts:&lt;/strong&gt; The application the user interacts with (e.g., Claude Desktop, Cursor IDE).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP Clients:&lt;/strong&gt; The protocol layer inside the host application that manages connections.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP Servers:&lt;/strong&gt; Lightweight programs you run locally or in the cloud that expose your data (e.g., a local SQLite database, a GitHub repo, a Slack workspace).&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  🛠️ The 3 Core Primitives of MCP
&lt;/h2&gt;

&lt;p&gt;When an AI connects to an MCP server, it gets access to three core primitives:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Resources (Read-only data)
&lt;/h3&gt;

&lt;p&gt;Resources are like file systems for AI. They allow the LLM to read data without modifying it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;Example:&lt;/em&gt; Giving the AI read access to your local API documentation, system logs, or a Notion workspace. &lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Tools (Executable actions)
&lt;/h3&gt;

&lt;p&gt;Tools are functions the LLM can call to actually &lt;em&gt;do&lt;/em&gt; things. The server defines the required arguments, and the client prompts the user for permission before executing.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;Example:&lt;/em&gt; &lt;code&gt;execute_sql_query&lt;/code&gt;, &lt;code&gt;create_github_issue&lt;/code&gt;, or &lt;code&gt;restart_docker_container&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Prompts (Reusable templates)
&lt;/h3&gt;

&lt;p&gt;Pre-defined prompt templates that help users get the most out of the connected data. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;Example:&lt;/em&gt; A "Code Review" prompt that automatically pulls the current git diff and asks the LLM to review it against your company's style guide.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🚀 Why this matters for your workflow &lt;em&gt;right now&lt;/em&gt;
&lt;/h2&gt;

&lt;p&gt;You don't need to be building an AI startup to benefit from MCP. You can use it today to make your local dev environment incredibly powerful.&lt;/p&gt;

&lt;p&gt;Imagine this workflow:&lt;br&gt;
You are debugging an issue in Cursor. Instead of copying and pasting logs from your terminal, you spin up a local &lt;strong&gt;Postgres MCP Server&lt;/strong&gt; and a &lt;strong&gt;Datadog MCP Server&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;You simply ask your AI:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Look at the recent 500 errors in Datadog, query the users table in my local Postgres to see if their accounts are active, and find the bug in my codebase."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Because the AI is connected to those MCP servers, it can autonomously fetch the logs, run the SQL query, and fix the code in one seamless interaction.&lt;/p&gt;

&lt;h2&gt;
  
  
  💻 Building your first MCP Server
&lt;/h2&gt;

&lt;p&gt;Building a server is surprisingly easy. You can write them in TypeScript or Python. Here is the conceptual skeleton of exposing a simple database tool in TypeScript:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

// 1. Initialize the server
const server = new McpServer({
  name: "Local-DB-Server",
  version: "1.0.0"
});

// 2. Add a Tool for the AI to use
server.tool(
  "query_users",
  "Run a search query against the local users database",
  { searchTerm: z.string() },
  async ({ searchTerm }) =&amp;gt; {
    // Run your actual DB logic here
    const results = await mockDbSearch(searchTerm);
    return {
      content: [{ type: "text", text: JSON.stringify(results) }]
    };
  }
);

// 3. Start listening over standard I/O
const transport = new StdioServerTransport();
await server.connect(transport);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>mcp</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Scaling Multi-Agent Systems: Why Your Docker Container Keeps Crashing</title>
      <dc:creator>Mindinu Ariyawansha</dc:creator>
      <pubDate>Wed, 02 Sep 2026 14:22:02 +0000</pubDate>
      <link>https://dev.to/mindinu/scaling-multi-agent-systems-why-your-docker-container-keeps-crashing-53o7</link>
      <guid>https://dev.to/mindinu/scaling-multi-agent-systems-why-your-docker-container-keeps-crashing-53o7</guid>
      <description>&lt;p&gt;If you are building autonomous AI agents, you eventually hit a scaling wall. While developing Saturn AI, I noticed that pushing past five or six simultaneous agents caused the entire X11 Docker container to choke, API requests to time out, and the system to crash. &lt;/p&gt;

&lt;p&gt;The issue was not the LLM API latency. It was OS process thrashing. &lt;/p&gt;

&lt;h3&gt;
  
  
  The Root Cause: Process Overhead and Disk I/O
&lt;/h3&gt;

&lt;p&gt;When prototyping, it is common to rely on CLI wrappers to orchestrate agents. However, this introduces massive overhead. If six agents take a turn simultaneously, the backend spawns six heavy Node or Rust processes. If those agents invoke tools, they spawn additional child processes. &lt;/p&gt;

&lt;p&gt;The container rapidly runs out of memory, IPC pipe bandwidth, and CPU threads. Furthermore, if these wrappers maintain state by constantly reading and writing JSON session files, the disk I/O locks up completely.&lt;/p&gt;

&lt;h3&gt;
  
  
  Three Architectural Fixes for Multi-Agent Stability
&lt;/h3&gt;

&lt;p&gt;To resolve this and scale efficiently, you have to treat agent turns like asynchronous network requests rather than OS shell processes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Implement Concurrency Queuing&lt;/strong&gt;&lt;br&gt;
If you cannot rewrite your engine immediately, introduce an asynchronous job queue using a library like &lt;code&gt;p-queue&lt;/code&gt;. Cap the concurrency to two or three active processes at a time. When a trigger wakes up six agents, the queue allows the first few to execute while the others wait in memory. This eliminates CPU thrashing and keeps response times stable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Shift to In-Process SDK Calls&lt;/strong&gt;&lt;br&gt;
The long-term fix is removing the CLI middleman entirely. Build a custom ReAct loop using a native framework directly inside your main event loop. By executing agent turns as standard asynchronous network calls to the LLM provider, you can run dozens of concurrent agents in a single instance without spawning external processes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Use a Shared "Blackboard" Memory Model&lt;/strong&gt;&lt;br&gt;
Isolated JSON files for state management will bottleneck your disk. Transition to a shared state model stored directly in memory or a local Redis instance. All agents can instantly read and write their context, tasks, and tool outputs from this shared space. Additionally, boot a single persistent tool server on startup, and have all agents route through it via internal WebSockets, rather than each agent booting its own tool instances.&lt;/p&gt;

&lt;p&gt;By shifting away from process-heavy wrappers toward lightweight, async architecture, you can scale multi-agent environments reliably without burning through compute resources.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>docker</category>
    </item>
    <item>
      <title>I got into Batch0</title>
      <dc:creator>Mindinu Ariyawansha</dc:creator>
      <pubDate>Tue, 01 Sep 2026 16:32:51 +0000</pubDate>
      <link>https://dev.to/mindinu/i-got-into-batch0-4idl</link>
      <guid>https://dev.to/mindinu/i-got-into-batch0-4idl</guid>
      <description>&lt;p&gt;Hey DEV community! 👋 &lt;/p&gt;

&lt;p&gt;My name is Mindinu. I am 14 years old, based in Sri Lanka, and I am the founder of &lt;strong&gt;Luveo Technologies&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Today, I am super excited to announce a massive milestone for my journey: &lt;strong&gt;Luveo Technologies just got accepted into Batch 0!&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;For those who might not know, being part of a "Batch 0" cohort means you are in the inaugural, foundational group of an accelerator or incubator program. It’s the launchpad cohort. It means the mentors and organizers saw enough raw potential in the vision to take a bet on it from the very beginning. &lt;/p&gt;

&lt;p&gt;And the product that got us here? &lt;strong&gt;Saturn AI.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🪐 What is Saturn AI?
&lt;/h2&gt;

&lt;p&gt;Saturn AI is an autonomous AI employee. &lt;/p&gt;

&lt;p&gt;You might have seen tools like OpenClaw or Viktor, but Saturn AI is being built to be better, faster, and much more deeply integrated into actual developer and business workflows. It’s not just a chatbot; it’s a self-improving AI environment that executes tasks natively.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Under the hood, here is what I am building:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Infrastructure:&lt;/strong&gt; The entire platform is hosted on a Contabo private VPS, allowing us to manage our own cloud container orchestration without insane cloud fees.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authentication:&lt;/strong&gt; Seamless OAuth integration for secure and frictionless user onboarding.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monetization:&lt;/strong&gt; A fully custom tiered subscription model currently transitioning to Dodo Payments.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tech Stack:&lt;/strong&gt; I'm utilizing tools like ClaudeCode, Gemini CLI, OpenCode, Vite, Bun, and Cloudflare tunnels to build out a robust, scalable backend and a lightning-fast frontend.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🛠️ How I Will Participate in Batch 0
&lt;/h2&gt;

&lt;p&gt;I am treating Batch 0 as a high-speed sprint. Over the course of the program, my goals are strictly technical and growth-focused:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Ship the MVP:&lt;/strong&gt; Move Saturn AI from a complex local/cloud environment into a stable, user-ready product.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stress-Test the AI:&lt;/strong&gt; Push the autonomous agent capabilities to ensure it actually outperforms existing AI employee models in real-world coding and operational tasks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build in Public:&lt;/strong&gt; I am going to document the entire process right here on DEV. Expect deep dives into my Docker configurations, how I manage my VPS, and how I handle the payment gateway architecture.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  🚀 The Road Ahead
&lt;/h2&gt;

&lt;p&gt;Building a SaaS product and running a startup at 14 isn't easy. I have to balance school, table tennis training, robotics competitions, and coding late into the night. But getting accepted into Batch 0 validates that the late nights are worth it.&lt;/p&gt;

&lt;p&gt;I can't wait to share this journey with you all. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is the hardest technical challenge you faced when building your first SaaS? Drop some advice for a young founder in the comments! 👇&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If you want to follow the journey of Saturn AI, keep an eye on &lt;a href="https://luveo.net/" rel="noopener noreferrer"&gt;luveo.net&lt;/a&gt; and my personal portfolio &lt;a href="https://mindinu.luveo.net/" rel="noopener noreferrer"&gt;mindinu.luveo.net&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>startup</category>
      <category>teendev</category>
      <category>coding</category>
    </item>
  </channel>
</rss>
