<?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: Abhishek tiwari</title>
    <description>The latest articles on DEV Community by Abhishek tiwari (@abhishek-tiwari-dev).</description>
    <link>https://dev.to/abhishek-tiwari-dev</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%2F4103856%2Fa803f0d8-86fe-41c6-bf73-3a8852832f7c.png</url>
      <title>DEV Community: Abhishek tiwari</title>
      <link>https://dev.to/abhishek-tiwari-dev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/abhishek-tiwari-dev"/>
    <language>en</language>
    <item>
      <title>Preventing Double Booking at 2M Concurrent Users: Redis Seat Locks + MySQL + Kafka" published: false</title>
      <dc:creator>Abhishek tiwari</dc:creator>
      <pubDate>Wed, 04 Nov 2026 09:51:00 +0000</pubDate>
      <link>https://dev.to/abhishek-tiwari-dev/preventing-double-booking-at-2m-concurrent-users-redis-seat-locks-mysql-kafkapublished-false-2i13</link>
      <guid>https://dev.to/abhishek-tiwari-dev/preventing-double-booking-at-2m-concurrent-users-redis-seat-locks-mysql-kafkapublished-false-2i13</guid>
      <description>&lt;p&gt;How a BookMyShow-style ticket booking system handles seat locking, idempotent payments and async events during a blockbuster release."&lt;br&gt;
tags: systemdesign, architecture, redis, backend&lt;/p&gt;

&lt;p&gt;2 million people open the app at 7:00 PM on release day. Seat A12 can be sold exactly once.&lt;/p&gt;

&lt;p&gt;The customer experience is simple: pick seats, pay ₹600, done in under 2 minutes. Behind that is a concurrency problem. Here is how I would design it. (This is my own design, not BookMyShow's actual internals.)&lt;/p&gt;
&lt;h2&gt;
  
  
  The flow
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Client → CDN → API Gateway → Load Balancer → Services → Redis lock → MySQL → Kafka → Notifications&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Edge:&lt;/strong&gt; the CDN serves static content, and the gateway and load balancer absorb the spike.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Core services:&lt;/strong&gt; Movie, Theatre, Seat Lock, Booking and Payment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data:&lt;/strong&gt; MySQL for bookings, Redis for temporary seat holds, Kafka for async events.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Step 1: Lock seats in Redis
&lt;/h2&gt;

&lt;p&gt;When a user selects seats, try to take a short-lived lock:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;SET seat:&lt;span class="o"&gt;{&lt;/span&gt;show:42&lt;span class="o"&gt;}&lt;/span&gt;:A12 &amp;lt;user-token&amp;gt; NX PX 600000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;NX&lt;/code&gt; means set only if the key doesn't exist, so the first request wins.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PX 600000&lt;/code&gt; gives a 10-minute hold. If the user abandons payment, the lock expires and no cleanup job is needed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Users usually pick several seats, so the lock must be all-or-nothing. A Lua script is atomic in Redis:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lua"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- KEYS = seat keys, ARGV[1] = user token, ARGV[2] = ttl in ms&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="n"&gt;KEYS&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"EXISTS"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;KEYS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="n"&gt;KEYS&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"SET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;KEYS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;ARGV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s2"&gt;"PX"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ARGV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Redis Cluster, all keys in one script must be in the same hash slot. That is why the &lt;code&gt;{show:42}&lt;/code&gt; hash tag is in the key name.&lt;/p&gt;

&lt;p&gt;Release locks safely by checking the token first, so a user can never free someone else's lock:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lua"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;KEYS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;ARGV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"DEL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;KEYS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: MySQL is the final judge
&lt;/h2&gt;

&lt;p&gt;Redis is fast, but locks can be lost during a failover. The database must be able to refuse a double booking on its own:&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="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;booking_seats&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt;         &lt;span class="nb"&gt;BIGINT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="n"&gt;AUTO_INCREMENT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;booking_id&lt;/span&gt; &lt;span class="nb"&gt;BIGINT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;show_id&lt;/span&gt;    &lt;span class="nb"&gt;BIGINT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;seat_id&lt;/span&gt;    &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;UNIQUE&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="n"&gt;uniq_show_seat&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;show_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seat_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;ENGINE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;InnoDB&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Write the booking and its seats in one transaction. If two requests somehow get past Redis, the second insert fails on the unique key. Redis handles the crowd, and MySQL guarantees correctness. On cancellation, delete the seat rows so the seats can be sold again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Idempotent payments
&lt;/h2&gt;

&lt;p&gt;Users double-tap "Pay" and networks retry. Generate one idempotency key per payment attempt and store it with a unique constraint:&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="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;payment_attempts&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;idempotency_key&lt;/span&gt; &lt;span class="nb"&gt;CHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;36&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;booking_id&lt;/span&gt;      &lt;span class="nb"&gt;BIGINT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;status&lt;/span&gt;          &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the same key arrives again, return the stored result instead of charging again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Kafka and the outbox pattern
&lt;/h2&gt;

&lt;p&gt;Emails, SMS and push notifications must never slow down a booking, so they go through Kafka. The risk is saving a booking but failing to publish the event. The fix is an outbox table written in the same transaction as the booking:&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="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;outbox_events&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt;           &lt;span class="nb"&gt;BIGINT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="n"&gt;AUTO_INCREMENT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;event_type&lt;/span&gt;   &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;payload&lt;/span&gt;      &lt;span class="n"&gt;JSON&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;published_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A small relay process reads unpublished rows and sends them to Kafka. Kafka delivery is at-least-once, so consumers must also be idempotent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Fail gracefully
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Retries with backoff for transient errors.&lt;/li&gt;
&lt;li&gt;A circuit breaker so a failing payment or SMS provider doesn't take down the flow.&lt;/li&gt;
&lt;li&gt;Fallbacks, such as queueing a notification instead of dropping it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What breaks first
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Failure&lt;/th&gt;
&lt;th&gt;What protects you&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Two users tap the same seat&lt;/td&gt;
&lt;td&gt;Redis &lt;code&gt;NX&lt;/code&gt; lock&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redis restarts and loses locks&lt;/td&gt;
&lt;td&gt;MySQL unique constraint&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;User double-taps Pay&lt;/td&gt;
&lt;td&gt;Idempotency key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SMS provider is down&lt;/td&gt;
&lt;td&gt;Kafka + retries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Booking saved, event lost&lt;/td&gt;
&lt;td&gt;Outbox pattern&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Open questions
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;How long should a seat hold last? Too short hurts payments, too long locks inventory.&lt;/li&gt;
&lt;li&gt;Is a virtual waiting room worth it for blockbuster releases?&lt;/li&gt;
&lt;li&gt;Where do you see hot-key problems at peak?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'd like to hear how you'd approach this in the comments.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkmejm99prp80b6hhvdza.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkmejm99prp80b6hhvdza.png" alt=" " width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Daily Career Digest</title>
      <dc:creator>Abhishek tiwari</dc:creator>
      <pubDate>Mon, 02 Nov 2026 07:38:36 +0000</pubDate>
      <link>https://dev.to/abhishek-tiwari-dev/daily-career-digest-4ice</link>
      <guid>https://dev.to/abhishek-tiwari-dev/daily-career-digest-4ice</guid>
      <description>&lt;p&gt;Unauthorized spam post attempt&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>programming</category>
    </item>
    <item>
      <title>Silent N+1 Database Query Bottlenecks in TypeScript &amp; Node.js REST APIs</title>
      <dc:creator>Abhishek tiwari</dc:creator>
      <pubDate>Sun, 18 Oct 2026 13:50:28 +0000</pubDate>
      <link>https://dev.to/abhishek-tiwari-dev/silent-n1-database-query-bottlenecks-in-typescript-nodejs-rest-apis-37h4</link>
      <guid>https://dev.to/abhishek-tiwari-dev/silent-n1-database-query-bottlenecks-in-typescript-nodejs-rest-apis-37h4</guid>
      <description>&lt;p&gt;Most slow PostgreSQL queries in Node.js REST APIs don’t happen because of missing database indexes.&lt;/p&gt;

&lt;p&gt;They happen because of silent N+1 query patterns executing inside map loops.&lt;/p&gt;

&lt;p&gt;When building REST endpoints with Node.js and TypeScript, it’s easy to write clean-looking code like this:&lt;/p&gt;

&lt;p&gt;const users = await getUsers();&lt;br&gt;
const posts = await Promise.all(&lt;br&gt;
  users.map(user =&amp;gt; getPostsByUserId(user.id))&lt;br&gt;
);&lt;/p&gt;

&lt;p&gt;On a local machine with 10 test records? It runs in 5ms.&lt;br&gt;
In production with 1,000 active users? That single HTTP request triggers 1,001 database roundtrips—exhausting your PostgreSQL connection pool and bringing response times to a crawl.&lt;/p&gt;

&lt;p&gt;Here is how we eliminate N+1 query bottlenecks in Node.js + Postgres services:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Batching with &lt;code&gt;WHERE id = ANY($1)&lt;/code&gt;&lt;br&gt;
Instead of querying per item in a loop, collect IDs into a single array and fetch all matching records in one database roundtrip.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Leveraging PostgreSQL &lt;code&gt;JOIN&lt;/code&gt; and &lt;code&gt;JSON_AGG&lt;/code&gt;&lt;br&gt;
Let Postgres do the heavy lifting. Using SQL joins with &lt;code&gt;json_agg()&lt;/code&gt; yields structured JSON directly from the database layer, skipping Node.js mapping overhead entirely.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;DataLoader Pattern for Nested REST Dependencies&lt;br&gt;
If you have nested dependencies across sub-services, leverage DataLoader to automatically batch and cache queries per request context.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Query Count Logging in Staging&lt;br&gt;
Enable query logging or APM tracing in non-production environments to catch high query counts before code hits production.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Clean code is great, but database network roundtrips are expensive.&lt;/p&gt;

&lt;p&gt;How do you catch N+1 query patterns during code reviews in your team? Let’s discuss below! 👇&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>node</category>
      <category>postgres</category>
      <category>backenddevelopment</category>
    </item>
    <item>
      <title>Silent N+1 Database Query Bottlenecks in TypeScript &amp; Node.js REST APIs</title>
      <dc:creator>Abhishek tiwari</dc:creator>
      <pubDate>Sun, 18 Oct 2026 09:56:23 +0000</pubDate>
      <link>https://dev.to/abhishek-tiwari-dev/silent-n1-database-query-bottlenecks-in-typescript-nodejs-rest-apis-3imk</link>
      <guid>https://dev.to/abhishek-tiwari-dev/silent-n1-database-query-bottlenecks-in-typescript-nodejs-rest-apis-3imk</guid>
      <description>&lt;p&gt;Most slow PostgreSQL queries in Node.js REST APIs don’t happen because of missing database indexes.&lt;/p&gt;

&lt;p&gt;They happen because of silent N+1 query patterns executing inside map loops.&lt;/p&gt;

&lt;p&gt;When building REST endpoints with Node.js and TypeScript, it’s easy to write clean-looking code like this:&lt;/p&gt;

&lt;p&gt;const users = await getUsers();&lt;br&gt;
const posts = await Promise.all(&lt;br&gt;
  users.map(user =&amp;gt; getPostsByUserId(user.id))&lt;br&gt;
);&lt;/p&gt;

&lt;p&gt;On a local machine with 10 test records? It runs in 5ms.&lt;br&gt;
In production with 1,000 active users? That single HTTP request triggers 1,001 database roundtrips—exhausting your PostgreSQL connection pool and bringing response times to a crawl.&lt;/p&gt;

&lt;p&gt;Here is how we eliminate N+1 query bottlenecks in Node.js + Postgres services:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Batching with &lt;code&gt;WHERE id = ANY($1)&lt;/code&gt;&lt;br&gt;
Instead of querying per item in a loop, collect IDs into a single array and fetch all matching records in one database roundtrip.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Leveraging PostgreSQL &lt;code&gt;JOIN&lt;/code&gt; and &lt;code&gt;JSON_AGG&lt;/code&gt;&lt;br&gt;
Let Postgres do the heavy lifting. Using SQL joins with &lt;code&gt;json_agg()&lt;/code&gt; yields structured JSON directly from the database layer, skipping Node.js mapping overhead entirely.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;DataLoader Pattern for Nested REST Dependencies&lt;br&gt;
If you have nested dependencies across sub-services, leverage DataLoader to automatically batch and cache queries per request context.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Query Count Logging in Staging&lt;br&gt;
Enable query logging or APM tracing in non-production environments to catch high query counts before code hits production.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Clean code is great, but database network roundtrips are expensive.&lt;/p&gt;

&lt;p&gt;How do you catch N+1 query patterns during code reviews in your team? Let’s discuss below! 👇&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>node</category>
      <category>postgres</category>
      <category>backenddevelopment</category>
    </item>
    <item>
      <title>Developer Growth &amp; Tech Trends</title>
      <dc:creator>Abhishek tiwari</dc:creator>
      <pubDate>Sun, 18 Oct 2026 09:56:18 +0000</pubDate>
      <link>https://dev.to/abhishek-tiwari-dev/developer-growth-tech-trends-4ppa</link>
      <guid>https://dev.to/abhishek-tiwari-dev/developer-growth-tech-trends-4ppa</guid>
      <description>&lt;p&gt;Developer Growth &amp;amp; Tech Trends&lt;/p&gt;

</description>
      <category>tech</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>Building Resilient TypeScript REST APIs in Node.js with PostgreSQL: Beyond Basic CRUD</title>
      <dc:creator>Abhishek tiwari</dc:creator>
      <pubDate>Sat, 17 Oct 2026 19:42:54 +0000</pubDate>
      <link>https://dev.to/abhishek-tiwari-dev/building-resilient-typescript-rest-apis-in-nodejs-with-postgresql-beyond-basic-crud-2a6d</link>
      <guid>https://dev.to/abhishek-tiwari-dev/building-resilient-typescript-rest-apis-in-nodejs-with-postgresql-beyond-basic-crud-2a6d</guid>
      <description>&lt;p&gt;Most backend bottlenecks don’t happen because of poor hardware.&lt;/p&gt;

&lt;p&gt;They happen because of unoptimized queries, missing connection pools, and sloppy TypeScript types.&lt;/p&gt;

&lt;p&gt;Moving from basic CRUD to a production-grade Node.js and PostgreSQL architecture requires a shift in mindset:&lt;/p&gt;

&lt;p&gt;• Strict Type Safety: Leveraging TypeScript interfaces for request validation and database models to catch errors at compile time, not in production.&lt;br&gt;
• Connection Pooling: Managing PostgreSQL pools efficiently to prevent socket exhaustion under high concurrent load.&lt;br&gt;
• Indexed Queries: Analyzing execution plans with &lt;code&gt;EXPLAIN ANALYZE&lt;/code&gt; rather than blindly adding indexes.&lt;br&gt;
• Graceful Error Handling: Centralizing exception handling to maintain consistent REST API response contracts.&lt;/p&gt;

&lt;p&gt;Your database isn't slow. Your query lifecycle probably is.&lt;/p&gt;

&lt;p&gt;What’s the most stubborn bottleneck you’ve debugged in a Node.js REST API recently?&lt;/p&gt;

&lt;h1&gt;
  
  
  TypeScript #NodeJS #PostgreSQL #BackendDevelopment #WebDev
&lt;/h1&gt;

</description>
      <category>typescript</category>
      <category>node</category>
      <category>postgres</category>
      <category>backenddevelopment</category>
    </item>
    <item>
      <title>Building Resilient TypeScript REST APIs in Node.js with PostgreSQL: Beyond Basic CRUD</title>
      <dc:creator>Abhishek tiwari</dc:creator>
      <pubDate>Sat, 17 Oct 2026 09:56:37 +0000</pubDate>
      <link>https://dev.to/abhishek-tiwari-dev/building-resilient-typescript-rest-apis-in-nodejs-with-postgresql-beyond-basic-crud-2icn</link>
      <guid>https://dev.to/abhishek-tiwari-dev/building-resilient-typescript-rest-apis-in-nodejs-with-postgresql-beyond-basic-crud-2icn</guid>
      <description>&lt;p&gt;Most backend bottlenecks don’t happen because of poor hardware.&lt;/p&gt;

&lt;p&gt;They happen because of unoptimized queries, missing connection pools, and sloppy TypeScript types.&lt;/p&gt;

&lt;p&gt;Moving from basic CRUD to a production-grade Node.js and PostgreSQL architecture requires a shift in mindset:&lt;/p&gt;

&lt;p&gt;• Strict Type Safety: Leveraging TypeScript interfaces for request validation and database models to catch errors at compile time, not in production.&lt;br&gt;
• Connection Pooling: Managing PostgreSQL pools efficiently to prevent socket exhaustion under high concurrent load.&lt;br&gt;
• Indexed Queries: Analyzing execution plans with &lt;code&gt;EXPLAIN ANALYZE&lt;/code&gt; rather than blindly adding indexes.&lt;br&gt;
• Graceful Error Handling: Centralizing exception handling to maintain consistent REST API response contracts.&lt;/p&gt;

&lt;p&gt;Your database isn't slow. Your query lifecycle probably is.&lt;/p&gt;

&lt;p&gt;What’s the most stubborn bottleneck you’ve debugged in a Node.js REST API recently?&lt;/p&gt;

&lt;h1&gt;
  
  
  TypeScript #NodeJS #PostgreSQL #BackendDevelopment #WebDev
&lt;/h1&gt;

</description>
      <category>typescript</category>
      <category>node</category>
      <category>postgres</category>
      <category>backenddevelopment</category>
    </item>
    <item>
      <title>Scaling Node.js REST APIs with TypeScript and PostgreSQL: Beyond Basic CRUD</title>
      <dc:creator>Abhishek tiwari</dc:creator>
      <pubDate>Fri, 16 Oct 2026 19:24:26 +0000</pubDate>
      <link>https://dev.to/abhishek-tiwari-dev/scaling-nodejs-rest-apis-with-typescript-and-postgresql-beyond-basic-crud-3coo</link>
      <guid>https://dev.to/abhishek-tiwari-dev/scaling-nodejs-rest-apis-with-typescript-and-postgresql-beyond-basic-crud-3coo</guid>
      <description>&lt;p&gt;Most backend bottlenecks don’t happen because of poor hardware.&lt;/p&gt;

&lt;p&gt;They happen because of unoptimized queries, missing connection pooling, and poor async orchestration in Node.js.&lt;/p&gt;

&lt;p&gt;When scaling a TypeScript and Node.js REST API with PostgreSQL under heavy traffic, standard practices quickly break down. Here is how we build production-grade, resilient backends:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Connection Pooling &amp;amp; Limits — Avoid creating a new database client per request. Use a robust pool manager with pg or Prisma to handle concurrency safely.&lt;/li&gt;
&lt;li&gt;Strict TypeScript DTOs — Validate incoming payloads at the edge using Zod or class-validator before touching the database layer.&lt;/li&gt;
&lt;li&gt;Indexing Strategy — B-tree and GIN indexes save high-latency queries when handling millions of rows in PostgreSQL.&lt;/li&gt;
&lt;li&gt;Async Background Jobs — Offload heavy computations, webhooks, and third-party API calls to queue workers.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Great architecture isn't about complex tooling; it's about predictable performance under stress.&lt;/p&gt;

&lt;p&gt;How do you handle DB connection spikes in your Node.js apps? Drop your thoughts below. 👇&lt;/p&gt;

&lt;h1&gt;
  
  
  TypeScript #NodeJS #PostgreSQL #RESTAPIs #SoftwareEngineering #BackendDevelopment
&lt;/h1&gt;

</description>
      <category>typescript</category>
      <category>node</category>
      <category>postgres</category>
      <category>restapis</category>
    </item>
    <item>
      <title>Scaling Node.js REST APIs with TypeScript and PostgreSQL: Beyond Basic CRUD</title>
      <dc:creator>Abhishek tiwari</dc:creator>
      <pubDate>Fri, 16 Oct 2026 18:51:19 +0000</pubDate>
      <link>https://dev.to/abhishek-tiwari-dev/scaling-nodejs-rest-apis-with-typescript-and-postgresql-beyond-basic-crud-3ip6</link>
      <guid>https://dev.to/abhishek-tiwari-dev/scaling-nodejs-rest-apis-with-typescript-and-postgresql-beyond-basic-crud-3ip6</guid>
      <description>&lt;p&gt;Most backend bottlenecks don’t happen because of poor hardware.&lt;/p&gt;

&lt;p&gt;They happen because of unoptimized queries, missing connection pooling, and poor async orchestration in Node.js.&lt;/p&gt;

&lt;p&gt;When scaling a TypeScript and Node.js REST API with PostgreSQL under heavy traffic, standard practices quickly break down. Here is how we build production-grade, resilient backends:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Connection Pooling &amp;amp; Limits — Avoid creating a new database client per request. Use a robust pool manager with pg or Prisma to handle concurrency safely.&lt;/li&gt;
&lt;li&gt;Strict TypeScript DTOs — Validate incoming payloads at the edge using Zod or class-validator before touching the database layer.&lt;/li&gt;
&lt;li&gt;Indexing Strategy — B-tree and GIN indexes save high-latency queries when handling millions of rows in PostgreSQL.&lt;/li&gt;
&lt;li&gt;Async Background Jobs — Offload heavy computations, webhooks, and third-party API calls to queue workers.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Great architecture isn't about complex tooling; it's about predictable performance under stress.&lt;/p&gt;

&lt;p&gt;How do you handle DB connection spikes in your Node.js apps? Drop your thoughts below. 👇&lt;/p&gt;

&lt;h1&gt;
  
  
  TypeScript #NodeJS #PostgreSQL #RESTAPIs #SoftwareEngineering #BackendDevelopment
&lt;/h1&gt;

</description>
      <category>typescript</category>
      <category>node</category>
      <category>postgres</category>
      <category>restapis</category>
    </item>
    <item>
      <title>Scaling Node.js REST APIs with TypeScript and PostgreSQL: Beyond Basic CRUD</title>
      <dc:creator>Abhishek tiwari</dc:creator>
      <pubDate>Fri, 16 Oct 2026 09:55:56 +0000</pubDate>
      <link>https://dev.to/abhishek-tiwari-dev/scaling-nodejs-rest-apis-with-typescript-and-postgresql-beyond-basic-crud-55ef</link>
      <guid>https://dev.to/abhishek-tiwari-dev/scaling-nodejs-rest-apis-with-typescript-and-postgresql-beyond-basic-crud-55ef</guid>
      <description>&lt;p&gt;Most backend bottlenecks don’t happen because of poor hardware.&lt;/p&gt;

&lt;p&gt;They happen because of unoptimized queries, missing connection pooling, and poor async orchestration in Node.js.&lt;/p&gt;

&lt;p&gt;When scaling a TypeScript and Node.js REST API with PostgreSQL under heavy traffic, standard practices quickly break down. Here is how we build production-grade, resilient backends:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Connection Pooling &amp;amp; Limits — Avoid creating a new database client per request. Use a robust pool manager with pg or Prisma to handle concurrency safely.&lt;/li&gt;
&lt;li&gt;Strict TypeScript DTOs — Validate incoming payloads at the edge using Zod or class-validator before touching the database layer.&lt;/li&gt;
&lt;li&gt;Indexing Strategy — B-tree and GIN indexes save high-latency queries when handling millions of rows in PostgreSQL.&lt;/li&gt;
&lt;li&gt;Async Background Jobs — Offload heavy computations, webhooks, and third-party API calls to queue workers.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Great architecture isn't about complex tooling; it's about predictable performance under stress.&lt;/p&gt;

&lt;p&gt;How do you handle DB connection spikes in your Node.js apps? Drop your thoughts below. 👇&lt;/p&gt;

&lt;h1&gt;
  
  
  TypeScript #NodeJS #PostgreSQL #RESTAPIs #SoftwareEngineering #BackendDevelopment
&lt;/h1&gt;

</description>
      <category>typescript</category>
      <category>node</category>
      <category>postgres</category>
      <category>restapis</category>
    </item>
    <item>
      <title>How I Built a 768-Dimensional Vector Search Engine with Node.js, TypeScript, and Supabase</title>
      <dc:creator>Abhishek tiwari</dc:creator>
      <pubDate>Fri, 16 Oct 2026 04:34:00 +0000</pubDate>
      <link>https://dev.to/abhishek-tiwari-dev/how-i-built-a-768-dimensional-vector-search-engine-with-nodejs-typescript-and-supabase-2ffh</link>
      <guid>https://dev.to/abhishek-tiwari-dev/how-i-built-a-768-dimensional-vector-search-engine-with-nodejs-typescript-and-supabase-2ffh</guid>
      <description>&lt;p&gt;&lt;strong&gt;How paragraph-aware chunking, embeddings, and PostgreSQL pgvector can make AI retrieval more efficient.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When you build an AI application that needs to work with large documents, one problem shows up quickly: how do you give an LLM the information it needs without sending the entire document with every request?&lt;/p&gt;

&lt;p&gt;Sending more text isn't always the answer. It can increase token usage, introduce irrelevant context, and make retrieval harder to manage.&lt;/p&gt;

&lt;p&gt;I explored a different approach: a vector search engine built with Node.js, TypeScript, and Supabase.&lt;/p&gt;

&lt;p&gt;The idea is simple: turn document content into embeddings, store those vectors, and retrieve the passages most relevant to a user's query.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture
&lt;/h2&gt;

&lt;p&gt;The retrieval pipeline follows this flow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Ingest the source documents.&lt;/li&gt;
&lt;li&gt;Split the content into meaningful paragraph-based chunks.&lt;/li&gt;
&lt;li&gt;Preserve headings and contextual metadata.&lt;/li&gt;
&lt;li&gt;Generate 768-dimensional embeddings.&lt;/li&gt;
&lt;li&gt;Store the chunks and embeddings in Supabase with pgvector.&lt;/li&gt;
&lt;li&gt;Embed the incoming query and rank relevant chunks using cosine similarity.&lt;/li&gt;
&lt;li&gt;Pass the selected context to the LLM.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This approach forms the retrieval layer of a retrieval-augmented generation (RAG) system.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Chunking documents without losing context
&lt;/h2&gt;

&lt;p&gt;A common mistake is to split a document every fixed number of characters without considering its structure.&lt;/p&gt;

&lt;p&gt;That can separate a paragraph from its heading or break a thought in the middle.&lt;/p&gt;

&lt;p&gt;Instead, I use paragraph-aware chunking and preserve contextual headers wherever possible.&lt;/p&gt;

&lt;p&gt;For example, a chunk might retain this structure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Document: Vector Search Architecture&lt;/li&gt;
&lt;li&gt;Section: Similarity Scoring&lt;/li&gt;
&lt;li&gt;Content: How cosine similarity ranks query and document embeddings.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This metadata helps make each chunk more meaningful on its own.&lt;/p&gt;

&lt;p&gt;For production systems, chunk size and overlap should be tested against the document type and retrieval quality rather than chosen arbitrarily.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Storing 768-dimensional embeddings with Supabase
&lt;/h2&gt;

&lt;p&gt;Once the content is chunked, an embedding model converts each chunk into a vector containing 768 numerical dimensions.&lt;/p&gt;

&lt;p&gt;These vectors represent semantic features learned by the embedding model. Similar meanings can be close in vector space, even when the exact words differ.&lt;/p&gt;

&lt;p&gt;Supabase provides a PostgreSQL database, while the pgvector extension supports vector storage and similarity operations.&lt;/p&gt;

&lt;p&gt;A simplified table could look like this:&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="k"&gt;create&lt;/span&gt; &lt;span class="n"&gt;extension&lt;/span&gt; &lt;span class="n"&gt;if&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;exists&lt;/span&gt; &lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="n"&gt;document_chunks&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;bigint&lt;/span&gt; &lt;span class="k"&gt;generated&lt;/span&gt; &lt;span class="n"&gt;always&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="k"&gt;identity&lt;/span&gt; &lt;span class="k"&gt;primary&lt;/span&gt; &lt;span class="k"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;metadata&lt;/span&gt; &lt;span class="n"&gt;jsonb&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="s1"&gt;'{}'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;jsonb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;768&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&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;Important:&lt;/strong&gt; The embedding model must actually produce 768-dimensional vectors. The database dimension needs to match the model output, and query and document embeddings must come from a compatible embedding space.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Ranking results with cosine similarity
&lt;/h2&gt;

&lt;p&gt;After a user submits a query, the application generates a query embedding and compares it with the stored document vectors.&lt;/p&gt;

&lt;p&gt;With pgvector, cosine distance can be calculated using the &lt;code&gt;&amp;lt;=&amp;gt;&lt;/code&gt; operator:&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="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;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&amp;gt;&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;similarity&lt;/span&gt;
&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;document_chunks&lt;/span&gt;
&lt;span class="k"&gt;order&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&amp;gt;&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;vector&lt;/span&gt;
&lt;span class="k"&gt;limit&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, &lt;code&gt;$1&lt;/code&gt; represents the query vector supplied by the application.&lt;/p&gt;

&lt;p&gt;The query returns the five nearest chunks according to cosine distance. The similarity expression is a convenient ranking score, not a calibrated probability that a result is correct.&lt;/p&gt;

&lt;p&gt;For larger datasets, vector indexes, query plans, filtering, and recall-versus-latency trade-offs become important.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Keeping retrieval fast
&lt;/h2&gt;

&lt;p&gt;A vector search system is only useful if it can return relevant context within the application's latency budget.&lt;/p&gt;

&lt;p&gt;My performance goal for this project is sub-200ms semantic search responses.&lt;/p&gt;

&lt;p&gt;To evaluate that properly, I would measure query latency separately from embedding generation, network overhead, and the subsequent LLM response. I would also track p50 and p95 latency across representative queries.&lt;/p&gt;

&lt;p&gt;The actual result depends on the dataset size, index configuration, hardware, query patterns, and network conditions.&lt;/p&gt;

&lt;p&gt;Fast retrieval also needs to preserve relevance. Returning the wrong passages quickly doesn't solve the original problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Why this architecture matters
&lt;/h2&gt;

&lt;p&gt;This design is useful for applications such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Documentation and codebase search&lt;/li&gt;
&lt;li&gt;Internal knowledge assistants&lt;/li&gt;
&lt;li&gt;Semantic search across long documents&lt;/li&gt;
&lt;li&gt;Retrieval-augmented generation pipelines&lt;/li&gt;
&lt;li&gt;Multimodal applications, when compatible text and non-text embeddings are supported by the selected model&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One distinction matters: a 768-dimensional vector is not automatically multimodal. Multimodal retrieval requires an embedding model that supports the relevant modalities and produces compatible vectors.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;Building a vector search engine taught me to treat retrieval as its own engineering problem rather than an afterthought in an LLM application.&lt;/p&gt;

&lt;p&gt;Good chunking preserves meaning. Embeddings make semantic comparison possible. pgvector makes retrieval practical inside PostgreSQL. And proper benchmarking tells you whether the architecture meets its performance goals.&lt;/p&gt;

&lt;p&gt;The next challenge is balancing retrieval quality, latency, and cost as the dataset grows.&lt;/p&gt;

&lt;p&gt;If you're building a RAG pipeline, I'd be interested to know what has worked best for you: smaller chunks, larger chunks, hybrid search, or reranking?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tech stack:&lt;/strong&gt; Node.js, TypeScript, Supabase, PostgreSQL, pgvector, vector embeddings, RAG.&lt;/p&gt;

&lt;h1&gt;
  
  
  node #typescript #ai #database #rag
&lt;/h1&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>The Hidden Cost of Unoptimized REST API Payloads in Node.js</title>
      <dc:creator>Abhishek tiwari</dc:creator>
      <pubDate>Thu, 15 Oct 2026 15:49:51 +0000</pubDate>
      <link>https://dev.to/abhishek-tiwari-dev/the-hidden-cost-of-unoptimized-rest-api-payloads-in-nodejs-26cd</link>
      <guid>https://dev.to/abhishek-tiwari-dev/the-hidden-cost-of-unoptimized-rest-api-payloads-in-nodejs-26cd</guid>
      <description>&lt;p&gt;Most REST API performance issues in Node.js apps don’t happen because of heavy queries.&lt;/p&gt;

&lt;p&gt;They happen because we send the entire database model down the wire when the client only needs two fields.&lt;/p&gt;

&lt;p&gt;Here is what usually happens:&lt;br&gt;
• Database returns 50 columns with nested relations.&lt;br&gt;
• Mongoose or TypeORM serializes the whole object graph.&lt;br&gt;
• JSON.stringify blocks the Node.js event loop for 40ms.&lt;br&gt;
• Network bandwidth gets wasted on metadata the client discards.&lt;/p&gt;

&lt;p&gt;Fixing this isn't about rewriting in Rust or upgrading your AWS instance.&lt;/p&gt;

&lt;p&gt;It’s about intentional API design:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Project your queries strictly at the SQL/ORM level using DTOs or explicit selects.&lt;/li&gt;
&lt;li&gt;Apply pagination limits by default — never trust &lt;code&gt;SELECT *&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Profile your JSON serialization overhead using clinic.js or built-in node --prof.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Fast APIs are built by moving less data, not just writing faster algorithms.&lt;/p&gt;

&lt;p&gt;What’s the largest JSON payload you've ever had to debug in production? Let's discuss in the comments.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>node</category>
      <category>postgres</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
