<?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: Tim Nguyen</title>
    <description>The latest articles on DEV Community by Tim Nguyen (@deko39).</description>
    <link>https://dev.to/deko39</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%2F1104417%2F201ab886-cbc6-4ee8-b671-562d7620fded.png</url>
      <title>DEV Community: Tim Nguyen</title>
      <link>https://dev.to/deko39</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/deko39"/>
    <language>en</language>
    <item>
      <title>COUNT(column) vs COUNT(*) in SQL — With INNER, LEFT, RIGHT &amp; FULL JOIN Explained</title>
      <dc:creator>Tim Nguyen</dc:creator>
      <pubDate>Tue, 24 Feb 2026 08:57:09 +0000</pubDate>
      <link>https://dev.to/deko39/countcolumn-vs-count-in-sql-with-inner-left-right-full-join-explained-1a1f</link>
      <guid>https://dev.to/deko39/countcolumn-vs-count-in-sql-with-inner-left-right-full-join-explained-1a1f</guid>
      <description>&lt;h1&gt;
  
  
  Counting seems simple in SQL… until joins enter the picture.
&lt;/h1&gt;

&lt;p&gt;One of the most common bugs in reporting queries comes from misunderstanding:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;COUNT(*) vs COUNT(column)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  1. The Core Rule
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;✅ COUNT(*) -&amp;gt; Counts rows&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;✅ COUNT(column) -&amp;gt; Counts non-NULL values in that column&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;That’s it.&lt;br&gt;
But joins create NULLs — and that’s where things change.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Example setup
&lt;/h3&gt;

&lt;p&gt;Customer&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;id&lt;/th&gt;
&lt;th&gt;name&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Alice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Bob&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Carol&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Orders&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;id&lt;/th&gt;
&lt;th&gt;customer_id&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;a. &lt;strong&gt;INNER JOIN&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT c.id, COUNT(*), COUNT(o.id)
FROM customers c
INNER JOIN orders o ON c.id = o.customer_id
GROUP BY c.id;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;id&lt;/th&gt;
&lt;th&gt;COUNT(*)&lt;/th&gt;
&lt;th&gt;COUNT(o.id)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Why?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;INNER JOIN&lt;/strong&gt; removes non-matching rows.&lt;br&gt;
No NULL rows are produced.&lt;/p&gt;

&lt;p&gt;So:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;COUNT(*) == COUNT(o.id)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No surprises here.&lt;/p&gt;

&lt;p&gt;b. &lt;strong&gt;LEFT JOIN (Where Most Bugs Happen)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT c.id, COUNT(*), COUNT(o.id)
FROM customers c
LEFT JOIN orders o ON c.id = o.customer_id
GROUP BY c.id;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;id&lt;/th&gt;
&lt;th&gt;COUNT(*)&lt;/th&gt;
&lt;th&gt;COUNT(o.id)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Why is Carol different?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LEFT JOIN&lt;/strong&gt; keeps all customers.&lt;br&gt;
For Carol: A row exists + o.id is NULL&lt;/p&gt;

&lt;p&gt;So:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;COUNT(*) counts the row → 1
COUNT(o.id) ignores NULL → 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Usage
&lt;/h3&gt;

&lt;p&gt;When you have to count multiple joined tables with different expressions, mind to use count + join properly and wisely&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Final thoughts
&lt;/h3&gt;

&lt;p&gt;Most production reporting bugs involving SQL counts come from misunderstanding how NULL behaves in outer joins.&lt;/p&gt;

&lt;p&gt;Once you internalize:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;COUNT(*) counts rows
COUNT(column) counts values
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You’ll stop shipping incorrect dashboards.&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>database</category>
      <category>sql</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>PostgreSQL MVCC vs MySQL Key-Next Locking: How Transaction Isolation Affects Concurrency</title>
      <dc:creator>Tim Nguyen</dc:creator>
      <pubDate>Tue, 09 Dec 2025 03:14:16 +0000</pubDate>
      <link>https://dev.to/deko39/postgresql-mvcc-vs-mysql-key-next-locking-how-transaction-isolation-affects-concurrency-3a37</link>
      <guid>https://dev.to/deko39/postgresql-mvcc-vs-mysql-key-next-locking-how-transaction-isolation-affects-concurrency-3a37</guid>
      <description>&lt;p&gt;When developers talk about ACID databases, two giants dominate: &lt;strong&gt;PostgreSQL&lt;/strong&gt; and &lt;strong&gt;MySQL (InnoDB)&lt;/strong&gt;. Both claim full MVCC (Multi-Version Concurrency Control) support, but their implementations are fundamentally different.&lt;/p&gt;

&lt;p&gt;This post explains the key differences, how transaction isolation levels behave in each, and why PostgreSQL is truly non-blocking for readers while MySQL often isn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR Summary
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;PostgreSQL&lt;/th&gt;
&lt;th&gt;MySQL InnoDB&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;MVCC Implementation&lt;/td&gt;
&lt;td&gt;Versioned tuples (true MVCC)&lt;/td&gt;
&lt;td&gt;Undo logs + next-key locking (hybrid)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Readers block writers?&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes (in REPEATABLE READ)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Writers block readers?&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes (in REPEATABLE READ)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Default isolation level&lt;/td&gt;
&lt;td&gt;READ COMMITTED&lt;/td&gt;
&lt;td&gt;REPEATABLE READ&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Non-blocking READ COMMITTED&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No (uses semi-consistent reads)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Phantom reads in default mode&lt;/td&gt;
&lt;td&gt;Possible (READ COMMITTED)&lt;/td&gt;
&lt;td&gt;Prevented (REPEATABLE READ + gap locks)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lock escalation / gap locks&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes (notorious source of deadlocks)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h1&gt;
  
  
  1. PostgreSQL's True MVCC: How It Achieves Non-Blocking Reads &amp;amp; Writes
&lt;/h1&gt;

&lt;p&gt;PostgreSQL is famous for its rock-solid ACID compliance and incredible concurrency. The secret sauce? &lt;strong&gt;True Multi-Version Concurrency Control (MVCC)&lt;/strong&gt; — an implementation that lets readers and writers fly past each other without ever blocking.&lt;/p&gt;

&lt;p&gt;Let’s dive deep into how it actually works under the hood.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "True MVCC" Really Means
&lt;/h2&gt;

&lt;p&gt;Every time a row is &lt;code&gt;UPDATE&lt;/code&gt; or &lt;code&gt;DELETE&lt;/code&gt;, PostgreSQL &lt;strong&gt;does not overwrite&lt;/strong&gt; the old row. Instead:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;new version (tuple)&lt;/strong&gt; of the row is created.&lt;/li&gt;
&lt;li&gt;Old versions get metadata:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;xmin&lt;/code&gt; → transaction ID that created this version&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;xmax&lt;/code&gt; → transaction ID that deleted/updated it (or still active)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Each transaction gets a &lt;strong&gt;snapshot&lt;/strong&gt; (via the transaction ID snapshot) that determines which row versions are visible to it.&lt;/li&gt;

&lt;li&gt;Dead tuples are cleaned up later by &lt;strong&gt;VACUUM&lt;/strong&gt;.&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Result: &lt;strong&gt;No read locks are ever needed&lt;/strong&gt;.&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;-- This SELECT will NEVER block, even if another transaction is updating the row right now&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;customer_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Visibility Rules — The Heart of PostgreSQL MVCC
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Condition&lt;/th&gt;
&lt;th&gt;Visible to my transaction?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Row's xmin committed and ≤ my snapshot&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Row's xmax committed and &amp;gt; my snapshot&lt;/td&gt;
&lt;td&gt;No (deleted/updated after I started)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Row's xmax is still active (in progress)&lt;/td&gt;
&lt;td&gt;No (not yet committed)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  How Isolation Levels Change Snapshot Behavior
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Level&lt;/th&gt;
&lt;th&gt;Snapshot Taken&lt;/th&gt;
&lt;th&gt;Non-Repeatable Reads?&lt;/th&gt;
&lt;th&gt;Phantom Reads?&lt;/th&gt;
&lt;th&gt;Blocking?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;READ COMMITTED&lt;/td&gt;
&lt;td&gt;New snapshot per statement&lt;/td&gt;
&lt;td&gt;Possible&lt;/td&gt;
&lt;td&gt;Possible&lt;/td&gt;
&lt;td&gt;Never&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;REPEATABLE READ&lt;/td&gt;
&lt;td&gt;Snapshot at first statement of tx&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Possible&lt;/td&gt;
&lt;td&gt;Never&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SERIALIZABLE&lt;/td&gt;
&lt;td&gt;Snapshot at tx start + SSI (predicate checks)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Never (aborts instead of deadlocks)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Yes — even in &lt;code&gt;SERIALIZABLE&lt;/code&gt;, PostgreSQL never blocks; it aborts one transaction if a conflict is detected.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real Example: Zero Blocking
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Session 1 (long-running report)&lt;/span&gt;
&lt;span class="k"&gt;BEGIN&lt;/span&gt; &lt;span class="n"&gt;TRANSACTION&lt;/span&gt; &lt;span class="k"&gt;ISOLATION&lt;/span&gt; &lt;span class="k"&gt;LEVEL&lt;/span&gt; &lt;span class="k"&gt;REPEATABLE&lt;/span&gt; &lt;span class="k"&gt;READ&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;-- runs for 20 seconds&lt;/span&gt;

&lt;span class="c1"&gt;-- Session 2 (concurrent write)&lt;/span&gt;
&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;35&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;999&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;-- finishes instantly!&lt;/span&gt;

&lt;span class="c1"&gt;-- Session 1 continues happily with its old snapshot&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;-- still sees pre-update data&lt;/span&gt;
&lt;span class="k"&gt;COMMIT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No waiting. No deadlocks. Pure magic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benifits and Trade-offs
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Benefits: 

&lt;ul&gt;
&lt;li&gt;Readers never block writers&lt;/li&gt;
&lt;li&gt;Writers never block readers&lt;/li&gt;
&lt;li&gt;Almost zero deadlocks (only from explicit FOR UPDATE/LOCK TABLE)&lt;/li&gt;
&lt;li&gt;Great for analytics + OLTP mixed workloads&lt;/li&gt;
&lt;li&gt;VACUUM and autovacuum keep bloat under control&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Trade-offs:

&lt;ul&gt;
&lt;li&gt;Table bloat if VACUUM lags (solved with good autovacuum tuning)&lt;/li&gt;
&lt;li&gt;Slightly higher write amplification (new tuple per update)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h1&gt;
  
  
  2. MySQL InnoDB: MVCC + Next-Key Locking (The Hidden Truth)
&lt;/h1&gt;

&lt;h1&gt;
  
  
  MySQL InnoDB’s MVCC Is Not What You Think: The Truth About Next-Key Locks
&lt;/h1&gt;

&lt;p&gt;Everyone says "InnoDB has MVCC". That’s technically true — but it’s &lt;strong&gt;not&lt;/strong&gt; the same as PostgreSQL’s non-blocking MVCC.&lt;/p&gt;

&lt;p&gt;InnoDB combines MVCC with &lt;strong&gt;row locks&lt;/strong&gt; and &lt;strong&gt;gap/next-key locks&lt;/strong&gt;, and the default isolation level (&lt;code&gt;REPEATABLE READ&lt;/code&gt;) turns innocent &lt;code&gt;SELECT&lt;/code&gt;s into blocking operations.&lt;/p&gt;

&lt;p&gt;Let’s uncover what really happens.&lt;/p&gt;

&lt;h2&gt;
  
  
  InnoDB’s Hybrid Approach
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Old row versions are stored in &lt;strong&gt;undo logs&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Each transaction gets a &lt;strong&gt;read view&lt;/strong&gt; (similar to a snapshot)&lt;/li&gt;
&lt;li&gt;But… to prevent phantom reads in &lt;code&gt;REPEATABLE READ&lt;/code&gt;, InnoDB uses &lt;strong&gt;next-key locking&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Record lock (on existing row)&lt;/li&gt;
&lt;li&gt;Gap lock (on the "gap" between index entries)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;This means: &lt;strong&gt;plain SELECTs can acquire locks and block INSERTs/UPDATEs&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Infamous Gap Lock Example
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Session 1&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;TRANSACTION&lt;/span&gt; &lt;span class="k"&gt;ISOLATION&lt;/span&gt; &lt;span class="k"&gt;LEVEL&lt;/span&gt; &lt;span class="k"&gt;REPEATABLE&lt;/span&gt; &lt;span class="k"&gt;READ&lt;/span&gt;&lt;span class="p"&gt;;&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;users&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;-- scans index on age&lt;/span&gt;

&lt;span class="c1"&gt;-- This query places GAP LOCKS on age ranges (...,20], (20,25], (25,30], (30,...) &lt;/span&gt;

&lt;span class="c1"&gt;-- Session 2 (gets BLOCKED!)&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Alice'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;27&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;-- Waits... forever... until Session 1 commits/rollbacks&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A harmless &lt;code&gt;SELECT&lt;/code&gt; just blocked an &lt;code&gt;INSERT&lt;/code&gt; on a non-existing row!&lt;/p&gt;

&lt;h2&gt;
  
  
  How Isolation Levels Really Behave in InnoDB
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Level&lt;/th&gt;
&lt;th&gt;Snapshot?&lt;/th&gt;
&lt;th&gt;Gap Locks?&lt;/th&gt;
&lt;th&gt;Readers Block Writers?&lt;/th&gt;
&lt;th&gt;Writers Block Readers?&lt;/th&gt;
&lt;th&gt;Deadlocks Common?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;READ UNCOMMITTED&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Rare&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;READ COMMITTED&lt;/td&gt;
&lt;td&gt;Partial&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Less&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;REPEATABLE READ (default)&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Very common&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SERIALIZABLE&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes + stricter&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Extremely common&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Yes — in the default &lt;code&gt;REPEATABLE READ&lt;/code&gt;, reads block writes and writes block reads.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why InnoDB Chose This Design
&lt;/h2&gt;

&lt;p&gt;Goal: Prevent phantom reads without predicate locking (like PostgreSQL’s SSI).&lt;br&gt;
Solution: Lock the gaps in indexes → no phantoms, but at the cost of blocking.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-World Pain Points
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A background analytics query can freeze your entire app&lt;/li&gt;
&lt;li&gt;Random deadlocks at high concurrency&lt;/li&gt;
&lt;li&gt;Hard to debug: &lt;code&gt;SHOW ENGINE INNODB STATUS&lt;/code&gt; shows giant lock wait chains&lt;/li&gt;
&lt;li&gt;Scaling writes often requires partitioning or read replicas&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When InnoDB’s Approach Is Acceptable
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Low-to-medium concurrency&lt;/li&gt;
&lt;li&gt;Mostly primary key lookups (no range scans → fewer gap locks)&lt;/li&gt;
&lt;li&gt;You’re already deep in the MySQL ecosystem&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to Reduce Pain (if you’re stuck with MySQL)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Use READ COMMITTED + row-based replication&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="k"&gt;SESSION&lt;/span&gt; &lt;span class="n"&gt;TRANSACTION&lt;/span&gt; &lt;span class="k"&gt;ISOLATION&lt;/span&gt; &lt;span class="k"&gt;LEVEL&lt;/span&gt; &lt;span class="k"&gt;READ&lt;/span&gt; &lt;span class="k"&gt;COMMITTED&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Avoid SELECT ... FOR UPDATE/SHARE unless necessary&lt;/span&gt;
&lt;span class="c1"&gt;-- Use primary key lookups instead of range scans when possible&lt;/span&gt;
&lt;span class="c1"&gt;-- Consider MariaDB (its default is often less aggressive)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  3. Conclusion
&lt;/h1&gt;

&lt;p&gt;InnoDB’s "MVCC" is a hybrid of versioning + locking.&lt;br&gt;
It gives you strong consistency (no phantoms by default) but sacrifices true non-blocking concurrency.&lt;br&gt;
If you ever wondered why your MySQL app randomly deadlocks at scale even though "you’re just reading" — now you know: your reads are silently locking gaps.&lt;br&gt;
PostgreSQL proved decades ago that you can have both MVCC and no blocking. MySQL chose a different trade-off.&lt;br&gt;
Choose your database accordingly.&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>mysql</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Why Your `@Transform` Decorator Doesn’t Run in class-transformer (and How to Fix It)</title>
      <dc:creator>Tim Nguyen</dc:creator>
      <pubDate>Sun, 07 Sep 2025 11:57:59 +0000</pubDate>
      <link>https://dev.to/deko39/why-your-transform-decorator-doesnt-run-in-class-transformer-and-how-to-fix-it-3ka6</link>
      <guid>https://dev.to/deko39/why-your-transform-decorator-doesnt-run-in-class-transformer-and-how-to-fix-it-3ka6</guid>
      <description>&lt;p&gt;If you’ve ever used &lt;a href="https://github.com/typestack/class-transformer" rel="noopener noreferrer"&gt;&lt;code&gt;class-transformer&lt;/code&gt;&lt;/a&gt; with &lt;code&gt;@Transform&lt;/code&gt;, you might have run into this weird situation:&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="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;reflect-metadata&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;plainToInstance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Transform&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;class-transformer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;IsEmail&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;IsNotEmpty&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;class-validator&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MobileSignupBody&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;IsNotEmpty&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;IsEmail&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Transform&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;normalized_email&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&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;dto&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;plainToInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;MobileSignupBody&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;abc@test.com&lt;/span&gt;&lt;span class="dl"&gt;'&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="nx"&gt;dto&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// MobileSignupBody { email: 'abc@test.com' }&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="nx"&gt;dto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;normalized_email&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// ❌ undefined&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wait… why is normalized_email undefined even though I clearly added a @Transform?&lt;/p&gt;

&lt;h1&gt;
  
  
  The Gotcha
&lt;/h1&gt;

&lt;p&gt;By default, class-transformer only runs @Transform on properties that exist in the plain object input.&lt;/p&gt;

&lt;p&gt;In this case, the plain object only had:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"abc@test.com"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since there was no normalized_email key, class-transformer skipped that property completely.&lt;br&gt;
That’s why the transform never fired.&lt;/p&gt;
&lt;h1&gt;
  
  
  The Fix: Use &lt;code&gt;@Expose&lt;/code&gt;
&lt;/h1&gt;

&lt;p&gt;You need to tell class-transformer: “always include this property, even if it’s not in the plain object.”&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Expose&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Transform&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;class-transformer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MobileSignupBody&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Expose&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// Will always include in class/plain object -&amp;gt; be transformed&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;IsNotEmpty&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;IsEmail&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Expose&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Transform&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;normalized_email&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&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;dto&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;plainToInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;MobileSignupBody&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;abc@test.com&lt;/span&gt;&lt;span class="dl"&gt;'&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="nx"&gt;dto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;normalized_email&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// ✅ "abc@test.com"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now it works because &lt;code&gt;@Expose&lt;/code&gt; forces &lt;code&gt;normalized_email&lt;/code&gt; into the transform pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Global Config Options
&lt;/h2&gt;

&lt;p&gt;If you don’t want to sprinkle &lt;code&gt;@Expose()&lt;/code&gt; everywhere, you have a couple of global strategies:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;excludeExtraneousValues&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dto&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;plainToInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;MobileSignupBody&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;abc@test.com&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="na"&gt;excludeExtraneousValues&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;In this mode, only properties with @Expose() are kept.&lt;br&gt;
Downside: you must decorate all properties you want to keep.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;strategy: 'exposeAll'&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Expose&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;exposeAll&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MobileSignupBody&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Expose&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Transform&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;normalized_email&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&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;This makes all properties exposed by default, and you only need @Expose() where you want transforms.&lt;/p&gt;
&lt;h2&gt;
  
  
  Alternative: Use a Getter
&lt;/h2&gt;

&lt;p&gt;Sometimes, the simplest solution is just a computed property:&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MobileSignupBody&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nf"&gt;normalized_email&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&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;No @Expose, no @Transform, always works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;@Transform&lt;/code&gt; only runs if the property exists in the plain object.&lt;br&gt;
Use &lt;code&gt;@Expose&lt;/code&gt; to force properties into the transformation pipeline.&lt;br&gt;
For global behavior, use excludeExtraneousValues or strategy: 'exposeAll'. Or skip it all and use getters if you just want a derived property.&lt;/p&gt;

&lt;h2&gt;
  
  
  🔗 Useful refs:
&lt;/h2&gt;

&lt;p&gt;class-transformer &lt;a href="https://github.com/typestack/class-transformer" rel="noopener noreferrer"&gt;docs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;class-validator &lt;a href="https://github.com/typestack/class-validator" rel="noopener noreferrer"&gt;docs&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Have you ever hit this @Transform gotcha? How do you usually handle derived fields in DTOs — getters, transforms, or something else? Let me know 👇&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>javascript</category>
      <category>typescript</category>
    </item>
    <item>
      <title>😬 The Silent Gotcha in MySQL: ENUMs and Non-Strict SQL Mode</title>
      <dc:creator>Tim Nguyen</dc:creator>
      <pubDate>Tue, 01 Jul 2025 15:31:45 +0000</pubDate>
      <link>https://dev.to/deko39/the-silent-gotcha-in-mysql-enums-and-non-strict-sql-mode-38c6</link>
      <guid>https://dev.to/deko39/the-silent-gotcha-in-mysql-enums-and-non-strict-sql-mode-38c6</guid>
      <description>&lt;p&gt;If you're using MySQL and rely on ENUM columns for validation, you might be silently inserting bad data without realizing it. Let's talk about what happens when SQL mode is not strict.&lt;/p&gt;

&lt;h3&gt;
  
  
  🧪 The Setup
&lt;/h3&gt;

&lt;p&gt;You define a table 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="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;marital_status&lt;/span&gt; &lt;span class="nb"&gt;ENUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;'single'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'married'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'unmarried'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'divorced'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'separated'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'widowed'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'never_married'&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;Looks solid, right? Now, let’s try inserting an invalid value:&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;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;marital_status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'unknown'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  😱 What Happens in Non-Strict Mode?
&lt;/h3&gt;

&lt;p&gt;If you're not in strict SQL mode, MySQL won't throw an error. It will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Insert the value as an empty string ''&lt;/li&gt;
&lt;li&gt;Silently trigger a warning&lt;/li&gt;
&lt;li&gt;Store it as the internal ENUM value 0&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Yep, your invalid input just became... nothing.&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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- Output:&lt;/span&gt;
&lt;span class="c1"&gt;-- | marital_status |&lt;/span&gt;
&lt;span class="c1"&gt;-- |----------------|&lt;/span&gt;
&lt;span class="c1"&gt;-- |                |  &amp;lt;-- EMPTY STRING!&lt;/span&gt;

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  🔍 Why This Happens
&lt;/h3&gt;

&lt;p&gt;Internally, MySQL maps ENUM values to integers (and enum in MySQL has index starting at 0 &lt;a href="https://dev.mysql.com/doc/refman/8.4/en/enum.html" rel="noopener noreferrer"&gt;docs&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Value      Index (internal)
''(empty)  0
'single'   1
'married'  2
...        ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you insert an invalid value, MySQL stores 0, which corresponds to ''.&lt;/p&gt;

&lt;h3&gt;
  
  
  🔥 How to Avoid It: Enable Strict Mode
&lt;/h3&gt;

&lt;p&gt;Use strict SQL mode to make MySQL behave properly:&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;SET&lt;/span&gt; &lt;span class="n"&gt;sql_mode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'STRICT_TRANS_TABLES'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now try that bad insert again:&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;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;marital_status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'unknown'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;-- ❌ ERROR 1265 (01000): Data truncated for column 'marital_status' at row 1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Perfect. You’re now protected from silent failures.&lt;/p&gt;

&lt;h3&gt;
  
  
  💡 Pro Tip: Make It Permanent
&lt;/h3&gt;

&lt;p&gt;Edit your MySQL config file:&lt;br&gt;
On Linux (/etc/mysql/my.cnf) or macOS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[mysqld]&lt;/span&gt;
&lt;span class="py"&gt;sql_mode&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;STRICT_TRANS_TABLES&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then restart MySQL.&lt;/p&gt;

&lt;p&gt;Read more: &lt;a href="https://dev.mysql.com/doc/refman/8.4/en/sql-mode.html#sql-mode-strict" rel="noopener noreferrer"&gt;SQL strict mode&lt;/a&gt;&lt;/p&gt;

</description>
      <category>mysql</category>
      <category>webdev</category>
      <category>prisma</category>
      <category>programming</category>
    </item>
    <item>
      <title>Avoid This Silent Timeout Bug with Axios in Lambda as proxy, ALB, and Content-Length</title>
      <dc:creator>Tim Nguyen</dc:creator>
      <pubDate>Thu, 19 Jun 2025 06:21:05 +0000</pubDate>
      <link>https://dev.to/deko39/avoid-this-silent-timeout-bug-with-axios-in-lambda-as-proxy-alb-and-content-length-1fi5</link>
      <guid>https://dev.to/deko39/avoid-this-silent-timeout-bug-with-axios-in-lambda-as-proxy-alb-and-content-length-1fi5</guid>
      <description>&lt;p&gt;Have you ever encountered a &lt;strong&gt;504 Gateway Timeout&lt;/strong&gt; from your &lt;strong&gt;AWS Application Load Balancer (ALB)&lt;/strong&gt; when proxying requests via &lt;strong&gt;Axios&lt;/strong&gt;, even though everything looks right on the surface?&lt;/p&gt;

&lt;p&gt;Here’s a tricky issue I ran into — and how to avoid it in the future.&lt;/p&gt;




&lt;h2&gt;
  
  
  💥 The Setup
&lt;/h2&gt;

&lt;p&gt;You're using AWS Lambda using a Node.js proxy to forward a &lt;code&gt;POST&lt;/code&gt; request to a backend behind ALB:&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://my-backend&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="na"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2025-06-10 10:49:46&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fEjck23HJkLlrRkDKtNWVI:APA91bHphS5as...&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="na"&gt;headers&lt;/span&gt;&lt;span class="p"&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;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;keep&lt;/span&gt; &lt;span class="nx"&gt;it&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="nx"&gt;original&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="o"&gt;&amp;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;Content-Length&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="nx"&gt;original&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="o"&gt;&amp;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;Looks harmless, but suddenly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;504 Gateway Timeout from ALB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the backend logs show:&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="nx"&gt;SyntaxError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Unterminated&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt; &lt;span class="nx"&gt;at&lt;/span&gt; &lt;span class="nx"&gt;position&lt;/span&gt; &lt;span class="nx"&gt;xxx&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🧠 Root Cause&lt;br&gt;
The error comes from a mismatch between the Content-Length and the actual body size.&lt;/p&gt;

&lt;p&gt;What actually happens:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Axios serializes the data object to JSON.&lt;/li&gt;
&lt;li&gt;You manually set Content-Length: xxx or forward it from orignial request, but the serialized body is longer.&lt;/li&gt;
&lt;li&gt;The backend receives only the first xxx bytes.&lt;/li&gt;
&lt;li&gt;It tries to parse, but the JSON is incomplete.&lt;/li&gt;
&lt;li&gt;It waits for the rest of the body, but it never comes.&lt;/li&gt;
&lt;li&gt;Eventually, the connection times out → ALB returns a 504.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;❌ Why Not a 400?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Normally, a malformed JSON gives a 400 Bad Request.&lt;/li&gt;
&lt;li&gt;But here:

&lt;ul&gt;
&lt;li&gt;The backend doesn't know the full body has been sent.&lt;/li&gt;
&lt;li&gt;It thinks the rest is still coming.&lt;/li&gt;
&lt;li&gt;So it never sends a response → ALB gives up and returns 504.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;✅ The Fix&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Just don’t manually set Content-Length. Let Axios compute it.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://my-backend&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="na"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2025-06-10 10:49:46&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fEjck23HJkLlrRkDKtNWVI:APA91b...&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="na"&gt;headers&lt;/span&gt;&lt;span class="p"&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;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;keep&lt;/span&gt; &lt;span class="nx"&gt;it&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="nx"&gt;original&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;// ✅ Do not manually set Content-Length&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;Axios will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Serialize the object to JSON&lt;/li&gt;
&lt;li&gt;Compute the correct length&lt;/li&gt;
&lt;li&gt;Set the header automatically&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Have fun coding!&lt;/p&gt;

</description>
      <category>lambda</category>
      <category>loadbalancer</category>
      <category>axios</category>
      <category>http</category>
    </item>
    <item>
      <title>CloudFront + S3 + Static domain routing</title>
      <dc:creator>Tim Nguyen</dc:creator>
      <pubDate>Wed, 11 Dec 2024 14:43:25 +0000</pubDate>
      <link>https://dev.to/deko39/cloudfront-s3-static-domain-routing-1afh</link>
      <guid>https://dev.to/deko39/cloudfront-s3-static-domain-routing-1afh</guid>
      <description>&lt;h2&gt;
  
  
  What are existing:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;S3 bucket 1: &lt;code&gt;xxx.s3.us-east-1.amazonaws.com&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;S3 bucket 2: &lt;code&gt;yyy.s3.us-east-1.amazonaws.com&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Domain name: &lt;code&gt;abc.com&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What is requiring:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Using only 1 static domain name: &lt;code&gt;static.abc.com&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Using only 1 CloudFront distribution to serve both S3 buckets above with prefix (S3 bucket 1 - prefix &lt;code&gt;admin&lt;/code&gt; and S3 bucket 2 - prefix &lt;code&gt;public&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Eg: What is served with &lt;code&gt;https://xxx.s3.us-east-1.amazonaws.com/img.jpg&lt;/code&gt; can be served with &lt;code&gt;https://static.abc.com/admin/img.jpg&lt;/code&gt; and what is served with &lt;code&gt;https://yyy.s3.us-east-1.amazonaws.com/img.png&lt;/code&gt; can be served with &lt;code&gt;https://static.abc.com/public/img.png&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Idea
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Create a distribution in CloudFront with 2 origins are 2 bucket above&lt;/li&gt;
&lt;li&gt;Create 3 distribution behaviors:

&lt;ul&gt;
&lt;li&gt;/admin/* - To Distribution's Origin of S3 Bucket 1&lt;/li&gt;
&lt;li&gt;/public/* - To Distribution's Origin of S3 Bucket 2&lt;/li&gt;
&lt;li&gt;Default(*) - To either Origin of S3 Bucket 1 or Origin of S3 Bucket 2&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Modify 3 distribution behaviors to have function associations to Lambda@Edge function (this will be in another post, but the document for create one &lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/edge-functions.html" rel="noopener noreferrer"&gt;here&lt;/a&gt;)&lt;/li&gt;

&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>Node.js + ioredis + elasticache</title>
      <dc:creator>Tim Nguyen</dc:creator>
      <pubDate>Thu, 05 Dec 2024 02:36:22 +0000</pubDate>
      <link>https://dev.to/deko39/nodejs-ioredis-elasticache-e7l</link>
      <guid>https://dev.to/deko39/nodejs-ioredis-elasticache-e7l</guid>
      <description>&lt;p&gt;For who is struggling with ioredis connection to elasticache (cluster enable)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const REDIS_COMMAND_TIMEOUT = 10000;
const MAX_REDIS_ATTEMPS = 3;
const REDIS_BACKOFF_RETRY = 2000;

const REDIS_HOST = process.env.REDIS_HOST || '127.0.0.1';
const REDIS_PORT = Number(process.env.REDIS_PORT) || 6379;

// https://github.com/redis/lettuce/wiki/Redis-URI-and-connection-details
const REDIS_SCHEME = REDIS_CLUSTER_ENABLED ? `rediss` : `redis`;

const redis = new Redis(`${REDIS_SCHEME}://${REDIS_HOST}:${REDIS_PORT}`, {
  commandTimeout: REDIS_COMMAND_TIMEOUT,
  retryStrategy: (times: number): number | null =&amp;gt; {
    if (times &amp;gt; MAX_REDIS_ATTEMPS) {
      return null; // Stop retrying
    }
    return REDIS_BACKOFF_RETRY; // Retry after some time
  },
  tls: REDIS_CLUSTER_ENABLED ? {} : undefined,
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>elasticache</category>
      <category>ioredis</category>
      <category>node</category>
    </item>
    <item>
      <title>MySQL syntaxes</title>
      <dc:creator>Tim Nguyen</dc:creator>
      <pubDate>Tue, 25 Jun 2024 08:23:44 +0000</pubDate>
      <link>https://dev.to/deko39/mysql-syntaxes-1moi</link>
      <guid>https://dev.to/deko39/mysql-syntaxes-1moi</guid>
      <description>&lt;h1&gt;
  
  
  What
&lt;/h1&gt;

&lt;p&gt;👎 &lt;code&gt;JSON_UNQUOTE + JSON_EXTRACT&lt;/code&gt; vs 👍 &lt;code&gt;-&amp;gt;&amp;gt;&lt;/code&gt;&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

&lt;p&gt;SELECT JSON_UNQUOTE(JSON_EXTRACT(metadata, 'field')) 👎&lt;br&gt;
SELECT metadata-&amp;gt;&amp;gt;'$.field' 👍&lt;/p&gt;

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

&lt;/div&gt;
&lt;h1&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Why it matters?&lt;br&gt;
&lt;/h1&gt;

&lt;p&gt;&lt;code&gt;JSON_EXTRACT&lt;/code&gt; vs &lt;code&gt;-&amp;gt;&lt;/code&gt; only return the json result&lt;br&gt;
&lt;code&gt;JSON_UNQUOTE + JSON_EXTRACT&lt;/code&gt; vs &lt;code&gt;-&amp;gt;&amp;gt;&lt;/code&gt; return the &lt;code&gt;parsed&lt;/code&gt; result (you can think of the JSON.parse method in JS)&lt;/p&gt;

</description>
      <category>syntaxes</category>
      <category>mysql</category>
    </item>
    <item>
      <title>Lambda configurations for ALB to reach</title>
      <dc:creator>Tim Nguyen</dc:creator>
      <pubDate>Fri, 07 Jun 2024 04:27:54 +0000</pubDate>
      <link>https://dev.to/deko39/lambda-permission-for-alb-to-reach-4a1n</link>
      <guid>https://dev.to/deko39/lambda-permission-for-alb-to-reach-4a1n</guid>
      <description>&lt;p&gt;We need&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A lambda function:

&lt;ul&gt;
&lt;li&gt;No need Function URL enabled&lt;/li&gt;
&lt;li&gt;VPC configs to be in the same VPC and subnet with main VPC used by ALB&lt;/li&gt;
&lt;li&gt;Security group with inbound from main VPC CIDR block (eg: 10.1.0.0/16) and outbound to internet (eg: 0.0.0.0/0) &lt;/li&gt;
&lt;li&gt;Permission set to accept both &lt;strong&gt;ALB arn and Target group arn&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;A target group set to lambda function&lt;/li&gt;
&lt;li&gt;An application load balancer has a listener to lambda target group above&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>aws</category>
    </item>
    <item>
      <title>AWS Lambda - Socket hang up Nodejs</title>
      <dc:creator>Tim Nguyen</dc:creator>
      <pubDate>Thu, 05 Oct 2023 06:53:32 +0000</pubDate>
      <link>https://dev.to/deko39/aws-lambda-socket-hang-up-nodejs-ind</link>
      <guid>https://dev.to/deko39/aws-lambda-socket-hang-up-nodejs-ind</guid>
      <description>&lt;h2&gt;
  
  
  Context: Lambda error said 'socket hang up' within nodejs code when performing http/https request to 3rd party backend
&lt;/h2&gt;

&lt;p&gt;Let's describe my tries on solving the problem&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Increase the timeout of lambda -&amp;gt; Not help&lt;/li&gt;
&lt;li&gt;Increase the timeout of httpClient -&amp;gt; Not help&lt;/li&gt;
&lt;li&gt;Figure out that lambda having some problem with connection -&amp;gt; Put HttpAgent to keepalive the connection -&amp;gt; Work perfectly&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Code demo:
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;new JwksClient({
      jwksUri: 'xxx' + '/oidc/2/certs',
      timeout: 10000,
      requestAgent: new Agent({
        keepAlive: true
      })
    })
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>node</category>
      <category>lambda</category>
      <category>http</category>
      <category>https</category>
    </item>
    <item>
      <title>Quote hàng ngày #1</title>
      <dc:creator>Tim Nguyen</dc:creator>
      <pubDate>Mon, 19 Jun 2023 14:54:59 +0000</pubDate>
      <link>https://dev.to/deko39/quote-hang-ngay-1-1g0i</link>
      <guid>https://dev.to/deko39/quote-hang-ngay-1-1g0i</guid>
      <description>&lt;h2&gt;
  
  
  Quote hôm nay là...
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Good code is not just functional, it is also beautiful. Good code is organized, easy to read, and well documented. Organization can be achieved by separating code into useful functions and collecting functions into modules or libraries. Good organization means that at any one time, we only need to focus on a small part of a program.&lt;br&gt;
(&lt;em&gt;Computer Science I - Dr. Chris Bourke&lt;/em&gt;)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Chuyện là...
&lt;/h2&gt;

&lt;p&gt;Hôm nay rảnh nợ ngồi đọc quyển &lt;em&gt;Computer Science&lt;/em&gt; thì thấy đoạn quote khá hợp ý và mình cũng chiêm nghiệm khá đúng khi đi làm một vài năm. Đôi khi có một vài bạn fresher hoặc junior được mình kèm hoặc muốn được mình góp ý để có thể code tốt hơn sau nhiều lần ăn blame từ các senior, mentor hay leader 🤣.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mình nghĩ rằng...
&lt;/h2&gt;

&lt;p&gt;Việc code tốt lên thì cũng có nhiều yếu tố, có lý do khách quan ví dụ như được ở trong môi trường làm việc chuẩn, có đủ tài nguyên cần thiết để phát triển, cũng có lý do chủ quan, do bản thân tự thân mày mò để mình tốt hơn, ví dụ như tự đi tham khảo tài liệu, học từ những người giỏi hơn. Chung quy đều là phải có tài liệu, phải có tiêu chuẩn, từ tiêu chuẩn thì mới có thể phát triển mở rộng ra những thứ khác được. Ở đâu đó mình có nghe 1 câu là "&lt;em&gt;Khi bạn có 1 nền tảng vững chắc rồi thì việc bạn giỏi lên chỉ còn là vấn đề thời gian.&lt;/em&gt;"&lt;/p&gt;

&lt;h2&gt;
  
  
  Đúc kết...
&lt;/h2&gt;

&lt;p&gt;Quay lại việc code tốt lên, ngoài việc code chạy được, chạy đúng ra thì việc code sạch, đẹp, dễ bảo trì cũng là một yếu tố tiên quyết để đánh giá 1 developer có đủ tính kiên nhẫn, tỉ mỉ và gọn gàng hay không. Qua vài năm đi làm thợ code thì mình đúc kết lại được như thế này: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Đầu tiên, bạn phải biết bạn đang làm gì giữa 1 đống chữ (Điều này rất quan trọng, bạn phải biết bạn đang làm gì khi muốn thêm 1 phần code vào giữa những phần khác, phải đánh giá được nó sẽ ảnh hưởng thế nào với những phần code còn lại)&lt;/li&gt;
&lt;li&gt;Thứ hai, bạn cần phải đặt tên theo một tiêu chuẩn nhất định (cái này gọi là naming convention, refer cái repo này: &lt;a href="https://github.com/kettanaito/naming-cheatsheet#naming-convention"&gt;https://github.com/kettanaito/naming-cheatsheet#naming-convention&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Thứ ba, nhận biết một hàm như thế nào là đủ "lớn" và cần refactor (về vấn đề này thực sự có khá nhiều ý kiến, nhưng theo kinh nghiệm cá nhân mình, một hàm thực sự chỉ nên có dưới 50 dòng không tính khoảng trống và comment)&lt;/li&gt;
&lt;li&gt;Thứ tư, một hàm nên chỉ làm một nhiệm vụ (ví dụ hàm getMessage thì sẽ không tạo thêm message, nó làm mất đi tính uniqueness của hàm và khiến việc đặt tên hàm dường như không còn nhiều ý nghĩa, cùng với việc viết unit test sẽ có cảm giác như đang viết e2e testing vậy)&lt;/li&gt;
&lt;li&gt;Thứ năm, guard clause có thể giúp bạn code đẹp hơn (nếu bạn thấy quá nhiều if-else lồng nhau thì bạn nên dừng lại và nghĩ về việc refactor đoạn if-else sử dụng điều kiện tổng hợp hoặc guard clause, như này: &lt;a href="https://deviq.com/design-patterns/guard-clause"&gt;https://deviq.com/design-patterns/guard-clause&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Thứ sáu, nên tuân theo các design pattern và best practice trên internet, đừng tự invent lại những gì đã có sẵn trừ khi bạn nắm chắc được hầu hết các chuẩn khác và nhận ra "Ồ cái này có thể làm tốt hơn")&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Và quan trọng hơn cả là luôn học hỏi từ những thứ trên internet, bất cứ điều gì có thể học được đều luôn tốt. Have a nice day!!&lt;/p&gt;

</description>
      <category>quote</category>
      <category>webdev</category>
      <category>beginners</category>
      <category>vietnam</category>
    </item>
  </channel>
</rss>
