<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Tech Tonic - Medium]]></title>
        <description><![CDATA[Articles on popular things like Node.js, Deno, Bun, etc. - Medium]]></description>
        <link>https://medium.com/deno-the-complete-reference?source=rss----cca06f8d4889---4</link>
        <image>
            <url>https://cdn-images-1.medium.com/proxy/1*TGH72Nnw24QL3iV9IOm4VA.png</url>
            <title>Tech Tonic - Medium</title>
            <link>https://medium.com/deno-the-complete-reference?source=rss----cca06f8d4889---4</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 15 Nov 2026 04:23:52 GMT</lastBuildDate>
        <atom:link href="https://medium.com/feed/deno-the-complete-reference" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[10 Javascript Interview Questions — part 1]]></title>
            <link>https://medium.com/deno-the-complete-reference/10-javascript-interview-questions-part-1-15df79bfa078?source=rss----cca06f8d4889---4</link>
            <guid isPermaLink="false">https://medium.com/p/15df79bfa078</guid>
            <category><![CDATA[javascript]]></category>
            <dc:creator><![CDATA[Mayank C]]></dc:creator>
            <pubDate>Thu, 21 Aug 2025 20:23:44 GMT</pubDate>
            <atom:updated>2025-08-21T20:23:43.962Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*vJl9cH4G0bmNF93SlT6jhw.png" /></figure><h3>10 Javascript Interview Questions — part 1</h3><p>While AI is great, and we use it a lot while coding, we still have to know JavaScript to clear JS interviews. We need to know the fundamentals and advanced concepts. In this article series, we’ll go through interesting JS questions that will test your knowledge of the language. Let’s get started.</p><h4>1 What happens when you call setTimeout with a delay of 0, and why might the actual execution time vary?</h4><p>When you call setTimeout(callback, 0), the callback function is not executed immediately. Instead, it&#39;s placed at the back of the <strong>message queue</strong>. The JavaScript engine will only execute this callback once the current call stack is empty. This is why the actual execution time may vary—it depends on the length of time it takes to process all the code currently in the call stack before the event loop can pull the callback from the message queue.</p><p>Here’s a simple example to illustrate this behavior:</p><pre>console.log(&#39;Start&#39;);<br><br>setTimeout(() =&gt; {<br>  console.log(&#39;Callback executed&#39;);<br>}, 0);<br><br>console.log(&#39;End&#39;);<br><br>// The expected output will be:<br>// Start<br>// End<br>// Callback executed</pre><p><strong>Why the Actual Execution Time Varies</strong></p><p>The delay isn’t a guaranteed execution time, but a <strong>minimum delay</strong>. Several factors can influence the actual time of execution:</p><ul><li><strong>Call Stack:</strong> If the current code in the call stack is computationally expensive or takes a long time to run, the setTimeout callback will have to wait for it to finish. The callback only gets its turn when the stack is clear.</li><li><strong>Event Loop:</strong> The event loop is what checks the call stack and the message queue. It continuously pushes new tasks from the message queue to the call stack when the stack is empty.</li><li><strong>Browser/Node.js Environment:</strong> The JavaScript engine’s internal workings, including other tasks in the message queue (like user interactions or network requests), can affect the order and timing of execution.</li></ul><p>This mechanism ensures that JavaScript, being a single-threaded language, doesn’t block the main thread. It allows for asynchronous operations without freezing the user interface.</p><h4>2 Explain the difference between Object.create(null) and {}, and when you would use each.</h4><p>The primary difference between Object.create(null) and {} lies in their prototype chain.</p><ul><li>{} is a shorthand for new Object(). It creates a plain object that inherits from Object.prototype. This means it has access to common methods and properties like toString(), hasOwnProperty(), and isPrototypeOf().</li><li>Object.create(null) creates an object with no prototype at all. It&#39;s a truly empty object, often referred to as a &quot;dictionary&quot; or &quot;hash map,&quot; with no inherited properties or methods from Object.prototype.</li></ul><p>This distinction can be seen in the following example:</p><pre>const obj1 = {};<br>const obj2 = Object.create(null);<br><br>console.log(obj1.toString); // function toString() { ... }<br>console.log(obj2.toString); // undefined<br><br>console.log(obj1.hasOwnProperty(&#39;a&#39;)); // false<br>// console.log(obj2.hasOwnProperty(&#39;a&#39;)); // TypeError: obj2.hasOwnProperty is not a function<br><br>console.log(Object.getPrototypeOf(obj1)); // [Object: null prototype] {}<br>console.log(Object.getPrototypeOf(obj2)); // null</pre><p><strong>When to Use Each</strong></p><p><em>Use </em><em>{} when:</em></p><p>You need a standard object for general-purpose use. This is the most common use case for objects in JavaScript. Since it inherits from Object.prototype, you can use built-in methods like toString(), hasOwnProperty(), and others without any special checks.</p><p><em>Use </em><em>Object.create(null) when:</em></p><ul><li><strong>You need a pure data map:</strong> Use this when you want to create an object that will only serve as a hash map or dictionary. It’s safe from prototype pollution, which is a common security vulnerability.</li><li><strong>You’re using the object to store user-provided keys:</strong> A malicious user could provide a key like &quot;toString&quot;, which would overwrite the inherited method in a regular object ({}) but won&#39;t affect an object created with Object.create(null).</li><li><strong>Performance is critical:</strong> An object with no prototype chain can be slightly faster for property lookups and access, as the JavaScript engine doesn’t have to traverse the prototype chain.</li><li><strong>You’re building an API or a library:</strong> When building an API or a library that exposes objects, using Object.create(null) ensures that the consumers of your API don&#39;t accidentally rely on inherited methods that you haven&#39;t explicitly documented or intended to be part of the object&#39;s public API.</li></ul><h4>3 What are the potential issues with this code, and how would you fix them?</h4><pre>for (var i = 0; i &lt; 3; i++) {<br>  setTimeout(() =&gt; console.log(i), 100);<br>}</pre><p>The code will print 3 three times instead of 0, 1, and 2 as one might expect. This happens because of a classic JavaScript problem involving var and closures within a loop.</p><p><strong>The Issue</strong></p><p>The core issue is that var has function scope, not block scope. The variable i is declared once and is hoisted to the top of the function or global scope. The setTimeout callback function, which executes asynchronously after the loop has finished, forms a closure over the variable i. By the time the callbacks are executed, the loop has already completed, and the value of i has been incremented to 3. All three callbacks reference the same i variable in the outer scope, which holds the final value of 3.</p><p><strong>Solutions</strong></p><p>There are three common ways to fix this issue, each utilizing a different approach to create a new, distinct scope for each iteration.</p><p><em>1. Using </em><em>let</em></p><p>The most modern and idiomatic solution is to use the let keyword, which has block scope. A new i is created for each iteration of the loop, so each setTimeout callback captures the correct value.</p><pre>for (let i = 0; i &lt; 3; i++) {<br>  setTimeout(() =&gt; console.log(i), 100);<br>}<br>// Expected Output: 0, 1, 2</pre><p><em>2. Using an Immediately Invoked Function Expression (IIFE)</em></p><p>Before let was widely adopted, a common solution was to wrap the setTimeout call in an IIFE. The IIFE creates a new function scope for each iteration, and by passing i as an argument, you can capture its value. This is a functional programming approach to creating a closure.</p><pre>for (var i = 0; i &lt; 3; i++) {<br>  ((j) =&gt; {<br>    setTimeout(() =&gt; console.log(j), 100);<br>  })(i);<br>}<br>// Expected Output: 0, 1, 2</pre><p><em>3. Using </em><em>bind()</em></p><p>Another less common but valid solution is to use bind(). The first argument to bind() is the value for this, which is not relevant here, so we pass null. The second argument allows you to pass a value to be used as an argument to the function when it&#39;s executed, effectively creating a new scope for each call.</p><pre>for (var i = 0; i &lt; 3; i++) {<br>  setTimeout(console.log.bind(null, i), 100);<br>}<br>// Expected Output: 0, 1, 2</pre><h4>4 How does JavaScript’s garbage collection work, and what can cause memory leaks in modern applications?</h4><p>JavaScript’s garbage collection is primarily handled by an algorithm called <strong>mark-and-sweep</strong>. The garbage collector’s job is to reclaim memory that’s no longer in use by the program.</p><p><strong>The Mark-and-Sweep Algorithm</strong></p><ol><li><strong>Marking Phase:</strong> The garbage collector starts from the <strong>root</strong> objects, which are variables in the global scope (like window or global), and objects on the current call stack. It then &quot;marks&quot; all objects that are reachable from these roots. This includes following references from the root objects to other objects, and then from those objects to others, and so on. Any object that can be reached from the root is considered &quot;in use.&quot;</li><li><strong>Sweeping Phase:</strong> The garbage collector then “sweeps” through all memory. Any objects that were not marked as reachable are considered “garbage” and are removed, and their memory is freed.</li></ol><p><strong>Common Causes of Memory Leaks</strong></p><p>Although JavaScript’s automatic garbage collection prevents many memory leaks, some common patterns in modern applications can still cause them. These leaks occur when objects that are no longer needed are still being referenced, preventing the garbage collector from freeing their memory.</p><ol><li><strong>Global Variables:</strong> Declaring variables without a keyword (var, let, or const) inside a function will make them global. They will then be referenced by the global object and will not be garbage collected until the application is closed.</li></ol><pre>function createLeak() {<br>  // &#39;leaky&#39; becomes a global variable (e.g., window.leaky in a browser)<br>  leaky = &#39;I am leaking!&#39;;<br>}</pre><p><strong>2. Forgotten Timers and Event Listeners:</strong> If you set a setTimeout, setInterval, or an event listener without clearing it, the callback function will hold a reference to the objects within its scope. This prevents those objects from being garbage collected, even if the element or component they are associated with is removed from the DOM.</p><pre>const element = document.getElementById(&#39;my-button&#39;);<br>element.addEventListener(&#39;click&#39;, () =&gt; {<br>  // This listener will not be garbage collected until the page unloads,<br>  // even if `element` is removed from the DOM.<br>});<br><br>// To prevent the leak, you must remove the listener<br>// element.removeEventListener(&#39;click&#39;, ...);</pre><p><strong>3. Detached DOM Elements:</strong> If you remove a DOM element from the document but a JavaScript variable still holds a reference to it, the entire element and its data will remain in memory. The garbage collector sees this as a valid reference and won’t free the memory.</p><pre>let myElement = document.getElementById(&#39;some-id&#39;);<br>document.body.removeChild(myElement); // The element is removed from the DOM.<br>// However, `myElement` variable still holds a reference, causing a leak.<br>myElement = null; // Setting the reference to null prevents the leak.</pre><p><strong>4. Improper Closures:</strong> While closures are a powerful feature, they can cause memory leaks if not used carefully. A closure that references a large object or an element from an outer scope will keep that object in memory as long as the closure exists. If that closure is never released, the referenced object can never be collected.</p><h4>5 Explain the differences between call, apply, and bind, then implement your own version of bind.</h4><p>call, apply, and bind are all methods used to explicitly set the this context for a function. The main difference lies in how they handle function execution and arguments.</p><p><strong>call</strong></p><ul><li>Executes the function immediately.</li><li>The first argument is the this value.</li><li>The subsequent arguments are passed to the function individually, one by one.</li></ul><p><strong>Syntax:</strong> function.call(thisArg, arg1, arg2, ...)</p><pre>const person = { name: &#39;Alice&#39; };<br>function greet(city, country) {<br>  console.log(`Hello, my name is ${this.name} and I live in ${city}, ${country}.`);<br>}<br><br>greet.call(person, &#39;New York&#39;, &#39;USA&#39;);<br>// Output: Hello, my name is Alice and I live in New York, USA.</pre><p><strong>apply</strong></p><ul><li>Executes the function immediately.</li><li>The first argument is the this value.</li><li>The arguments are passed to the function as a single array or an array-like object.</li></ul><p><strong>Syntax:</strong> function.apply(thisArg, [argsArray])</p><pre>const person = { name: &#39;Bob&#39; };<br>function greet(city, country) {<br>  console.log(`Hello, my name is ${this.name} and I live in ${city}, ${country}.`);<br>}<br><br>greet.apply(person, [&#39;London&#39;, &#39;UK&#39;]);<br>// Output: Hello, my name is Bob and I live in London, UK.</pre><p><strong>bind</strong></p><ul><li>Returns a new function with the this context permanently bound.</li><li>Does not execute the function immediately.</li><li>Arguments can be passed to the returned function at a later time.</li></ul><p><strong>Syntax:</strong> const newFunction = function.bind(thisArg, arg1, arg2, ...)</p><pre>const person = { name: &#39;Charlie&#39; };<br>function greet() {<br>  console.log(`Hello, my name is ${this.name}.`);<br>}<br><br>const boundGreet = greet.bind(person);<br>boundGreet();<br>// Output: Hello, my name is Charlie.</pre><p>The main takeaway is: <strong>call and </strong><strong>apply are for immediate execution, while </strong><strong>bind is for creating a new, reusable function.</strong> The only difference between call and apply is how they accept arguments.</p><p><strong>Implementing Your Own </strong><strong>bind</strong></p><p>To create a custom bind implementation, we need a function that returns another function. This returned function should be able to accept its own arguments and combine them with any arguments passed to the original bind call. We&#39;ll use a Symbol for the method name to avoid potential conflicts with existing properties on Function.prototype.</p><pre>Function.prototype.myBind = function(thisArg, ...args) {<br>  const func = this; // &#39;this&#39; refers to the function on which myBind is called.<br>  return function(...restArgs) {<br>    // Combine arguments from myBind and the returned function.<br>    const allArgs = [...args, ...restArgs];<br>    // Use apply to call the original function with the correct context and arguments.<br>    return func.apply(thisArg, allArgs);<br>  };<br>};<br><br>// Example usage with our custom myBind<br>const person = { name: &#39;Dave&#39; };<br>function greet(city, country) {<br>  console.log(`Hello, my name is ${this.name} and I live in ${city}, ${country}.`);<br>}<br><br>const boundGreet = greet.myBind(person, &#39;Tokyo&#39;);<br>boundGreet(&#39;Japan&#39;);<br>// Output: Hello, my name is Dave and I live in Tokyo, Japan.</pre><h4>6 What is the temporal dead zone, and how does it affect let, const, and var declarations?</h4><p>The <strong>Temporal Dead Zone (TDZ)</strong> is a behavioral characteristic of let and const declarations. It&#39;s the period between the beginning of a block scope and the point where a variable is declared and initialized with a value. During this time, the variable exists but cannot be accessed. Any attempt to access it will result in a ReferenceError.</p><p><strong>How It Affects Declarations</strong></p><p><strong>let and const</strong></p><p>Both let and const declarations are hoisted, but they are not initialized. This creates the TDZ. The variable name is &quot;hoisted&quot; to the top of its block scope, but it remains in a state where it cannot be accessed until the code execution reaches its actual declaration line.</p><p><em>Example:</em></p><pre>{<br>  // TDZ for `x` starts here<br>  console.log(x); // ReferenceError: Cannot access &#39;x&#39; before initialization<br>  let x = &#39;hello&#39;; // TDZ for `x` ends here<br>}</pre><p>This behavior is a key feature that helps catch common programming errors and promotes better code practices by making variable declaration explicit.</p><p><strong>var</strong></p><p>The var keyword does not have a temporal dead zone. Variables declared with var are fully hoisted—both their declaration and an initialization to undefined are moved to the top of their function or global scope. This means you can access a var variable before its declaration line without getting a ReferenceError.</p><pre>{<br>  console.log(y); // undefined<br>  var y = &#39;world&#39;;<br>  console.log(y); // world<br>}</pre><p>While this might seem flexible, it can lead to unexpected behavior and subtle bugs, which is one of the main reasons let and const were introduced to the language. The TDZ for let and const provides more predictable and safer variable scoping.</p><h4>7 How would you implement deep equality comparison for two JavaScript objects, handling edge cases like circular references?</h4><p>Deep equality comparison for two JavaScript objects is the process of checking if their properties and values are strictly equal, recursively. This goes beyond a simple === check, which only compares object references. A robust implementation needs to handle various data types and, importantly, circular references to prevent infinite loops.</p><p><strong>Implementation without Circular Reference Handling</strong></p><p>A basic recursive function can handle most cases. It would check for:</p><ul><li><strong>Simple data types:</strong> Use strict equality (===) for primitives.</li><li><strong>Object and array types:</strong> Recursively call the function on their properties and elements.</li><li><strong>Null and other edge cases:</strong> Handle null and non-object types appropriately.</li></ul><p>Here’s a simple example that works for non-circular objects:</p><pre>function isEqual(obj1, obj2) {<br>  // 1. Check for strict equality (handles primitives and same object reference)<br>  if (obj1 === obj2) {<br>    return true;<br>  }<br><br>  // 2. Check for null or non-object types<br>  if (obj1 === null || typeof obj1 !== &#39;object&#39; || obj2 === null || typeof obj2 !== &#39;object&#39;) {<br>    return false;<br>  }<br><br>  // 3. Get keys and check if they have the same number of properties<br>  const keys1 = Object.keys(obj1);<br>  const keys2 = Object.keys(obj2);<br><br>  if (keys1.length !== keys2.length) {<br>    return false;<br>  }<br><br>  // 4. Recursively check properties<br>  for (const key of keys1) {<br>    if (!keys2.includes(key) || !isEqual(obj1[key], obj2[key])) {<br>      return false;<br>    }<br>  }<br><br>  return true;<br>}</pre><p>This implementation works well, but it will cause a stack overflow error if it encounters a circular reference.</p><p><strong>Implementing Deep Equality with Circular Reference Handling</strong></p><p>To handle circular references, we need to keep track of the objects we have already visited during the recursion. A good way to do this is with a WeakMap or two arrays to store the objects from each comparison pair. When comparing two objects, we first check if they have already been seen together in the current comparison chain.</p><p>Here is a more robust implementation using a WeakMap to store object pairs that have been visited.</p><pre>function isEqualCircular(obj1, obj2, map = new WeakMap()) {<br>  // 1. Strict equality check for primitives and same object reference<br>  if (obj1 === obj2) {<br>    return true;<br>  }<br><br>  // 2. Handle null or non-object types<br>  if (obj1 === null || typeof obj1 !== &#39;object&#39; || obj2 === null || typeof obj2 !== &#39;object&#39;) {<br>    return false;<br>  }<br><br>  // 3. Check for circular references<br>  // If we&#39;ve already compared these two objects, return true to prevent an infinite loop.<br>  // We use a WeakMap to avoid memory leaks.<br>  if (map.get(obj1) === obj2) {<br>    return true;<br>  }<br>  map.set(obj1, obj2); // Store the current pair of objects<br><br>  // 4. Handle array and object types<br>  if (Array.isArray(obj1) !== Array.isArray(obj2)) {<br>    return false;<br>  }<br><br>  const keys1 = Object.keys(obj1);<br>  const keys2 = Object.keys(obj2);<br><br>  if (keys1.length !== keys2.length) {<br>    return false;<br>  }<br><br>  // 5. Recursively check properties<br>  for (const key of keys1) {<br>    // We need to check if key exists in obj2 to avoid potential errors<br>    if (!Object.prototype.hasOwnProperty.call(obj2, key) || !isEqualCircular(obj1[key], obj2[key], map)) {<br>      return false;<br>    }<br>  }<br><br>  return true;<br>}<br><br>// Example with a circular reference<br>const a = { b: null };<br>const b = { a: a };<br>a.b = b;<br><br>const c = { d: null };<br>const d = { c: c };<br>c.d = d;<br><br>console.log(isEqualCircular(a, c)); // true</pre><h4>8 What are the performance implications of different iteration methods (for, forEach, map, for…of) and when would you choose each?</h4><p>Different iteration methods in JavaScript have varying performance characteristics, with for loops generally being the fastest and most flexible, while array-specific methods like forEach and map provide better readability and functional benefits at a small performance cost.</p><p><strong>Performance Implications</strong></p><ul><li><strong>for loop:</strong> This is the <strong>fastest</strong> iteration method. It&#39;s a low-level construct with minimal overhead. It has direct control over the loop index, making it memory-efficient and ideal for large datasets or performance-critical tasks.</li><li><strong>for...of loop:</strong> This loop is designed to iterate over iterable objects, such as arrays, strings, and maps. It is generally <strong>faster than </strong><strong>forEach</strong> and map because it doesn&#39;t involve a function call for each iteration. It provides a clean syntax and avoids common off-by-one errors associated with for loops.</li><li><strong>forEach:</strong> This is an array method that calls a callback function for each element. The performance overhead comes from the function call in each iteration. While this overhead is negligible for small arrays, it can accumulate for large datasets. It also <strong>cannot be stopped or broken</strong> using break or return, which can be a limitation.</li><li><strong>map:</strong> Similar to forEach, map also involves a function call for each element, incurring a similar performance cost. However, its main distinction is that it <strong>returns a new array</strong> with the results of the callback function. This can be less performant than for loops if you don&#39;t need to create a new array.</li></ul><p><strong>When to Choose Each</strong></p><ul><li><strong>for loop:</strong> Choose a for loop when <strong>performance is the top priority</strong>, especially with large datasets, or when you need fine-grained control over the iteration, such as skipping elements, breaking out of the loop, or iterating in reverse.</li><li><strong>for...of loop:</strong> Use for...of when you want a simple and readable way to <strong>iterate over the values of an array or iterable object</strong> and do not need the index. It&#39;s a great balance of performance and readability.</li><li><strong>forEach:</strong> This is the go-to method for iterating over an array when <strong>readability is more important than absolute performance</strong>, and you need to perform a side effect for each element (e.g., logging to the console, pushing to another array).</li><li><strong>map:</strong> Choose map specifically when you need to <strong>transform each element of an array into a new value</strong> and create a new array containing the results. It&#39;s a core concept of functional programming and makes your code declarative and concise.</li></ul><h4>9 Explain how JavaScript’s this binding works in arrow functions vs regular functions, especially in the context of event handlers and method calls.</h4><p>In JavaScript, the behavior of this is a crucial difference between regular functions and arrow functions. The key is that a regular function&#39;s this is dynamic and determined by how the function is called, while an arrow function&#39;s this is lexically scoped, meaning it&#39;s determined by the surrounding code where it was defined.</p><p><strong>Regular Functions</strong></p><p>The this value in a regular function is determined at <strong>runtime</strong> by its <strong>invocation context</strong>. It can change depending on how the function is called.</p><ul><li><strong>Method Call:</strong> When a function is called as a method of an object (e.g., obj.myMethod()), this refers to the <strong>object itself</strong>.</li><li><strong>Simple Function Call:</strong> In a standard function call (e.g., myFunction()), this refers to the <strong>global object</strong> in non-strict mode (like window in browsers) and is undefined in strict mode.</li><li><strong>Constructor Call:</strong> When a function is called with new (e.g., new MyObject()), this refers to the <strong>newly created instance</strong>.</li><li><strong>Event Handlers:</strong> In the context of a DOM event listener, this typically refers to the <strong>element that the event listener was attached to</strong>.</li></ul><p><strong>Example:</strong></p><pre>const person = {<br>  name: &#39;Alice&#39;,<br>  greet: function() {<br>    console.log(this.name); // &#39;this&#39; refers to the &#39;person&#39; object<br>  }<br>};<br>person.greet(); // Output: Alice<br><br>const greetFunc = person.greet;<br>greetFunc(); // Output: &#39;&#39; (or &#39;undefined&#39; in strict mode)</pre><p>In the second call, the function is detached from the person object, and its this context defaults to the global object.</p><p><strong>Arrow Functions</strong></p><p>Arrow functions handle this differently. They do <strong>not have their own </strong><strong>this binding</strong>. Instead, they <strong>lexically inherit</strong> the this value from the enclosing scope at the time they are defined. This behavior is fixed and cannot be changed by call, apply, or bind.</p><ul><li><strong>In a Global Scope:</strong> this is the global object.</li><li><strong>Inside a Regular Function:</strong> this inherits the this of the regular function.</li><li><strong>Inside a Method:</strong> this inherits the this of the enclosing object, which is the object the method is defined on.</li></ul><p>This makes them predictable and very useful for callbacks, especially in event handlers, where a regular function’s this might not be what you expect.</p><p><strong>Example:</strong></p><pre>const person = {<br>  name: &#39;Bob&#39;,<br>  greet: function() {<br>    const innerArrowFunc = () =&gt; {<br>      console.log(this.name); // &#39;this&#39; inherits from the outer &#39;greet&#39; function<br>    };<br>    innerArrowFunc();<br>  },<br>  arrowGreet: () =&gt; {<br>    console.log(this.name); // &#39;this&#39; inherits from the global scope (e.g., &#39;window&#39;)<br>  }<br>};<br><br>person.greet();       // Output: Bob<br>person.arrowGreet();  // Output: &#39;&#39; (or &#39;undefined&#39;)</pre><p><strong>Event Handlers</strong></p><p>This distinction is especially important in event handling.</p><ul><li><strong>Regular function handler:</strong> this will refer to the <strong>DOM element</strong> that triggered the event. This is useful if you need to manipulate the element directly.</li><li><strong>Arrow function handler:</strong> this will refer to the <strong>context where the handler was defined</strong>, which is often the component instance in frameworks like React or Vue, making it a common pattern.</li></ul><pre>const button = document.querySelector(&#39;#myButton&#39;);<br><br>// Regular function<br>button.addEventListener(&#39;click&#39;, function() {<br>  console.log(this); // &#39;this&#39; is the button element<br>});<br><br>// Arrow function<br>button.addEventListener(&#39;click&#39;, (event) =&gt; {<br>  // In a component class, &#39;this&#39; would refer to the class instance<br>  // Here, it would be the global object (window)<br>  console.log(this);<br>});</pre><h4>10 What happens during the execution of this code, and what are the potential race conditions?</h4><pre>Promise.resolve().then(() =&gt; console.log(&#39;A&#39;));<br>setTimeout(() =&gt; console.log(&#39;B&#39;), 0);<br>console.log(&#39;C&#39;);</pre><p>During the execution of this code, the output will be <strong>C, </strong><strong>A, </strong><strong>B</strong>. This is a classic example that demonstrates the difference between the <strong>microtask queue</strong> (used by promises) and the <strong>macrotask queue</strong> (used by setTimeout).</p><p><strong>Execution Breakdown</strong></p><ol><li><strong>Synchronous Code:</strong> The JavaScript engine first executes all synchronous code. console.log(&#39;C&#39;) is called immediately.</li><li><strong>Promise Microtask:</strong> Promise.resolve().then(...) places its callback (() =&gt; console.log(&#39;A&#39;)) into the <strong>microtask queue</strong>. The microtask queue has higher priority than the macrotask queue and is processed as soon as the current call stack is empty.</li><li><strong>setTimeout Macrotask:</strong> setTimeout(() =&gt; console.log(&#39;B&#39;), 0) places its callback (() =&gt; console.log(&#39;B&#39;)) into the <strong>macrotask queue</strong>. Macrotasks are processed by the event loop only after the microtask queue is empty.</li><li><strong>Event Loop:</strong> Once the synchronous code is finished, the event loop checks the microtask queue. It finds the callback for console.log(&#39;A&#39;), moves it to the call stack, and executes it.</li><li><strong>Final Execution:</strong> After the microtask queue is empty, the event loop then checks the macrotask queue. It finds the callback for console.log(&#39;B&#39;), moves it to the call stack, and executes it.</li></ol><p>This priority system ensures that all promises are resolved before the next event loop cycle begins.</p><p><strong>Potential Race Conditions</strong></p><p>Race conditions can occur when multiple asynchronous tasks are initiated, and their execution order is not guaranteed, leading to unpredictable outcomes. In this specific code, the setTimeout callback has a defined minimum delay of 0, but its actual execution time depends on several factors:</p><ul><li><strong>Task Queue Congestion:</strong> If there are other tasks (e.g., from other setTimeout calls, user events, or network requests) already in the macrotask queue, console.log(&#39;B&#39;) will have to wait for them to finish.</li><li><strong>Microtask Overload:</strong> If the microtask queue has many Promise or queueMicrotask callbacks, the setTimeout callback will be delayed until all microtasks are processed, making its execution time highly variable.</li></ul><p>These race conditions highlight that setTimeout(..., 0) is not a zero-delay operation but rather a scheduling mechanism that yields control back to the event loop, with the exact timing depending on the engine&#39;s current workload.</p><p>Thanks for reading!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=15df79bfa078" width="1" height="1" alt=""><hr><p><a href="https://medium.com/deno-the-complete-reference/10-javascript-interview-questions-part-1-15df79bfa078">10 Javascript Interview Questions — part 1</a> was originally published in <a href="https://medium.com/deno-the-complete-reference">Tech Tonic</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Advanced SQL Features Not Fully Supported by Node.js ORMs]]></title>
            <link>https://medium.com/deno-the-complete-reference/advanced-sql-features-not-fully-supported-by-node-js-orms-7e4ae4e380c8?source=rss----cca06f8d4889---4</link>
            <guid isPermaLink="false">https://medium.com/p/7e4ae4e380c8</guid>
            <category><![CDATA[nodejs]]></category>
            <dc:creator><![CDATA[Mayank C]]></dc:creator>
            <pubDate>Mon, 21 Jul 2025 20:54:04 GMT</pubDate>
            <atom:updated>2025-07-21T20:54:04.764Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*muPn3n4QR7mpfubjEVhQ6w.png" /></figure><p>Object-Relational Mapping (ORM) libraries have become a foundational component in modern Node.js application development. Frameworks such as Sequelize, Prisma, and TypeORM offer abstractions that facilitate rapid development by modeling relational data as high-level JavaScript or TypeScript objects. These abstractions enable developers to interact with databases using familiar object-oriented paradigms, thereby reducing the need to write raw SQL and lowering the barrier to entry for full-stack developers.</p><p>Despite these productivity benefits, ORMs often fail to expose the full expressive power of Structured Query Language (SQL), particularly in the context of advanced database features. These include but are not limited to window functions, recursive common table expressions (CTEs), materialized views, and expression-based indexing. Such features are essential in domains requiring complex data transformations, hierarchical processing, or performance-critical analytics.</p><p>This article examines the limitations of popular Node.js ORMs in supporting advanced SQL features.</p><p>Let’s get started.</p><h3>Common Limitations in SQL Feature Coverage</h3><p>While ORM frameworks in Node.js abstract away much of the complexity involved in database access, they frequently lack support for a range of advanced SQL features. These limitations constrain the expressiveness, performance optimization, and structural modeling capabilities that relational databases inherently provide. This section outlines several such SQL constructs and the degree to which they are unsupported or inefficiently modeled by ORMs.</p><h4>A. Window Functions</h4><p>Window functions allow for complex analytics operations over rows related to the current row, using clauses such as OVER, PARTITION BY, and ORDER BY. They are commonly used for ranking, running totals, and lag/lead computations.</p><p><strong><em>Raw SQL Example:</em></strong></p><pre>SELECT <br>    user_id, <br>    created_at,<br>    RANK() OVER (PARTITION BY user_id ORDER BY created_at) AS rank<br>FROM logins;</pre><p><strong><em>ORM Limitation:</em></strong><br>Prisma and Sequelize do not support window functions through their query builders. Developers must resort to raw SQL, which bypasses the ORM’s type safety and abstraction benefits.</p><p><em>Workaround Example (Sequelize):</em></p><pre>await sequelize.query(`<br>  SELECT user_id, created_at,<br>  RANK() OVER (PARTITION BY user_id ORDER BY created_at) AS rank<br>  FROM logins;<br>`);</pre><h4>B. Recursive Common Table Expressions (CTEs)</h4><p>Recursive CTEs are essential for querying hierarchical or graph-structured data, such as organizational charts or category trees.</p><p><strong><em>Raw SQL Example:</em></strong></p><pre>WITH RECURSIVE category_path(id, name, parent_id) AS (<br>  SELECT id, name, parent_id FROM categories WHERE parent_id IS NULL<br>  UNION ALL<br>  SELECT c.id, c.name, c.parent_id<br>  FROM categories c<br>  INNER JOIN category_path cp ON cp.id = c.parent_id<br>)<br>SELECT * FROM category_path;</pre><p><strong><em>ORM Limitation:</em></strong><br>None of the major ORMs in Node.js support recursive CTEs natively. Developers are again forced to write raw SQL, undermining the abstraction layer and increasing maintenance complexity.</p><h4>C. Materialized Views and View Binding</h4><p>Materialized views are precomputed query results stored physically for improved read performance. Similarly, standard views abstract complex queries into logical tables.</p><p><strong><em>ORM Limitation:</em></strong><br>ORMs do not support view creation or materialized view refreshing. Moreover, interacting with existing views (e.g., read-only bindings) is inconsistently supported and often undocumented.</p><p><strong><em>Example Issue:</em></strong><br>Prisma currently does not support introspection or migration for materialized views. Sequelize requires defining a model manually, without schema validation.</p><h4>D. Partial and Expression Indexes</h4><p>Partial indexes restrict indexing to rows that meet a condition, while expression indexes index derived values. These features are vital for performance optimization in large datasets.</p><p><strong><em>Raw SQL Example:</em></strong></p><pre>CREATE INDEX idx_active_users ON users (email) WHERE is_active = true;</pre><p><strong><em>ORM Limitation:</em></strong><br>There is no declarative support for partial indexes or functional expressions in most Node.js ORMs. Even where custom indexing is allowed (e.g., Sequelize’s indexes property), the syntax does not cover expressions or conditions.</p><h4>E. Native JSON and JSONB Operations</h4><p>PostgreSQL’s JSONB support enables deep querying and indexing of semi-structured data using operators such as @&gt;, #&gt;&gt;, and jsonb_path_query.</p><p><strong><em>Raw SQL Example:</em></strong></p><pre>SELECT * FROM events WHERE payload @&gt; &#39;{&quot;type&quot;: &quot;click&quot;}&#39;;</pre><p><strong><em>ORM Limitation:</em></strong><br>Prisma and Sequelize allow storage of JSON but lack full support for these advanced operators in query builders. As a result, developers must revert to raw SQL or use database-specific extensions.</p><h4>F. Table Inheritance and Advanced Constraints</h4><p>Features like table inheritance (PostgreSQL) and domain-specific constraints enhance schema design and reuse.</p><p><strong><em>ORM Limitation:</em></strong><br>Most Node.js ORMs do not support inheritance hierarchies at the database level. Instead, developers must simulate these relationships using manual table joins or polymorphic associations, which are more error-prone and less performant.</p><h3>Practical Implications of Feature Limitations</h3><p>The absence of advanced SQL feature support in Node.js ORMs has direct consequences for the development, performance, and scalability of data-intensive applications. This section outlines the tangible effects that such limitations impose on real-world systems.</p><h4>A. Increased Complexity Due to Raw SQL Injection</h4><p>In scenarios where ORMs do not support necessary SQL features, developers are forced to bypass ORM abstractions and write raw SQL queries. This undermines the benefits of abstraction, introduces risks such as SQL injection (when parameterization is mishandled), and complicates code maintainability.</p><p><strong><em>Example:</em></strong></p><p>A recursive CTE implemented via raw query:</p><pre>await prisma.$queryRaw`<br>  WITH RECURSIVE cte AS (<br>    SELECT id, parent_id FROM categories WHERE parent_id IS NULL<br>    UNION ALL<br>    SELECT c.id, c.parent_id FROM categories c<br>    JOIN cte ON c.parent_id = cte.id<br>  ) SELECT * FROM cte;<br>`;</pre><p>This approach lacks type safety, autocompletion, and the composability typically provided by ORM layers.</p><h4>B. Degraded Query Performance</h4><p>Inability to define partial indexes, expression indexes, or leverage materialized views through ORMs restricts the ability to optimize query execution. Applications with large datasets may suffer from poor response times and high resource consumption.</p><p>For example, a system that filters millions of rows by is_active = true cannot benefit from partial indexing if the ORM does not support it, resulting in full table scans.</p><h4>C. Reduced Portability and Migration Difficulty</h4><p>When advanced SQL features are used via raw queries, portability across database systems is reduced. Developers must account for dialect-specific syntax and execution semantics, defeating ORM-level database abstraction.</p><p>Additionally, migration tools bundled with ORMs (e.g., Prisma Migrate, Sequelize CLI) do not handle views, CTEs, or expression indexes, leading to fragmented schema definitions across code and migration files.</p><h4>D. Inconsistent Data Modeling</h4><p>The lack of support for inheritance, views, and constraints forces developers to design workarounds at the application layer. For example, emulating polymorphic associations or enforcing constraints through validation logic increases the burden on application code and can lead to data integrity issues.</p><h4>E. Tooling Limitations</h4><p>ORM-based code editors, linters, and type systems cannot infer structure from raw SQL, reducing developer productivity and increasing the potential for runtime errors. Automated tests may also miss edge cases if not designed with raw query paths in mind.</p><p>In summary, the absence of advanced SQL support in ORMs translates to increased complexity, performance trade-offs, and fragmented application logic. These consequences highlight the need to critically evaluate the use of ORMs in systems with complex data processing requirements.</p><p>Thanks for reading!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7e4ae4e380c8" width="1" height="1" alt=""><hr><p><a href="https://medium.com/deno-the-complete-reference/advanced-sql-features-not-fully-supported-by-node-js-orms-7e4ae4e380c8">Advanced SQL Features Not Fully Supported by Node.js ORMs</a> was originally published in <a href="https://medium.com/deno-the-complete-reference">Tech Tonic</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[When Not to Use ORM: Performance and Flexibility Considerations in Node.js Applications]]></title>
            <link>https://medium.com/deno-the-complete-reference/when-not-to-use-orm-performance-and-flexibility-considerations-in-node-js-applications-4d76b2e7211b?source=rss----cca06f8d4889---4</link>
            <guid isPermaLink="false">https://medium.com/p/4d76b2e7211b</guid>
            <category><![CDATA[nodejs]]></category>
            <dc:creator><![CDATA[Mayank C]]></dc:creator>
            <pubDate>Mon, 21 Jul 2025 18:56:51 GMT</pubDate>
            <atom:updated>2025-07-21T18:56:51.300Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*C3uF-fu9tTuQ4YoFD4FPOg.png" /></figure><p>ORM frameworks have gained widespread adoption in the Node.js ecosystem due to their ability to simplify database interactions and improve developer productivity. By abstracting the underlying relational database operations into high-level, object-oriented interfaces, ORMs reduce the complexity of managing persistent data and enforce consistency across application layers. The benefits of ORM usage, including maintainability, security, and cross-database compatibility, are well documented.</p><p>However, despite these advantages, ORMs are not universally optimal for all scenarios. Certain applications require high-performance data processing, complex query capabilities, or fine-grained control over transactional behavior that exceed the abstraction capabilities of typical ORM tools. Additionally, ORM usage may introduce operational challenges such as schema migration difficulties and increased runtime overhead, particularly in serverless or microservice architectures.</p><p>This article critically examines circumstances in which direct database access or alternative approaches may be preferable to ORM frameworks within Node.js applications. Let’s get started.</p><h3>1. Performance Overhead in High-Volume Transactions</h3><p>ORM frameworks introduce an additional abstraction layer between application logic and the database engine, which inherently incurs some performance overhead. This overhead comes primarily in the form of metadata processing, object instantiation, and query construction within the ORM’s internal mechanisms. While these operations are often negligible in small to medium workloads, they can significantly impact performance in scenarios involving high-volume transactions or bulk data processing.</p><p>In high-throughput applications requiring rapid insertion, updating, or retrieval of large datasets, the extra CPU cycles and memory consumption associated with ORM abstractions can lead to increased latency and reduced throughput. Moreover, ORM-generated queries, while syntactically correct, are not always optimized for performance; they may contain superfluous joins, nested selects, or unneeded data retrieval that degrade execution efficiency.</p><p>Some public benchmarks have demonstrated that raw SQL queries executed via native drivers consistently outperform ORM-generated queries in bulk operations. For example, bulk insertions implemented with direct parameterized SQL statements exhibit lower latency and higher throughput compared to ORM batch operations that internally map each record to an object instance before insertion.</p><p>The following code shows the difference:</p><p><strong><em>Raw SQL Bulk Insert Using </em></strong><strong><em>pg Library:</em></strong></p><pre>async function bulkInsertUsers(users) {<br>  // Construct bulk insert query using parameterized values<br>  const values = users.map((user, i) =&gt; `($${i * 2 + 1}, $${i * 2 + 2})`).join(&#39;, &#39;);<br>  const query = `INSERT INTO users (name, email) VALUES ${values}`;<br>  const params = users.flatMap(user =&gt; [user.name, user.email]);<br>  await pool.query(query, params);<br>}</pre><p><strong><em>ORM Bulk Insert Using Sequelize:</em></strong></p><pre>async function bulkInsertUsers(users) {<br>  await User.bulkCreate(users); // Each user mapped to model instance internally<br>}</pre><h3>2. Limited SQL Expression and Complex Querying</h3><p>In the raw SQL version, the query is constructed explicitly and executed directly, minimizing overhead. In contrast, the ORM version requires Sequelize to instantiate model objects for each record, validate fields, and generate individual insert queries or batch statements, which introduces computational overhead and memory usage.</p><p>While ORM frameworks simplify the implementation of common database operations, they impose limitations when expressing complex or highly optimized SQL queries. ORMs typically provide abstractions that cover standard CRUD operations and basic filtering or sorting logic. However, expressing advanced SQL constructs — such as Common Table Expressions (CTEs), window functions, recursive queries, or database-specific optimization hints — may either be unsupported or result in verbose, unreadable syntax.</p><p>This limitation becomes more visible in applications requiring analytical queries, multi-level aggregations, or fine-grained control over execution plans. In such cases, the ORM’s high-level interface may obscure critical database semantics or fail to expose advanced query capabilities available in the underlying relational engine.</p><p>Developers may attempt to work around these constraints using ORM-specific query builders or raw query escape hatches, but these solutions often reduce the benefits of using an ORM in the first place. Furthermore, reliance on undocumented or inconsistent query-generation behavior may lead to fragile implementations that break across versions or database dialects.</p><p>The following example illustrates the difference:</p><p><strong><em>Raw SQL Using </em></strong><strong><em>pg:</em></strong></p><pre>async function getTopUsersByPostCount() {<br>  const query = `<br>    SELECT id, name, post_count FROM (<br>      SELECT users.id, users.name, COUNT(posts.id) AS post_count,<br>             RANK() OVER (ORDER BY COUNT(posts.id) DESC) AS rank<br>      FROM users<br>      LEFT JOIN posts ON users.id = posts.user_id<br>      GROUP BY users.id<br>    ) ranked_users<br>    WHERE rank &lt;= 3<br>  `;<br>  const result = await pool.query(query);<br>  return result.rows;<br>}</pre><p><strong><em>ORM Attempt Using Sequelize:</em></strong></p><pre>// Sequelize does not natively support window functions in a composable way.<br>// A raw query is required for such operations.<br>async function getTopUsersByPostCount() {<br>  const [results] = await sequelize.query(`<br>    SELECT id, name, post_count FROM (<br>      SELECT users.id, users.name, COUNT(posts.id) AS post_count,<br>             RANK() OVER (ORDER BY COUNT(posts.id) DESC) AS rank<br>      FROM users<br>      LEFT JOIN posts ON users.id = posts.user_id<br>      GROUP BY users.id<br>    ) ranked_users<br>    WHERE rank &lt;= 3<br>  `);<br>  return results;<br>}</pre><p>In this case, the use of raw SQL is mandatory even when using an ORM, as the abstraction layer does not expose mechanisms to define window functions or derived tables in a structured, type-safe manner. The need to bypass the ORM undermines its core advantages and introduces inconsistencies in code structure.</p><h3>3. Lack of Fine-Grained Transaction and Locking Control</h3><p>Transactions are a foundational mechanism in relational databases for ensuring atomicity, consistency, isolation, and durability (ACID). In advanced application scenarios — particularly those involving concurrent updates, conditional writes, or resource locking — fine-grained control over transaction semantics is essential. While ORM frameworks provide basic support for transactional operations, they often abstract away or limit access to critical features such as isolation levels, advisory locks, savepoints, and conflict resolution mechanisms.</p><p>ORMs typically expose transactions through high-level wrappers or callback-based execution contexts, which are sufficient for simple commit/rollback workflows. However, this abstraction becomes restrictive in use cases requiring precise control over the transaction lifecycle, such as retryable writes, multi-phase commit protocols, or deadlock avoidance strategies. Furthermore, integration with database-specific locking primitives (e.g., SELECT ... FOR UPDATE, LOCK TABLE, or advisory locking functions) is frequently incomplete or unsupported.</p><p>The following example illustrates the difference:</p><p><strong><em>Raw SQL with Advisory Locking and Isolation Level:</em></strong></p><pre>async function updateBalanceWithLock(userId, amount) {<br>  const client = await pool.connect();<br>  try {<br>    await client.query(&#39;BEGIN ISOLATION LEVEL SERIALIZABLE&#39;);<br><br>    // Acquire advisory lock<br>    await client.query(&#39;SELECT pg_advisory_xact_lock($1)&#39;, [userId]);<br><br>    const result = await client.query(<br>      &#39;SELECT balance FROM accounts WHERE user_id = $1 FOR UPDATE&#39;,<br>      [userId]<br>    );<br>    const currentBalance = result.rows[0].balance;<br>    const newBalance = currentBalance + amount;<br><br>    await client.query(<br>      &#39;UPDATE accounts SET balance = $1 WHERE user_id = $2&#39;,<br>      [newBalance, userId]<br>    );<br><br>    await client.query(&#39;COMMIT&#39;);<br>  } catch (err) {<br>    await client.query(&#39;ROLLBACK&#39;);<br>    throw err;<br>  } finally {<br>    client.release();<br>  }<br>}</pre><p><strong><em>ORM (Sequelize) Equivalent — Limited Capability:</em></strong></p><pre>async function updateBalance(userId, amount) {<br>  const t = await sequelize.transaction({ isolationLevel: &#39;SERIALIZABLE&#39; });<br>  try {<br>    const account = await Account.findOne({ where: { userId }, transaction: t, lock: t.LOCK.UPDATE });<br>    account.balance += amount;<br>    await account.save({ transaction: t });<br>    await t.commit();<br>  } catch (err) {<br>    await t.rollback();<br>    throw err;<br>  }<br>}</pre><p>While the ORM version supports transaction boundaries and row-level locking, it does not support advisory locking or custom transaction logic without falling back to raw SQL. Moreover, handling database-specific exceptions or implementing retry logic in the context of ORM-managed transactions is non-trivial due to the lack of low-level control over the execution flow.</p><h3>4. Schema Management and Migration Friction</h3><p>In relational databases, schema evolution is a critical aspect of long-term application maintenance. It consists of the creation, alteration, and deprecation of tables, columns, constraints, indexes, and relationships. While ORM frameworks often integrate schema migration tooling, their abstractions introduce friction in maintaining alignment between application models and actual database schemas, especially in large-scale or heterogeneous environments.</p><p>ORM-based migration tools typically generate schema change scripts based on differences detected in model definitions. However, this process is inherently fragile and may yield inaccurate or non-deterministic results in complex schema scenarios, such as composite keys, partial indexes, or conditional constraints. Furthermore, automated diffing mechanisms can lead to silent regressions when coupled with implicit conventions or misconfigured data types.</p><p>Direct database access combined with manually curated migration scripts offers greater reliability and auditability. Hand-written migrations allow teams to explicitly define and review all schema changes, enforce consistent naming conventions, and integrate database-specific features. They also improve compatibility across environments where schema versions may diverge due to race conditions, partial deployments, or tooling inconsistencies.</p><p>The following example demonstrates a schema migration using direct SQL with the node-pg-migrate tool:</p><p><strong><em>Manual Migration Script (PostgreSQL, using </em></strong><strong><em>node-pg-migrate):</em></strong></p><pre>exports.up = (pgm) =&gt; {<br>  pgm.createTable(&#39;users&#39;, {<br>    id: { type: &#39;serial&#39;, primaryKey: true },<br>    email: { type: &#39;varchar(255)&#39;, notNull: true, unique: true },<br>    created_at: { type: &#39;timestamp&#39;, notNull: true, default: pgm.func(&#39;now()&#39;) },<br>  });<br><br>  pgm.addIndex(&#39;users&#39;, &#39;email&#39;, { unique: true });<br>};<br><br>exports.down = (pgm) =&gt; {<br>  pgm.dropTable(&#39;users&#39;);<br>};</pre><p><strong><em>ORM Schema Definition and Migration (Sequelize):</em></strong></p><pre>// Model definition<br>const User = sequelize.define(&#39;User&#39;, {<br>  email: {<br>    type: DataTypes.STRING,<br>    allowNull: false,<br>    unique: true,<br>  },<br>  createdAt: {<br>    type: DataTypes.DATE,<br>    allowNull: false,<br>    defaultValue: Sequelize.NOW,<br>  },<br>});<br><br>// Migration generated via CLI (simplified)<br>module.exports = {<br>  up: async (queryInterface, Sequelize) =&gt; {<br>    await queryInterface.createTable(&#39;Users&#39;, {<br>      id: { type: Sequelize.INTEGER, autoIncrement: true, primaryKey: true },<br>      email: { type: Sequelize.STRING, allowNull: false, unique: true },<br>      createdAt: { type: Sequelize.DATE, allowNull: false, defaultValue: Sequelize.NOW },<br>    });<br>  },<br>  down: async (queryInterface) =&gt; {<br>    await queryInterface.dropTable(&#39;Users&#39;);<br>  },<br>};</pre><p>Although ORM migrations provide convenience, they tightly couple schema changes to the ORM’s internal representations, leading to versioning issues and incompatibilities in projects that demand precision. Additionally, ORM-generated migrations often lack support for full-text indexing, geospatial columns, or database triggers — requiring supplemental raw SQL injections and breaking consistency.</p><h3>5. Debugging, Logging, and Error Transparency</h3><p>Effective debugging and monitoring are critical requirements in production-grade applications. When interacting with relational databases, clear visibility into executed queries, associated parameters, execution plans, and error propagation paths is essential for diagnosing faults, identifying performance bottlenecks, and ensuring query correctness. ORM frameworks, by virtue of abstraction, often obfuscate this visibility, hindering fine-grained introspection.</p><p>ORMs typically encapsulate query generation and execution, which results in loss of direct access to the actual SQL statements executed against the database. While some frameworks provide limited query logging, the logs may omit parameter values, normalize whitespace, or truncate large queries, thereby impeding root cause analysis. Additionally, ORM-level exceptions are frequently generalized or wrapped in custom error classes, which obscure the original database error codes and messages necessary for accurate remediation.</p><p>In contrast, applications using raw SQL via native drivers offer complete transparency and granular logging. Developers retain full control over query formatting, parameter binding, and error handling, thereby enabling precise instrumentation and integration with observability tooling.</p><p><strong><em>Example: Raw SQL with Detailed Logging Using </em></strong><strong><em>pg Library</em></strong></p><pre>async function getUserByEmail(email) {<br>  const query = &#39;SELECT * FROM users WHERE email = $1&#39;;<br>  console.log(&#39;Executing Query:&#39;, query, &#39;Parameters:&#39;, [email]);<br><br>  try {<br>    const res = await pool.query(query, [email]);<br>    return res.rows[0];<br>  } catch (err) {<br>    console.error(&#39;Database Error:&#39;, err.code, err.message);<br>    throw err;<br>  }<br>}</pre><p><strong><em>ORM Equivalent (Sequelize) with Limited Logging:</em></strong></p><pre>const user = await User.findOne({ where: { email } });<br>// Underlying query is not directly visible unless logging is enabled</pre><p><strong><em>Enabling Sequelize Logging (with Limitations):</em></strong></p><pre>const sequelize = new Sequelize(DB_URI, {<br>  logging: (msg) =&gt; console.log(&#39;ORM Query:&#39;, msg),<br>});</pre><p>Even with explicit logging enabled, the Sequelize ORM logs only the final SQL string, typically without bound parameter values or context-specific metadata. This limits traceability in multi-user or multi-tenant applications, where precise parameter matching is necessary for security and audit purposes.</p><p>Furthermore, the error-handling model in most ORMs abstracts vendor-specific error codes (e.g., PostgreSQL’s 23505 for unique constraint violations) into high-level error types. This generalization complicates the implementation of fine-grained retry policies or conditional logic based on error semantics.</p><p>Thanks for reading!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4d76b2e7211b" width="1" height="1" alt=""><hr><p><a href="https://medium.com/deno-the-complete-reference/when-not-to-use-orm-performance-and-flexibility-considerations-in-node-js-applications-4d76b2e7211b">When Not to Use ORM: Performance and Flexibility Considerations in Node.js Applications</a> was originally published in <a href="https://medium.com/deno-the-complete-reference">Tech Tonic</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[5 Key Advantages of Using ORM over Direct Database Access in Node.js Applications]]></title>
            <link>https://medium.com/deno-the-complete-reference/5-key-advantages-of-using-orm-over-direct-database-access-in-node-js-applications-c09007807996?source=rss----cca06f8d4889---4</link>
            <guid isPermaLink="false">https://medium.com/p/c09007807996</guid>
            <category><![CDATA[nodejs]]></category>
            <dc:creator><![CDATA[Mayank C]]></dc:creator>
            <pubDate>Sun, 20 Jul 2025 19:55:32 GMT</pubDate>
            <atom:updated>2025-07-20T19:55:32.841Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*80SXQjLCUSdzOzyBOaQf5w.png" /></figure><p>Node.js has become a prominent runtime environment for developing scalable and efficient server-side applications, particularly due to its event-driven, non-blocking I/O model. A critical component of most Node.js applications is database interaction, which can be implemented either through direct database access using raw SQL queries or by using Object-Relational Mapping (ORM) tools. Direct database access provides fine-grained control over queries and operations, but it often leads to verbose code, higher complexity, and increased risk of errors.</p><p>Object-Relational Mapping (ORM) is a programming technique that facilitates the conversion of data between incompatible systems using object-oriented paradigms. In the world of Node.js, ORM tools such as Sequelize, TypeORM, and Prisma provide abstractions over relational databases, enabling developers to interact with data using objects and class definitions rather than writing raw SQL queries. This abstraction simplifies many aspects of database management and aligns with modern software engineering practices.</p><p>This article presents five critical advantages of using ORM over direct database access in Node.js applications. Let’s get started.</p><h3>1. Code Abstraction and Maintainability</h3><p>One of the principal advantages of using an ORM in Node.js applications is the abstraction it provides over raw SQL queries. In traditional database access patterns, developers are required to manually construct SQL statements and handle query execution. This leads to repetition, increased verbosity, and tightly coupled logic, all of which negatively affect maintainability.</p><p>ORMs encapsulate query logic within object-oriented interfaces and reusable models. These abstractions allow developers to define data schemas using high-level constructs, which enhances readability and reduces the cognitive load associated with interpreting raw queries. As applications scale, this abstraction becomes increasingly beneficial for maintaining large codebases and enforcing consistency across database operations.</p><p>The following example illustrates the distinction:</p><p><strong><em>Raw SQL (Using </em></strong><strong><em>pg library):</em></strong></p><pre>// db.js<br>const { Pool } = require(&#39;pg&#39;);<br>const pool = new Pool();<br><br>async function getUserByEmail(email) {<br>  const query = &#39;SELECT id, name, email FROM users WHERE email = $1&#39;;<br>  const values = [email];<br>  const result = await pool.query(query, values);<br>  return result.rows[0];<br>}</pre><p><strong><em>Using ORM (Sequelize):</em></strong></p><pre>// models/User.js<br>const { Sequelize, DataTypes } = require(&#39;sequelize&#39;);<br>const sequelize = new Sequelize(/* config */);<br><br>const User = sequelize.define(&#39;User&#39;, {<br>  name: DataTypes.STRING,<br>  email: DataTypes.STRING<br>});<br><br>// elsewhere in application logic<br>async function getUserByEmail(email) {<br>  return await User.findOne({ where: { email } });<br>}</pre><p>In the ORM version, the model definition and retrieval logic are separated, promoting modularity and reuse. The SQL query is abstracted away, and the intent is clearly expressed through high-level syntax.</p><h3>2. Improved Productivity and Developer Experience</h3><p>ORM frameworks significantly enhance developer productivity by reducing the amount of boilerplate code required to perform routine database operations. ORM tools offer high-level APIs, built-in schema synchronization, and automatic query generation, which collectively streamline development workflows. This improvement in efficiency is particularly relevant in agile environments where rapid iteration and frequent schema changes are common.</p><p>Furthermore, modern ORMs integrate with popular IDEs to provide features such as auto-completion, type checking, and inline documentation. These capabilities reduce context-switching and minimize syntactic or logical errors during development. By abstracting repetitive tasks, ORMs allow developers to focus on business logic rather than database syntax, thereby improving overall velocity and code quality.</p><p>The difference in productivity can be seen in the following example:</p><p><strong><em>Raw SQL (Using </em></strong><strong><em>pg library):</em></strong></p><pre>async function createUser(name, email) {<br>  const query = &#39;INSERT INTO users (name, email) VALUES ($1, $2) RETURNING id&#39;;<br>  const values = [name, email];<br>  const result = await pool.query(query, values);<br>  return result.rows[0].id;<br>}</pre><p><strong><em>Using ORM (Sequelize):</em></strong></p><pre>async function createUser(name, email) {<br>  const newUser = await User.create({ name, email });<br>  return newUser.id;<br>}</pre><p>In the ORM version, the insertion logic is expressed in a single, declarative line without the need to construct query strings or manually handle value interpolation. Additionally, the ORM internally handles data type mapping, escaping, and schema validations, which would otherwise require explicit implementation in raw SQL.</p><h3>3. Cross-Database Compatibility</h3><p>ORM frameworks are designed to provide a unified interface for interacting with various relational database management systems (RDBMS). This abstraction enables applications to remain largely agnostic to the underlying database engine, thus simplifying database migrations and reducing vendor lock-in. In contrast, applications that rely on raw SQL are tightly coupled to the specific syntax, functions, and capabilities of the chosen RDBMS, leading to increased maintenance overhead when switching database systems.</p><p>ORMs achieve cross-database compatibility by internally translating high-level operations into dialect-specific SQL queries. Most modern ORMs support multiple dialects (e.g., PostgreSQL, MySQL, SQLite, and Microsoft SQL Server) through configurable adapters or dialect modules. This capability allows teams to develop and test applications against one database engine and deploy them to another with minimal changes to application logic.</p><p>The following example demonstrates this critical advantage:</p><p><strong><em>Using ORM (Sequelize):</em></strong></p><pre>// config/sequelize.js<br>const { Sequelize } = require(&#39;sequelize&#39;);<br><br>const sequelize = new Sequelize(&#39;database_name&#39;, &#39;username&#39;, &#39;password&#39;, {<br>  host: &#39;localhost&#39;,<br>  dialect: &#39;postgres&#39; // Can be changed to &#39;mysql&#39;, &#39;sqlite&#39;, etc.<br>});<br><br>module.exports = sequelize;</pre><p><strong><em>Model and Query (Dialect-Independent):</em></strong></p><pre>// models/User.js<br>const { DataTypes } = require(&#39;sequelize&#39;);<br>const sequelize = require(&#39;../config/sequelize&#39;);<br><br>const User = sequelize.define(&#39;User&#39;, {<br>  name: DataTypes.STRING,<br>  email: DataTypes.STRING<br>});<br><br>// data access<br>async function getAllUsers() {<br>  return await User.findAll();<br>}</pre><p>In this example, the application logic remains unchanged regardless of whether the underlying database is PostgreSQL, MySQL, or SQLite. Only the dialect parameter in the configuration needs to be updated. This level of decoupling is not feasible when using raw SQL, where even basic syntax differences (e.g., LIMIT vs. TOP) require conditional logic or entirely separate query definitions.</p><h3>4. Built-in Security Features</h3><p>Security is a fundamental requirement in application development, particularly when dealing with persistent data storage. One of the inherent advantages of ORM frameworks is the inclusion of built-in security mechanisms that mitigate common vulnerabilities associated with direct SQL access. The most notable among these is protection against SQL injection, a critical threat in applications that construct raw queries using dynamic user input.</p><p>ORMs address this issue by using parameterized queries and escaping mechanisms automatically during query construction. These protections are enforced consistently across all query methods, eliminating the need for manual sanitization or query validation logic. In contrast, direct database access requires developers to manually ensure that all input is properly escaped and parameterized, increasing the risk of oversight and human error.</p><p>The following example illustrates the difference in handling user input.</p><p><strong><em>Raw SQL (Unsafe Example Using </em></strong><strong><em>pg):</em></strong></p><pre>async function findUserUnsafe(email) {<br>  const query = `SELECT * FROM users WHERE email = &#39;${email}&#39;`; // vulnerable to SQL injection<br>  const result = await pool.query(query);<br>  return result.rows[0];<br>}</pre><p><strong><em>Raw SQL (Safe Parameterized Version):</em></strong></p><pre>async function findUserSafe(email) {<br>  const query = &#39;SELECT * FROM users WHERE email = $1&#39;;<br>  const result = await pool.query(query, [email]);<br>  return result.rows[0];<br>}</pre><p><strong><em>ORM Version (Sequelize):</em></strong></p><pre>async function findUser(email) {<br>  return await User.findOne({ where: { email } }); // parameterization handled internally<br>}</pre><p>In the ORM example, parameterization is implicit. The framework constructs the appropriate SQL query using dialect-specific escaping and binding methods, thereby ensuring that user-provided values are never directly embedded into raw query strings. This eliminates the possibility of injection attacks under standard usage patterns.</p><p>In addition to query-level protections, many ORM tools also support features such as model-level validation, input sanitization, and role-based access control when integrated with application-level authorization mechanisms.</p><h3>5. Easier Data Relationships and Modeling</h3><p>Relational databases are fundamentally designed around the concept of data normalization, where complex entities are distributed across multiple related tables. Managing these relationships — such as one-to-one, one-to-many, and many-to-many — requires careful construction of SQL queries, joins, and foreign key constraints. When using direct database access, such operations often involve verbose, error-prone SQL that must be manually maintained.</p><p>ORM frameworks simplify relationship modeling by enabling developers to define associations directly in the application layer using declarative syntax. These associations are represented as object references, allowing seamless navigation and querying across related entities without explicitly writing JOIN clauses. This abstraction enhances both the expressiveness and clarity of data access logic.</p><p>The following example demonstrates how a one-to-many relationship between User and Post entities is modeled and queried.</p><p><strong><em>Raw SQL (Using </em></strong><strong><em>pg, One-to-Many Query):</em></strong></p><pre>async function getPostsByUser(userId) {<br>  const query = `<br>    SELECT posts.id, posts.title<br>    FROM posts<br>    INNER JOIN users ON users.id = posts.user_id<br>    WHERE users.id = $1<br>  `;<br>  const result = await pool.query(query, [userId]);<br>  return result.rows;<br>}</pre><p><strong><em>ORM Version (Sequelize, with Relationship Mapping):</em></strong></p><pre>// models/User.js<br>const User = sequelize.define(&#39;User&#39;, {<br>  name: DataTypes.STRING,<br>});<br><br>// models/Post.js<br>const Post = sequelize.define(&#39;Post&#39;, {<br>  title: DataTypes.STRING,<br>});<br><br>// Define relationship<br>User.hasMany(Post, { foreignKey: &#39;user_id&#39; });<br>Post.belongsTo(User, { foreignKey: &#39;user_id&#39; });<br><br>// Query with ORM<br>async function getPostsByUser(userId) {<br>  const user = await User.findByPk(userId, {<br>    include: [{ model: Post }]<br>  });<br>  return user.Posts;<br>}</pre><p>In the ORM example, relationships are modeled once using high-level method calls such as hasMany and belongsTo. The resulting association can then be leveraged across the application to execute related queries without explicitly specifying join conditions. This not only reduces the amount of code but also minimizes the likelihood of errors arising from incorrectly written joins or mismatched foreign keys.</p><p>Thanks for reading!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c09007807996" width="1" height="1" alt=""><hr><p><a href="https://medium.com/deno-the-complete-reference/5-key-advantages-of-using-orm-over-direct-database-access-in-node-js-applications-c09007807996">5 Key Advantages of Using ORM over Direct Database Access in Node.js Applications</a> was originally published in <a href="https://medium.com/deno-the-complete-reference">Tech Tonic</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Node.js: The fastest web framework in 2025 — Static file server case]]></title>
            <link>https://medium.com/deno-the-complete-reference/node-js-the-fastest-web-framework-in-2025-static-file-server-case-1df462ad38cd?source=rss----cca06f8d4889---4</link>
            <guid isPermaLink="false">https://medium.com/p/1df462ad38cd</guid>
            <category><![CDATA[nodejs]]></category>
            <dc:creator><![CDATA[Mayank C]]></dc:creator>
            <pubDate>Mon, 09 Jun 2025 05:17:54 GMT</pubDate>
            <atom:updated>2025-06-09T05:17:54.335Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*HcqY0903HCy_jlf4ejBvzg.png" /></figure><h3>Node.js: The fastest web framework in 2025 — Static file server case</h3><h3>Introduction</h3><p>This article serves as the 2025 update to one of my most widely read pieces over the past three years. At the start of 2024, I published a comparative analysis of the most popular web frameworks in the Node.js ecosystem. That article &amp; the series continues to attract significant traffic and remains the top result on Google for its topic.</p><p>In the <a href="https://medium.com/deno-the-complete-reference/node-js-the-fastest-web-framework-in-2025-3d13ac5c35b6">previous &amp; first</a> article of this series, we’ve already seen how the frameworks compare to each other for the simplest hello world case. The results (RPS only) are summarized below:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/1*geqe0WdM3w2E7NXRJgFN_w.png" /></figure><p><a href="https://medium.com/deno-the-complete-reference/node-js-the-fastest-web-framework-in-2025-3d13ac5c35b6">Node.js: The fastest web framework in 2025</a></p><p>From this article, we’ll start looking at more practical use cases. In this article, the second in the series, we’ll take a comparative look at the case of static file servers.</p><p><strong>NOTE</strong>: The winner from ‘hello world case’, hyper express, is dropped out of this comparison. HyperExpress uses LiveDirectory to load all the files in memory, which makes it impractical. I have 100K files in my test environment, and loading them into memory is totally impractical.</p><p>If you happen to see this article first, you can see others in this series here:</p><ul><li><a href="https://medium.com/deno-the-complete-reference/node-js-the-fastest-web-framework-in-2025-3d13ac5c35b6">Fastest web framework in 2025 — Hello world case</a></li></ul><h3>Test setup</h3><p>All tests are executed on MacBook Pro M2 with 16G RAM. There are 8+4 CPU cores. The Node.js version is v23.11.1 (latest at the time of writing).</p><p>The load tester is a modified version of Bombardier (written in Go) that can send a different static URL for each request.</p><p>There are 100K files in the static directory. Each file size is exactly 10K (102400 bytes).</p><p>The code for each application is as follows:</p><h4>Express</h4><pre>import express from &quot;express&quot;;<br><br>const app = express();<br><br>app.use(<br>  &quot;/static&quot;,<br>  express.static(&quot;/Users/mayankc/Work/source/perfComparisons/static&quot;),<br>);<br><br>app.listen(3000);</pre><h4>Fastify</h4><pre>const fastify = require(&quot;fastify&quot;);<br>const app = fastify();<br><br>app.register(require(&quot;@fastify/static&quot;), {<br>  root: &quot;/Users/mayankc/Work/source/perfComparisons/static&quot;,<br>  prefix: &quot;/static/&quot;,<br>  prefixAvoidTrailingSlash: true,<br>});<br><br>app.listen({ port: 3000 });</pre><h4>Hapi</h4><pre>const Hapi = require(&quot;@hapi/hapi&quot;);<br><br>const start = async () =&gt; {<br>  const server = Hapi.server({<br>    routes: {<br>      files: {<br>        relativeTo: &quot;/Users/mayankc/Work/source/perfComparisons/static&quot;,<br>      },<br>    },<br>    port: 3000,<br>    host: &quot;127.0.0.1&quot;,<br>  });<br><br>  await server.register(require(&quot;@hapi/inert&quot;));<br><br>  server.route({<br>    method: &quot;GET&quot;,<br>    path: &quot;/static/{filename}&quot;,<br>    handler: {<br>      file: function (request) {<br>        return request.params.filename;<br>      },<br>    },<br>  });<br><br>  await server.start();<br>};<br><br>start();</pre><h4>Koa</h4><pre>import Koa from &quot;koa&quot;;<br>import serve from &quot;koa-static&quot;;<br>import mount from &quot;koa-mount&quot;;<br><br>const app = new Koa();<br>app.use(<br>  mount(&quot;/static&quot;, serve(&quot;/Users/mayankc/Work/source/perfComparisons/static&quot;)),<br>);<br><br>app.listen(3000);</pre><h4>Restify</h4><pre>import restify from &quot;restify&quot;;<br><br>var server = restify.createServer();<br>server.get(<br>  &quot;/static/*&quot;,<br>  restify.plugins.serveStaticFiles(<br>    &quot;/Users/mayankc/Work/source/perfComparisons/static&quot;,<br>  ),<br>);<br>server.listen(3000);</pre><h4>Ultimate Express</h4><pre>import express from &quot;ultimate-express&quot;;<br><br>const app = express();<br><br>app.use(<br>  &quot;/static&quot;,<br>  express.static(&quot;/Users/mayankc/Work/source/perfComparisons/static&quot;),<br>);<br><br>app.listen(3000);</pre><h4>NestJS</h4><p><em>main.ts</em></p><pre>import { NestFactory } from &quot;@nestjs/core&quot;;<br>import { AppModule } from &quot;./app.module&quot;;<br><br>async function bootstrap() {<br>  const app = await NestFactory.create(AppModule);<br>  await app.listen(3000);<br>}<br><br>bootstrap();</pre><p><em>app.module.ts</em></p><pre>import { Module } from &quot;@nestjs/common&quot;;<br>import { ServeStaticModule } from &quot;@nestjs/serve-static&quot;;<br>import { AppController } from &quot;./app.controller&quot;;<br><br>@Module({<br>  imports: [<br>    ServeStaticModule.forRoot({<br>      rootPath: &quot;/Users/mayankc/Work/source/perfComparisons/static&quot;,<br>      serveRoot: &quot;/static&quot;,<br>    }),<br>  ],<br>  controllers: [AppController],<br>  providers: [],<br>})<br>export class AppModule {}</pre><p><em>app.controller.ts</em></p><pre>import { Controller, Get } from &quot;@nestjs/common&quot;;<br><br>@Controller()<br>export class AppController {<br>  constructor() {}<br><br>  @Get()<br>  getHello(): string {<br>    return &quot;Hello, world!&quot;;<br>  }<br>}</pre><h4>NestJS Fastify</h4><p><em>main.ts</em></p><pre>import { NestFactory } from &quot;@nestjs/core&quot;;<br>import {<br>  FastifyAdapter,<br>  NestFastifyApplication,<br>} from &quot;@nestjs/platform-fastify&quot;;<br>import { AppModule } from &quot;./app.module&quot;;<br><br>async function bootstrap() {<br>  const app = await NestFactory.create&lt;NestFastifyApplication&gt;(<br>    AppModule,<br>    new FastifyAdapter(),<br>  );<br>  app.useStaticAssets({<br>    root: &quot;/Users/mayankc/Work/source/perfComparisons/static&quot;,<br>    prefix: &#39;/static/&#39;,<br>  });<br>  await app.listen(3000);<br>}<br>bootstrap();</pre><p><em>app.module.ts</em></p><pre>import { Module } from &quot;@nestjs/common&quot;;<br>import { AppController } from &quot;./app.controller&quot;;<br><br>@Module({<br>  imports: [],<br>  controllers: [AppController],<br>  providers: [],<br>})<br>export class AppModule {}</pre><p><em>app.controller.ts</em></p><pre>import { Controller, Get } from &quot;@nestjs/common&quot;;<br><br>@Controller()<br>export class AppController {<br>  constructor() {}<br><br>  @Get()<br>  getHello(): string {<br>    return &quot;Hello, world!&quot;;<br>  }<br>}</pre><h3>Results</h3><p>A total of 200K requests are executed for 100 concurrent connections. Along with latencies, I’ve also collected CPU and memory usage. This gives a complete picture. Just having high RPS is not enough. We also need to know the cost of the RPS.</p><p>The results in chart form are as follows:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/1*u-V0bP0n5KNBEglxz9CW6g.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/1*LcvI2q4jKHLnQzR1k2s8pQ.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/1*upIOvbjNju0y2_Ss6wrPpg.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/1*2Nb2_tCfi7MAF5zr3ax2Ag.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/1*wVDEoCNP3t2ZGhHXaEF0Pw.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/1*irmv-Sgv_cltYDifv59xKQ.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/1*G1aK4wrEPqXvb5XS_FnR6A.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/1*tnG6HDnhTc2EMBYFo8Cwog.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/1*0noKqpimr9egh4SrkabqQA.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/1*Eil7LGh6uGEqINQYoGl6zA.png" /></figure><h4>Verdict</h4><p>To begin with, the results for a practical scenario like serving static files differ significantly from the theoretical “Hello, world” benchmark. In this test, the requests per second (RPS) for most frameworks fall within a relatively narrow range of 8,000 to 9,500, indicating minimal performance variance across the board.</p><p>Notably, <strong>Express</strong>, which ranked lowest in the previous benchmark, climbs to second place in this round. This improvement may reflect the benefits of its long-standing maturity and ongoing optimizations over the years.</p><p>The top performer in this category is <strong>Restify</strong>, which delivers the highest throughput while maintaining relatively low resource consumption — making it a strong candidate for static content delivery.</p><p>Interestingly, <strong>Ultimate-Express</strong>, which led the previous round, ranks at the bottom this time, highlighting how performance can vary significantly depending on the nature of the workload.</p><p><strong>Winner:</strong> Restify</p><p>If you happen to see this article first, you can see others in this series here:</p><ul><li><a href="https://medium.com/deno-the-complete-reference/node-js-the-fastest-web-framework-in-2025-3d13ac5c35b6">Fastest web framework in 2025 — Hello world case</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1df462ad38cd" width="1" height="1" alt=""><hr><p><a href="https://medium.com/deno-the-complete-reference/node-js-the-fastest-web-framework-in-2025-static-file-server-case-1df462ad38cd">Node.js: The fastest web framework in 2025 — Static file server case</a> was originally published in <a href="https://medium.com/deno-the-complete-reference">Tech Tonic</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Node.js: The fastest web framework in 2025]]></title>
            <link>https://medium.com/deno-the-complete-reference/node-js-the-fastest-web-framework-in-2025-3d13ac5c35b6?source=rss----cca06f8d4889---4</link>
            <guid isPermaLink="false">https://medium.com/p/3d13ac5c35b6</guid>
            <category><![CDATA[nodejs]]></category>
            <dc:creator><![CDATA[Mayank C]]></dc:creator>
            <pubDate>Sun, 08 Jun 2025 01:48:30 GMT</pubDate>
            <atom:updated>2025-06-09T05:19:33.076Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*2xQwB1dke_oLBJnB_qKPfw.png" /></figure><h3>Introduction</h3><p>This article serves as the 2025 update to one of my most widely read pieces over the past three years. At the start of 2024, I published a comparative analysis of the most popular web frameworks in the Node.js ecosystem. That article continues to attract significant traffic and remains the top result on Google for its topic.</p><p><a href="https://medium.com/deno-the-complete-reference/node-js-the-fastest-web-framework-in-2024-fa11e513fa75">Node.js: The fastest web framework in 2024</a></p><p>However, given the rapid pace of change in the web development landscape, that piece, now over 18 months old, no longer reflects the current state of the ecosystem. As we approach mid-2025, it’s time for a comprehensive refresh.</p><p>Coincidentally, TechEmpower has also released Round 23 of their widely respected benchmarking series:</p><p><strong>TechEmpower Web Framework Performance Comparison</strong><br> <em>Performance comparison of a wide spectrum of web application frameworks and platforms using community-contributed tests.</em><br> <a href="https://www.techempower.com/">www.techempower.com</a></p><p>This new series of articles builds upon TechEmpower’s findings, with a specific focus on resource usage and latency in addition to raw requests per second (RPS).</p><p>The series will progress through multiple layers of real-world web application complexity — from the classic “Hello, world” example to handling JSON data, serving static files, performing database reads, and ultimately implementing practical use cases such as a URL shortener, a QR code generator, and JWT-based MySQL record access.</p><p>In this first installment, we begin with the foundational “Hello, world” example. While not practical on its own, it serves as a consistent and neutral baseline for all subsequent benchmarks.</p><p>Let’s dive in.</p><p>Before you read further, the other articles in this series are:</p><ul><li><a href="https://medium.com/deno-the-complete-reference/node-js-the-fastest-web-framework-in-2025-static-file-server-case-1df462ad38cd">Fastest web framework in 2025 — Static file server case</a></li></ul><h3>Testing Environment</h3><p>All tests are executed on a MacBook Pro M2 with 16G RAM. There are 8+4 CPU cores. The load tester is Bombardier (written in Go). The Node.js version is v23.11.1 (latest stable release at the time of writing).</p><p>I’m including Node’s native server too. This gives a good baseline for all the other frameworks. It is well known that Node’s native server is pretty much a bare-bones server.</p><p>The code for each application is as follows:</p><h4>Native</h4><pre>import http from &quot;node:http&quot;;<br><br>const reqHandler = (req, resp) =&gt; {<br>  try {<br>    if (req.method !== &quot;GET&quot;) {<br>      return resp.writeHead(405).end();<br>    }<br>    if (req.url !== &quot;/&quot;) {<br>      return resp.writeHead(404).end();<br>    }<br>    resp.writeHead(200, {<br>      &quot;content-type&quot;: &quot;text/plain&quot;,<br>    });<br>    resp.end(&quot;Hello world&quot;);<br>  } catch (e) {<br>    resp.writeHead(500).end();<br>  }<br>};<br><br>http.createServer(reqHandler).listen(3000);</pre><p><em>Note: Native code is longer so that it is close to the other frameworks where we usually don’t see this code.</em></p><h4>Express</h4><p><em>(Etag is disabled because other frameworks doesn’t send it by default)</em></p><pre>import express from &quot;express&quot;;<br><br>const app = express();<br><br>const reqHandler = (req, res) =&gt; {<br>  res.send(&quot;Hello World!&quot;);<br>};<br><br>app.disable(&quot;etag&quot;);<br>app.get(&quot;/&quot;, reqHandler);<br><br>app.listen(3000, () =&gt; console.log(&quot;Listening on 3000&quot;));</pre><h4>Fastify</h4><pre>import Fastify from &quot;fastify&quot;;<br><br>const fastify = Fastify({<br>  logger: false,<br>});<br><br>const reqHandler = (request, reply) =&gt; {<br>  reply.send(&quot;Hello World!&quot;);<br>};<br><br>fastify.get(&quot;/&quot;, reqHandler);<br><br>fastify.listen({ port: 3000 });</pre><h4>Koa</h4><pre>import Koa from &quot;koa&quot;;<br>import Router from &quot;@koa/router&quot;;<br><br>const app = new Koa();<br>const router = new Router();<br><br>const reqHandler = (ctx, next) =&gt; {<br>  ctx.body = &quot;Hello World!&quot;;<br>};<br><br>router.get(&quot;/&quot;, reqHandler);<br><br>app<br>  .use(router.routes())<br>  .use(router.allowedMethods());<br><br>app.listen(3000);</pre><h4>Hapi</h4><pre>import Hapi from &quot;@hapi/hapi&quot;;<br>const server = Hapi.server({<br>  port: 3000,<br>  host: &quot;127.0.0.1&quot;,<br>});<br><br>const reqHandler = (request, h) =&gt; {<br>  return &quot;Hello World!&quot;;<br>};<br><br>server.route({<br>  method: &quot;GET&quot;,<br>  path: &quot;/&quot;,<br>  handler: reqHandler,<br>});<br><br>server.start();</pre><h4>Restify</h4><pre>import restify from &quot;restify&quot;;<br><br>const reqHandler = (req, res, next) =&gt; {<br>  res.contentType = &quot;text/plain&quot;;<br>  res.send(&quot;Hello World!&quot;);<br>  next();<br>};<br><br>var server = restify.createServer();<br>server.get(&quot;/&quot;, reqHandler);<br>server.listen(3000);</pre><h4>Hyper-Express</h4><pre>import HyperExpress from &quot;hyper-express&quot;;<br>const webserver = new HyperExpress.Server();<br><br>const reqHandler = (request, response) =&gt; {<br>  response.send(&quot;Hello World!&quot;);<br>};<br><br>webserver.get(&quot;/&quot;, reqHandler);<br>webserver.listen(3000);</pre><h4>Ultimate-Express</h4><p>New entrant this time.</p><pre>import express from &quot;ultimate-express&quot;;<br><br>const app = express();<br><br>const reqHandler = (req, res) =&gt; {<br>  res.send(&quot;Hello World!&quot;);<br>};<br><br>app.get(&quot;/&quot;, reqHandler);<br><br>app.listen(3000, () =&gt; console.log(&quot;Listening on 3000&quot;));</pre><h4><strong>NestJS</strong></h4><p><em>(The NestJS app code is generated through their CLI. Only few relevant files are shown here)</em></p><p><em>main.ts</em></p><pre>import { NestFactory } from &#39;@nestjs/core&#39;;<br>import { AppModule } from &#39;./app.module&#39;;<br><br>async function bootstrap() {<br>  const app = await NestFactory.create(AppModule);<br>  app.getHttpAdapter().getInstance().set(&#39;etag&#39;, false);<br>  await app.listen(3000);<br>}<br>bootstrap();</pre><p><em>app.module.ts</em></p><pre>import { Module } from &#39;@nestjs/common&#39;;<br>import { AppController } from &#39;./app.controller&#39;;<br>import { AppService } from &#39;./app.service&#39;;<br><br>@Module({<br>  imports: [],<br>  controllers: [AppController],<br>  providers: [AppService],<br>})<br>export class AppModule {}</pre><p><em>app.controller.ts</em></p><pre>import { Controller, Get } from &#39;@nestjs/common&#39;;<br>import { AppService } from &#39;./app.service&#39;;<br><br>@Controller()<br>export class AppController {<br>  constructor(private readonly appService: AppService) {}<br><br>  @Get()<br>  getHello(): string {<br>    return this.appService.getHello();<br>  }<br>}</pre><p><em>app.service.ts</em></p><pre>import { Injectable } from &#39;@nestjs/common&#39;;<br><br>@Injectable()<br>export class AppService {<br>  getHello(): string {<br>    return &#39;Hello World!&#39;;<br>  }<br>}</pre><h4><strong>NestJS (Fastify)</strong></h4><p>By default, NestJS uses express adapter. Therefore, the performance is slightly worse than Express (which itself is the slowest in this lot). Fortunately, NestJS allows configuring fastify adapter instead of express.</p><p><em>main.ts</em></p><pre>import { NestFactory } from &quot;@nestjs/core&quot;;<br>import {<br>  FastifyAdapter,<br>  NestFastifyApplication,<br>} from &quot;@nestjs/platform-fastify&quot;;<br>import { AppModule } from &quot;./app.module&quot;;<br><br>async function bootstrap() {<br>  const app = await NestFactory.create&lt;NestFastifyApplication&gt;(<br>    AppModule,<br>    new FastifyAdapter(),<br>  );<br><br>  await app.listen(3000);<br>}<br>bootstrap();</pre><p>All other files are same as NestJS (express).</p><h3>Results</h3><p>A total of 1M requests are executed for 100 concurrent connections. Along with latencies, I’ve also collected CPU and memory usage. This gives a complete picture. Just having high RPS is not enough. We also need to know the cost of the RPS.</p><p>The results in chart form are as follows:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/1*5tumM24rw67z9oODUvqVcQ.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/1*XXF7j2TgUTVe85-XKxzssA.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/1*PtLWZacInjDY5RGLM44BcQ.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/1*qMwMaxs1-j6r1CNBF2k7gQ.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/1*18nAkuNSv_BHBxUiyvQY7Q.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/1*yiMIGd87O9-eiOhreNXOqA.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/1*Bih5CgLsJ4FY_MvecIO83A.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/1*oTJUgZrUT9DuWZMlnX_UlA.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/1*KQ1BRnUVIHtokdb9kkHrDg.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/1*0umUwZlf_auCymjxmCi7Fg.png" /></figure><h3>Verdict</h3><p>Before diving into the analysis, it’s important to acknowledge that this comparison includes a diverse mix of frameworks — ranging from lightweight libraries to feature-rich, opinionated platforms. Among them, <strong>NestJS</strong> (available with both Express and Fastify underpinnings) stands out as a full-fledged application framework, offering significantly more built-in functionality than most others in this list. As such, comparing NestJS directly with minimalist frameworks like <strong>Koa</strong> may not be entirely equitable.</p><p>In terms of raw performance, <strong>Express</strong> emerges as the slowest, handling approximately 20,000 requests per second. However, it’s worth emphasizing that Express has stood the test of time. It remains the most mature, reliable, and widely adopted web framework in the Node.js ecosystem. Despite lagging in raw throughput, Express continues to be a dominant force and is unlikely to lose its relevance any time soon.</p><p>At the other end of the spectrum, <strong>Hyper-Express</strong> retains its position as the fastest option, consistent with the findings from the 2024 analysis. Close behind is <strong>Ultimate-Express</strong>, which also demonstrates excellent performance. Both leverage <strong>uWebSockets</strong> under the hood, enabling similar high-throughput characteristics.</p><p>As for my personal recommendation, I lean toward <strong>Fastify</strong> (or <strong>Nest-Fastify</strong>) as a balanced choice. It consistently delivers solid performance — approximately 87,000 requests per second — while maintaining the reliability and robustness expected from a production-ready framework.</p><p>Winner: <strong>HyperExpress</strong></p><p>This was only about the do-nothing ‘hello world’ server. The winning margins might not be as wide for more complex cases. I’ll get to them very soon.</p><p>Once again, the other articles in this series are:</p><ul><li><a href="https://medium.com/deno-the-complete-reference/node-js-the-fastest-web-framework-in-2025-static-file-server-case-1df462ad38cd">Fastest web framework in 2025 — Static file server case</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=3d13ac5c35b6" width="1" height="1" alt=""><hr><p><a href="https://medium.com/deno-the-complete-reference/node-js-the-fastest-web-framework-in-2025-3d13ac5c35b6">Node.js: The fastest web framework in 2025</a> was originally published in <a href="https://medium.com/deno-the-complete-reference">Tech Tonic</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Use Optimistic hook in React 19]]></title>
            <link>https://medium.com/deno-the-complete-reference/use-optimistic-hook-in-react-19-97791ee655d5?source=rss----cca06f8d4889---4</link>
            <guid isPermaLink="false">https://medium.com/p/97791ee655d5</guid>
            <category><![CDATA[react]]></category>
            <dc:creator><![CDATA[Mayank C]]></dc:creator>
            <pubDate>Mon, 26 May 2025 18:41:24 GMT</pubDate>
            <atom:updated>2025-05-26T18:41:24.318Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*6lZL1Vxd9b-KeH8fiYPahg.png" /></figure><p>With React 19, the development experience becomes more fluid and user-centric, thanks in part to new hooks like useOptimistic. This hook addresses a long-standing challenge in frontend development—how to make interfaces feel fast and responsive when dealing with asynchronous updates, especially in form submissions, list modifications, or API-driven state transitions.</p><h3>What Is useOptimistic?</h3><p>The useOptimistic hook allows you to optimistically update the UI by predicting the final state of an asynchronous operation before it actually completes. Instead of waiting for a server response to reflect a change, your interface can immediately show the intended result, giving users instant feedback and a smoother experience.</p><p>Internally, useOptimistic works by layering a temporary optimistic state over the current actual state. When the async task finishes, the real state replaces or reconciles with the optimistic one. This helps bridge the perceptual gap between user actions and server acknowledgment.</p><h3>Why is this needed?</h3><p>Before useOptimistic, implementing such a feature often required writing complex custom logic—duplicating state management, manually rolling back changes on failure, and syncing server data. This led to bloated components and fragile UI behavior. The useOptimistic hook simplifies this pattern and makes it declarative.</p><h3>Syntax overview</h3><pre>const [optimisticState, addOptimistic] = useOptimistic(<br>  baseState,<br>  (previousState, optimisticUpdate) =&gt; {<br>    // return a new optimistic state<br>  }<br>);</pre><h4>Parameters:</h4><ol><li><strong>baseState:</strong> The current, committed state from the server or authoritative data source. This should be a value you trust to be accurate, such as the result of a useState, useReducer, or fetched response.</li><li><strong>reducerFn(previousState, optimisticUpdate): </strong>A pure function that defines how each new optimistic update transforms the current optimistic state. It’s similar in spirit to a reducer in useReducer, but it&#39;s applied to maintain an ephemeral state layer over baseState.</li></ol><h4><strong>Returns:</strong></h4><ul><li><strong>optimisticState</strong> – This is what should be rendered in the UI. It&#39;s a blend of the base state and any applied optimistic updates.</li><li><strong>addOptimistic(updatePayload)</strong> – A function you call when you want to simulate a change instantly. It accepts a payload that reducerFn will use to compute the next optimistic state.</li></ul><h3>Internal flow</h3><p>Here’s how useOptimistic behaves in practice:</p><ol><li>The UI renders using optimisticState.</li><li>When a user triggers an action (e.g., form submission), you:</li></ol><ul><li>Call addOptimistic(payload) to show the optimistic change.</li><li>Fire off an async task to persist the change to a backend.</li><li>Once the task resolves, you update baseState to the server-confirmed state via setState, setTodos, or similar.</li></ul><p>3. The optimistic layer is then reconciled or discarded.</p><p>This pattern ensures your interface stays fast and responsive even while waiting for real-world delays like API roundtrips.</p><h3>Code sample 1: Basic counter</h3><p>This example demonstrates a simple use case of incrementing a counter optimistically.</p><pre>&#39;use client&#39;; // needed for client components<br><br>import { useOptimistic, useState } from &#39;react&#39;;<br><br>export default function Counter() {<br>  const [count, setCount] = useState(0);<br>  const [optimisticCount, addOptimistic] = useOptimistic(count, (prev, next) =&gt; prev + next);<br><br>  const increment = async () =&gt; {<br>    addOptimistic(1); // show immediately<br>    await fakeAPICall(); // simulate server sync<br>    setCount((c) =&gt; c + 1); // commit real state<br>  };<br><br>  return (<br>    &lt;div&gt;<br>      &lt;p&gt;Count: {optimisticCount}&lt;/p&gt;<br>      &lt;button onClick={increment}&gt;+1&lt;/button&gt;<br>    &lt;/div&gt;<br>  );<br>}<br><br>async function fakeAPICall() {<br>  return new Promise((res) =&gt; setTimeout(res, 1000));<br>}</pre><ul><li>The UI shows the new count instantly.</li><li>The real state count is updated once the fake API call completes.</li><li>This structure mimics how real-time apps (like chat or notification systems) behave.</li></ul><h3>Code sample 2: Optimistic to-do list</h3><p>Imagine a to-do list where new tasks are shown immediately after submission, even though the server response may take a second or two.</p><pre>&#39;use client&#39;;<br><br>import { useOptimistic, useState } from &#39;react&#39;;<br><br>export default function TodoList() {<br>  const [todos, setTodos] = useState([{ id: 1, title: &#39;Learn React 19&#39; }]);<br><br>  const [optimisticTodos, addOptimisticTodo] = useOptimistic(<br>    todos,<br>    (prev, next) =&gt; [...prev, next]<br>  );<br><br>  const handleAdd = async (title) =&gt; {<br>    const newTodo = { id: Date.now(), title };<br>    addOptimisticTodo(newTodo); // temporary optimistic view<br><br>    const serverTodo = await saveToServer(newTodo);<br>    setTodos((t) =&gt; [...t, serverTodo]); // update real state<br>  };<br><br>  return (<br>    &lt;div&gt;<br>      &lt;ul&gt;<br>        {optimisticTodos.map((todo) =&gt; (<br>          &lt;li key={todo.id}&gt;{todo.title}&lt;/li&gt;<br>        ))}<br>      &lt;/ul&gt;<br>      &lt;button onClick={() =&gt; handleAdd(&#39;Write docs&#39;)}&gt;Add Task&lt;/button&gt;<br>    &lt;/div&gt;<br>  );<br>}<br><br>async function saveToServer(todo) {<br>  return new Promise((res) =&gt;<br>    setTimeout(() =&gt; res({ ...todo, id: Math.random() * 10000 }), 1500)<br>  );<br>}</pre><ul><li>Optimistic updates prevent flickers or perceived lags in dynamic UIs.</li><li>The server may return a different id, so reconciling it back into the real state is crucial.</li><li>This pattern improves UX while retaining data integrity.</li></ul><h3>Code sample 3: Error handling &amp; rollback</h3><p>Optimistic updates should also handle failures gracefully. In this example, we simulate a rollback if the server call fails.</p><pre>&#39;use client&#39;;<br><br>import { useOptimistic, useState } from &#39;react&#39;;<br><br>export default function FriendRequest() {<br>  const [status, setStatus] = useState(&#39;Not Sent&#39;);<br>  const [optimisticStatus, addOptimisticStatus] = useOptimistic(status, (_, next) =&gt; next);<br><br>  const sendRequest = async () =&gt; {<br>    addOptimisticStatus(&#39;Sending...&#39;);<br>    try {<br>      await sendToServer(); // may fail<br>      setStatus(&#39;Sent&#39;);<br>    } catch {<br>      setStatus(&#39;Not Sent&#39;); // rollback on error<br>    }<br>  };<br><br>  return (<br>    &lt;div&gt;<br>      &lt;p&gt;Status: {optimisticStatus}&lt;/p&gt;<br>      &lt;button onClick={sendRequest}&gt;Send Friend Request&lt;/button&gt;<br>    &lt;/div&gt;<br>  );<br>}<br><br>async function sendToServer() {<br>  const shouldFail = Math.random() &lt; 0.3;<br>  return new Promise((res, rej) =&gt;<br>    setTimeout(() =&gt; (shouldFail ? rej() : res()), 1000)<br>  );<br>}</pre><ul><li>Reflects the transient “Sending…” state immediately.</li><li>If the server fails, we rollback to the prior state.</li><li>Demonstrates how useOptimistic integrates with error boundaries and recovery flows.</li></ul><h3>Code Sample 4: Optimistic product rating</h3><p>In this example, we optimistically update the average rating of a product as soon as the user submits a new rating.</p><pre>&#39;use client&#39;;<br><br>import { useOptimistic, useState } from &#39;react&#39;;<br><br>export default function ProductRating() {<br>  const [ratingData, setRatingData] = useState({ total: 100, count: 25 });<br><br>  const [optimisticRating, addOptimisticRating] = useOptimistic(<br>    ratingData,<br>    (prev, newRating) =&gt; {<br>      const updatedTotal = prev.total + newRating;<br>      const updatedCount = prev.count + 1;<br>      return { total: updatedTotal, count: updatedCount };<br>    }<br>  );<br><br>  const handleRate = async (value) =&gt; {<br>    addOptimisticRating(value); // optimistic feedback<br>    try {<br>      await submitRatingToServer(value);<br>      setRatingData((prev) =&gt; ({<br>        total: prev.total + value,<br>        count: prev.count + 1,<br>      }));<br>    } catch {<br>      // Handle rollback or notify user<br>    }<br>  };<br><br>  const average = (optimisticRating.total / optimisticRating.count).toFixed(1);<br><br>  return (<br>    &lt;div&gt;<br>      &lt;p&gt;Average Rating: {average}&lt;/p&gt;<br>      &lt;button onClick={() =&gt; handleRate(5)}&gt;Rate 5 Stars&lt;/button&gt;<br>    &lt;/div&gt;<br>  );<br>}<br><br>async function submitRatingToServer(value) {<br>  return new Promise((res) =&gt; setTimeout(() =&gt; res(true), 1000));<br>}</pre><ul><li>This simulates instant feedback on a rating action.</li><li>The user sees the updated average immediately.</li><li>If the server fails, error handling or a silent rollback can be added to revert the UI.</li></ul><h3>Code sample 5: Optimistic shopping cart update</h3><p>In a real-time e-commerce scenario, users expect immediate updates when adding items to a cart. This example shows how to use useOptimistic to manage that.</p><pre>&#39;use client&#39;;<br><br>import { useOptimistic, useState } from &#39;react&#39;;<br><br>export default function ShoppingCart() {<br>  const [cart, setCart] = useState([{ id: &#39;123&#39;, name: &#39;T-Shirt&#39;, qty: 1 }]);<br><br>  const [optimisticCart, addOptimisticItem] = useOptimistic(<br>    cart,<br>    (prevCart, newItem) =&gt; {<br>      const existing = prevCart.find((item) =&gt; item.id === newItem.id);<br>      if (existing) {<br>        return prevCart.map((item) =&gt;<br>          item.id === newItem.id ? { ...item, qty: item.qty + newItem.qty } : item<br>        );<br>      }<br>      return [...prevCart, newItem];<br>    }<br>  );<br><br>  const handleAddToCart = async () =&gt; {<br>    const newItem = { id: &#39;456&#39;, name: &#39;Cap&#39;, qty: 1 };<br>    addOptimisticItem(newItem);<br><br>    try {<br>      await fakeCartAPI(newItem);<br>      setCart((prev) =&gt; {<br>        const existing = prev.find((item) =&gt; item.id === newItem.id);<br>        if (existing) {<br>          return prev.map((item) =&gt;<br>            item.id === newItem.id ? { ...item, qty: item.qty + 1 } : item<br>          );<br>        }<br>        return [...prev, newItem];<br>      });<br>    } catch {<br>      // optional rollback logic<br>    }<br>  };<br><br>  return (<br>    &lt;div&gt;<br>      &lt;h3&gt;Shopping Cart&lt;/h3&gt;<br>      &lt;ul&gt;<br>        {optimisticCart.map((item) =&gt; (<br>          &lt;li key={item.id}&gt;<br>            {item.name} - Qty: {item.qty}<br>          &lt;/li&gt;<br>        ))}<br>      &lt;/ul&gt;<br>      &lt;button onClick={handleAddToCart}&gt;Add Cap&lt;/button&gt;<br>    &lt;/div&gt;<br>  );<br>}<br><br>async function fakeCartAPI(item) {<br>  return new Promise((res) =&gt; setTimeout(() =&gt; res(true), 1000));<br>}</pre><ul><li>Demonstrates how to merge new items or update quantities optimistically.</li><li>Avoids UI flicker by updating the cart list instantly.</li><li>Allows for graceful degradation if the server response fails.</li></ul><h3>Best practices</h3><ul><li><strong>Keep optimistic updates minimal</strong>: Only reflect what’s necessary. Overextending optimistic assumptions can cause jarring rollbacks.</li><li><strong>Ensure reconciliation logic is robust</strong>: Sync real and optimistic states carefully, especially when the server modifies data (e.g., timestamps, IDs).</li><li><strong>Combine with suspense and error boundaries</strong>: For a seamless experience in server-heavy apps, pair useOptimistic with use() and suspense.</li></ul><h3>When not to use it</h3><ul><li><strong>Critical state updates</strong>: For banking, transactions, or sensitive operations, avoid assuming success.</li><li><strong>Highly unpredictable server behavior</strong>: If the outcome is frequently different from the input, optimistic updates may degrade UX rather than improve it.</li></ul><p>Thanks for reading!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=97791ee655d5" width="1" height="1" alt=""><hr><p><a href="https://medium.com/deno-the-complete-reference/use-optimistic-hook-in-react-19-97791ee655d5">Use Optimistic hook in React 19</a> was originally published in <a href="https://medium.com/deno-the-complete-reference">Tech Tonic</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[AWS OIDC Federation: Secure Authentication with GitHub & GitLab]]></title>
            <link>https://medium.com/deno-the-complete-reference/aws-oidc-federation-secure-authentication-with-github-gitlab-8da381251ba0?source=rss----cca06f8d4889---4</link>
            <guid isPermaLink="false">https://medium.com/p/8da381251ba0</guid>
            <category><![CDATA[oidc]]></category>
            <category><![CDATA[github-actions]]></category>
            <category><![CDATA[cicd]]></category>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[gitlab-ci]]></category>
            <dc:creator><![CDATA[BrianKim]]></dc:creator>
            <pubDate>Tue, 01 Apr 2025 17:18:20 GMT</pubDate>
            <atom:updated>2025-04-01T17:18:20.577Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*UfpgIBHxgclP4GCQfVOumw.jpeg" /></figure><h3>Introduction</h3><p>Managing AWS credentials in CI/CD pipelines can be risky and cumbersome. With <strong>OIDC federation</strong>, you can securely authenticate GitHub Actions and GitLab CI/CD pipelines to AWS <strong>without storing static credentials</strong>. In this post, we’ll walk through how to set up OIDC federation with AWS, enabling secure, short-lived access for your automation workflows — the modern way.</p><blockquote>For Github, <strong>OIDC is supported on all plans</strong>, including <strong>Free</strong> tier and <strong>hosted runners</strong>, but for Gitlab, it’s available only if you are on <strong>Premium, Ultimate</strong>, or using <strong>GitLab-hosted runners</strong> that support OIDC</blockquote><h3>What is OIDC Federation?</h3><p>Modern cloud security favors <strong>short-lived, identity-based credentials</strong> over long-lived static access keys. That’s where <strong>OIDC (OpenID Connect) Federation</strong> comes in.</p><p>OIDC is an authentication protocol based on OAuth 2.0 that allows external identity providers (like <strong>GitHub Actions</strong> or <strong>GitLab CI</strong>) to securely <strong>federate access to AWS</strong> — without creating or storing long-term credentials.</p><h4>Why Use OIDC in CI/CD?</h4><p>Traditionally, teams use IAM users or long-lived access keys in GitHub/GitLab to access AWS. This approach is:</p><ul><li>❌ Hard to rotate securely</li><li>❌ Risky if leaked (especially in public repos)</li><li>❌ Shared across environments or pipelines</li></ul><p>OIDC solves this by allowing GitHub or GitLab to <strong>assume a role in AWS</strong> securely at runtime using an <strong>identity token</strong>, with <strong>no secrets stored</strong> in your code or pipeline.</p><h4>How It Works</h4><ol><li>In your cloud provider, create an OIDC trust between your cloud role and your GitHub workflow(s) that need access to the cloud.</li><li>Every time a workflow runs, GitHub’s OIDC provider <strong>auto-generates a signed OIDC token</strong>. This token contains claims like the repository, branch, workflow, and job context — establishing a <strong>secure and verifiable identity</strong> about the job.</li><li>The workflow presents this token to AWS using sts:AssumeRoleWithWebIdentity.</li><li>AWS <strong>validates the claims</strong> and <strong>issues temporary cloud credentials</strong> to the job — valid only for the duration of the run.</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*hRturcrRev4mOlXP.png" /></figure><h3>Creating an OIDC Trust in AWS (GitHub &amp; GitLab)</h3><p>Now that we understand the power of OIDC Federation, the next step is to <strong>create that trust</strong> between AWS and your CI/CD platform.</p><p>This trust is what allows GitHub or GitLab to assume an IAM role — using an OIDC token — without any AWS access keys.</p><h4>Step 1: Create an OIDC Identity Provider in AWS</h4><p>An <strong>Identity Provider</strong> (IdP) in AWS is what lets you register GitHub or GitLab as a trusted issuer of identity tokens. You only need to do this <strong>once per account</strong> per CI/CD provider.</p><p><strong>GitHub OIDC Setup</strong></p><ol><li>Go to the AWS Console → <strong>IAM → Identity providers</strong> → <strong>Add provider</strong></li><li>Choose:</li></ol><ul><li><strong>Provider type</strong>: OpenID Connect</li><li><strong>Provider URL</strong>: <a href="https://token.actions.githubusercontent.com/">https://token.actions.githubusercontent.com</a></li><li><strong>Audience</strong>: sts.amazonaws.com</li></ul><p>3. Click <strong>Add provider</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ss8XNo8U1UOsJd03bwA5Yw.png" /></figure><p>This tells AWS to <strong>trust tokens issued by GitHub Actions</strong>, as long as the token’s audience is AWS STS (Security Token Service). In your GitHub workflow, make sure to explicitly request an OIDC token by setting:</p><pre>permissions:<br>  id-token: write<br>  contents: read</pre><p><strong>GitLab OIDC Setup</strong></p><p>If you’re using <strong>GitLab-hosted runners</strong> (with Premium or Ultimate tier), you can use their native OIDC integration.</p><ol><li>Go to the <strong>AWS Console → IAM → Identity providers → Add provider</strong></li><li>Choose:</li></ol><ul><li><strong>Provider type</strong>: OpenID Connect</li><li><strong>Provider URL</strong>: <a href="https://gitlab.com/">https://gitlab.com</a></li><li><strong>Audience</strong>: <a href="https://gitlab.com/">https://gitlab.com</a></li></ul><p>3. Click <strong>Add provider</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*lp21u7BC4m6muScfWToKXw.png" /></figure><p>Now AWS will accept signed tokens from GitLab jobs. GitLab jobs must explicitly request a token using the id_tokens keyword in .gitlab-ci.yml:</p><pre>id_tokens:<br>  default:<br>    aud: https://gitlab.com</pre><h4>Step 2: Create IAM Role(s) with Trust Policy and Permissions</h4><p>With the identity provider in place, we now create one <strong>IAM role per environment</strong> (dev, stg, prod). Each role:</p><ul><li>Defines <strong>what the workflow is allowed to do</strong> (permissions)</li><li>Defines <strong>who is allowed to assume it</strong> (trust policy)</li></ul><p><strong>Permission (</strong>GitHubRole_dev or GitLabRole_dev<strong> )</strong></p><pre>{<br>  &quot;Version&quot;: &quot;2012-10-17&quot;,<br>  &quot;Statement&quot;: [<br>    {<br>      &quot;Effect&quot;: &quot;Allow&quot;,<br>      &quot;Action&quot;: [&quot;secretsmanager:GetSecretValue&quot;],<br>      &quot;Resource&quot;: &quot;arn:aws:secretsmanager:YOUR_AWS_REGION:YOUR_AWS_ACCOUNT_ID:secret:dev/v1.0.0/*&quot;<br>    },<br>    {<br>      &quot;Effect&quot;: &quot;Allow&quot;,<br>      &quot;Action&quot;: [&quot;ssm:SendCommand&quot;],<br>      &quot;Resource&quot;: &quot;*&quot;,<br>      &quot;Condition&quot;: {<br>        &quot;StringEquals&quot;: {<br>          &quot;ec2:ResourceTag/Environment&quot;: &quot;dev&quot;<br>        }<br>      }<br>    }<br>  ]<br>}</pre><p>This policy allows the role to:</p><ul><li>Read secrets under dev/v1.0.0/ from <strong>Secrets Manager</strong></li><li>Send SSM commands only to EC2 instances tagged as Environment=dev</li></ul><p><strong>IAM Trust Policy</strong></p><pre>{<br>  &quot;Version&quot;: &quot;2012-10-17&quot;,<br>  &quot;Statement&quot;: [<br>    {<br>      &quot;Effect&quot;: &quot;Allow&quot;,<br>      &quot;Principal&quot;: {<br>        &quot;Federated&quot;: &quot;arn:aws:iam::YOUR_AWS_ACCOUNT_ID:oidc-provider/token.actions.githubusercontent.com&quot;<br>      },<br>      &quot;Action&quot;: &quot;sts:AssumeRoleWithWebIdentity&quot;,<br>      &quot;Condition&quot;: {<br>        &quot;StringEquals&quot;: {<br>          &quot;token.actions.githubusercontent.com:aud&quot;: &quot;sts.amazonaws.com&quot;<br>        },<br>        &quot;StringLike&quot;: {<br>          &quot;token.actions.githubusercontent.com:sub&quot;: &quot;repo:your-org/your-repo:*&quot;<br>        }<br>      }<br>    },<br>    {<br>      &quot;Effect&quot;: &quot;Allow&quot;,<br>      &quot;Principal&quot;: {<br>        &quot;Federated&quot;: &quot;arn:aws:iam::YOUR_AWS_ACCOUNT_ID:oidc-provider/gitlab.com&quot;<br>      },<br>      &quot;Action&quot;: &quot;sts:AssumeRoleWithWebIdentity&quot;,<br>      &quot;Condition&quot;: {<br>        &quot;StringEquals&quot;: {<br>          &quot;gitlab.com:aud&quot;: &quot;https://gitlab.com&quot;<br>        },<br>        &quot;StringLike&quot;: {<br>          &quot;gitlab.com:sub&quot;: &quot;project_path:your-group/your-project:ref_type:branch:ref:*&quot;<br>        }<br>      }<br>    }<br>  ]<br>}</pre><p>This lets <strong>either GitHub or GitLab</strong> assume the same role — useful if you’re deploying from both platforms.</p><h3>Prepare EC2 for Deployment</h3><p>Once GitHub/GitLab has secure access to your AWS environment using OIDC, the next step is to prepare your EC2 instance for actual app deployment. This includes setting up reverse proxy, TLS certificates, and storing environment-specific secrets.</p><h4>Setting Up TLS with Let’s Encrypt on a Custom-Built NGINX Server</h4><p>To maximize flexibility and enable custom modules, we install NGINX from source. This approach is documented in the <a href="https://github.com/Open-Web-Src/nginx-scripts/blob/dev/README.md">project’s README</a>, which includes:</p><ul><li>Compiling NGINX with required modules</li><li>Creating a systemd service</li><li>Reverse proxy config for NestJS (port 3000)</li></ul><p>After you finish setting up the Nginx from source, then let’s install TLS certificate to secure out requests.</p><p><strong>Option 1 — DNS-01 Challenge (Manual, No Auto-Renew)</strong></p><p>Use this if you prefer manual setup and are okay with reissuing the certificate manually every 90 days.</p><pre>sudo certbot certonly --manual --preferred-challenges dns -d yourdomain.com</pre><p>This method will prompt you to add a TXT record to your domain’s DNS. No HTTP server is needed. However, <strong>auto-renewal is not supported</strong> unless you use DNS automation via route53 or another provider plugin.</p><p><strong>Option 2 — HTTP-01 Challenge (Supports Auto-Renew)</strong></p><p>Before issuing the certificate, you must configure NGINX to serve .well-known/acme-challenge/ from a <strong>persistent path</strong> that won&#39;t be overridden during deployments.</p><p>Update your NGINX config (typically at /etc/nginx/nginx.conf or your custom path):</p><pre>user www-data;<br>worker_processes auto;<br><br>events {<br>    worker_connections 65000;<br>    multi_accept on;<br>}<br><br>http {<br>    include mime.types;<br>    default_type application/octet-stream;<br><br>    client_body_buffer_size 10K;<br>    client_max_body_size 8m;<br>    client_header_buffer_size 1k;<br>    client_body_timeout 12;<br>    client_header_timeout 12;<br>    keepalive_timeout 15;<br>    send_timeout 10;<br><br>    sendfile on;<br>    tcp_nopush on;<br>    tcp_nodelay on;<br>    types_hash_max_size 2048;<br><br>    gzip on;<br>    gzip_disable &quot;msie6&quot;;<br>    gzip_vary on;<br>    gzip_proxied any;<br>    gzip_comp_level 6;<br>    gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;<br><br>    # HTTP-only server for certificate issuance<br>    server {<br>        listen 80;<br>        server_name yourdomain.com;<br><br>        # Serve ACME challenge from a safe location<br>        location ^~ /.well-known/acme-challenge/ {<br>            root /var/www/letsencrypt;<br>            allow all;<br>        }<br><br>        # Redirect everything else to HTTPS<br>        location / {<br>            return 301 https://$host$request_uri;<br>        }<br>    }<br>}</pre><p>Then create the directory that will be used for challenges:</p><pre>sudo mkdir -p /var/www/letsencrypt<br>sudo chown -R ec2-user /var/www/letsencrypt</pre><p>Restart NGINX (use your custom path):</p><pre>systemctl restart nginx</pre><p>Now that NGINX is ready to serve challenges, issue your certificate:</p><pre>sudo certbot certonly --webroot -w /var/www/letsencrypt -d yourdomain.com</pre><p>If successful, Certbot will create:</p><pre>/etc/letsencrypt/live/yourdomain.com/fullchain.pem<br>/etc/letsencrypt/live/yourdomain.com/privkey.pem</pre><p>Now insert the listen 443 server block with SSL config:</p><pre>server {<br>    listen 443 ssl http2;<br>    server_name yourdomain.com;<br><br>    ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;<br>    ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;<br><br>    ssl_protocols TLSv1.2 TLSv1.3;<br>    ssl_ciphers ECDH+AESGCM:ECDH+AES256:ECDH+AES128:DH+3DES:!ADH:!AECDH:!MD5;<br>    ssl_prefer_server_ciphers on;<br><br>    ssl_session_cache shared:SSL:40m;<br>    ssl_session_timeout 4h;<br>    ssl_session_tickets on;<br>    ssl_dhparam /etc/nginx/ssl/dhparam.pem;<br><br>    add_header Strict-Transport-Security &quot;max-age=31536000; includeSubDomains&quot; always;<br>    add_header X-Frame-Options DENY;<br>    add_header X-Content-Type-Options nosniff;<br>    add_header Referrer-Policy strict-origin-when-cross-origin;<br>    add_header X-XSS-Protection &quot;1; mode=block&quot;;<br><br>    root /home/ec2-user/sites;<br>    index index.html;<br><br>    location / {<br>        try_files $uri $uri/ =404;<br>    }<br><br>    location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2|ttf|eot|otf|mp4|webm|ogg|ogv|webp|avif)$ {<br>        expires 1M;<br>        access_log off;<br>        log_not_found off;<br>        add_header Cache-Control &quot;public, max-age=2592000, immutable&quot;;<br>        add_header Pragma public;<br>        add_header Vary Accept-Encoding;<br>    }<br><br>    location /apis {<br>        proxy_pass http://localhost:3000/;<br>        proxy_set_header X-Real-IP $remote_addr;<br>        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;<br>        proxy_set_header X-Forwarded-Proto $scheme;<br><br>        proxy_buffering on;<br>        proxy_buffers 16 4k;<br>        proxy_buffer_size 8k;<br>        proxy_busy_buffers_size 16k;<br><br>        proxy_connect_timeout 60s;<br>        proxy_send_timeout 60s;<br>        proxy_read_timeout 60s;<br><br>        client_max_body_size 50M;<br>    }<br>}</pre><p>Because NGINX was built from source and is not located at /usr/sbin/nginx, Certbot’s default reload hook won&#39;t work.</p><p>Add this cron job instead:</p><pre>sudo crontab -e</pre><pre>0 2 * * * certbot renew --quiet --deploy-hook &quot;/usr/bin/nginx -s reload&quot;</pre><p>Test it:</p><pre>sudo certbot renew --dry-run</pre><p>This ensures TLS certificates will renew automatically before they expire.</p><p><strong>Make sure everytime CICD deploys to server, it does not overwrite </strong><strong>/var/www/letsencrypt/</strong> — or your auto-renewal will fail.</p><h4>Create Cloud Credentials as Secrets for Each Environment</h4><p>Your jobs will likely need credentials (e.g., ec2 host and key, AWS account id). You’ll store these securely based on environment (dev, stg, prod).</p><p><strong>Github</strong>: Repo → Settings → Environments → dev | stg | prod → Add Secret.</p><p><strong>Gitlab</strong>: Settings → CI/CD → Variables</p><h4>Store Environment Variables in AWS Secrets Manager</h4><p>Your app reads secrets like DB credentials from AWS Secrets Manager at runtime. Store them using a clear naming convention:</p><pre>dev/v1.0.0/database<br>stg/v1.0.0/database<br>prod/v1.0.0/database</pre><p>Each secret should contain a JSON payload like:</p><pre>{<br>  &quot;DB_HOST&quot;: &quot;db.dev.example.com&quot;,<br>  &quot;DB_USER&quot;: &quot;postgres&quot;,<br>  &quot;DB_PASSWORD&quot;: &quot;secure_password&quot;,<br>  &quot;DB_NAME&quot;: &quot;myapp&quot;<br>}</pre><p>This way, in your GitHub Actions or GitLab pipeline, you can dynamically fetch:</p><pre>aws secretsmanager get-secret-value --secret-id &quot;${DEPLOY_ENV}/v1.0.0/database&quot;</pre><p>Use the output to generate .env on the EC2 machine during deployment.</p><h3>GitHub Actions Workflow: Deploy NestJS to EC2</h3><h4>Trigger</h4><pre>on:<br>  push:<br>    branches:<br>      - dev<br>      - stg<br>      - main</pre><p>This workflow is triggered <strong>whenever a push happens</strong> to dev, stg, or main. Each branch maps to a deployment environment.</p><h4>Job 1: set-env — Determine Which Environment Is Being Deployed</h4><pre>jobs:<br>  set-env:<br>    name: Set Deployment Environment<br>    runs-on: ubuntu-latest<br>    outputs:<br>      deploy-env: ${{ steps.set-env.outputs.DEPLOY_ENV }}  # ✅ Pass evaluated DEPLOY_ENV<br>    steps:<br>      - name: Evaluate DEPLOY_ENV<br>        id: set-env<br>        run: echo &quot;DEPLOY_ENV=${{ github.ref_name == &#39;main&#39; &amp;&amp; &#39;prod&#39; || github.ref_name }}&quot; &gt;&gt; $GITHUB_OUTPUT</pre><p><strong>What it does:</strong></p><ul><li>Runs a tiny script that checks the branch being pushed</li><li>Dynamically sets DEPLOY_ENV as dev, stg, or prod</li></ul><pre>run: echo &quot;DEPLOY_ENV=${{ github.ref_name == &#39;main&#39; &amp;&amp; &#39;prod&#39; || github.ref_name }}&quot; &gt;&gt; $GITHUB_OUTPUT</pre><ul><li>If main, it sets DEPLOY_ENV=prod</li><li>If dev, it sets DEPLOY_ENV=dev</li><li>If stg, it sets DEPLOY_ENV=stg</li></ul><h4>Job 2: ci — Compile, Lint, and Test the Code</h4><pre>uses: ./.github/workflows/ci.yaml</pre><p>This imports another workflow (ci.yaml) as a <strong>reusable workflow</strong>. It receives:</p><pre>with:<br>  deploy-env: ${{ needs.set-env.outputs.deploy-env }}</pre><p>Inside ci.yaml, there are 4 sub-jobs:</p><h4>🔹 <strong>setup</strong></h4><ul><li>Checks out the repo</li><li>Sets up Node.js</li><li>Generates a cache key based on package-lock.json + env</li><li>Uses actions/cache to store apis/node_modules</li><li>Installs dependencies with npm ci inside the apis/ folder</li></ul><h4>🔹 lint</h4><ul><li>Depends on setup</li><li>Restores node_modules from cache</li><li>Runs npm run lint inside apis/ to enforce code quality</li></ul><h4>🔹 build</h4><ul><li>Depends on setup and lint</li><li>Runs npm run build to generate production-ready JS in apis/dist</li><li>Uploads build output (apis/dist) as an artifact</li></ul><h4>🔹 test</h4><ul><li>Depends on setup and build</li><li>Downloads the compiled app (dist)</li><li>Restores node_modules</li><li>Runs npm run test (or ls -a if placeholder)</li></ul><p>🟩 CI ends by producing:</p><ul><li>node_modules (cached)</li><li>dist/ (artifact)</li></ul><h4>Job 3: deploy — Deploy Built App to EC2</h4><p>This job is responsible for securely deploying the production-ready NestJS application to an EC2 instance. It uses GitHub OIDC to authenticate into AWS, fetches secrets, installs dependencies, and starts the app using PM2.</p><p><strong>Environment Setup</strong></p><pre>env:<br>  DEPLOY_ENV: ${{ needs.set-env.outputs.deploy-env }}<br>environment: ${{ needs.set-env.outputs.deploy-env }}<br>permissions:<br>  id-token: write<br>  contents: read</pre><ul><li>Dynamically assigns the environment (dev, stg, prod) based on the branch.</li><li>Grants the job permission to request a temporary OIDC token to access AWS.</li></ul><p><strong>Checkout the Repository</strong></p><pre>- name: Checkout Repository<br>  uses: actions/checkout@v4</pre><p>Pulls the project source code so it can access project files and structure.</p><p><strong>Setup Node.js Environment</strong></p><pre>- name: Setup Node.js<br>  uses: actions/setup-node@v4<br>  with:<br>    node-version: &#39;20.x&#39;</pre><p>Ensures Node.js is available to run any scripts or installs if needed on the runner.</p><p><strong>Download Build Artifact</strong></p><pre>- name: Download Build Artifact<br>  uses: actions/download-artifact@v4<br>  with:<br>    name: nestjs-build-${{ env.DEPLOY_ENV }}<br>    path: apis/dist/</pre><p>Fetches the compiled app (apis/dist/) previously generated by the CI build job.</p><p><strong>Deploy via Composite Action</strong></p><pre>- name: Deploy using Composite Action<br>  uses: ./.github/actions/deploy-to-ec2<br>  with:<br>    ec2_host: ${{ secrets.EC2_HOST }}<br>    ec2_user: ${{ secrets.EC2_USER }}<br>    ec2_ssh_key: ${{ secrets.EC2_SSH_KEY }}<br>    pm2_app_name: ${{ secrets.PM2_APP_NAME }}<br>    aws_region: &quot;${{ secrets.AWS_REGION }}&quot;<br>    secret_id: &quot;/${{ env.DEPLOY_ENV }}/v1.0.0/database&quot;<br>    role_arn: ${{ secrets.ROLE_ARN }}</pre><p>This step performs the actual deployment using a <strong>reusable composite GitHub Action</strong>, which does the following:</p><p><strong>Authenticate to AWS via OIDC</strong></p><pre>- uses: aws-actions/configure-aws-credentials@v4</pre><p>Uses sts:AssumeRoleWithWebIdentity to get short-lived AWS credentials for deployment.</p><p><strong>Fetch Secrets from AWS Secrets Manager</strong></p><pre>- name: Fetch Secrets via AWS CLI and write .env<br>      shell: bash<br>      run: |<br>        aws secretsmanager get-secret-value \<br>          --secret-id &quot;${{ inputs.secret_id }}&quot; \<br>          --query SecretString \<br>          --output text &gt; secret.json<br><br>        jq -r &#39;to_entries[] | &quot;\(.key)=\(.value)&quot;&#39; secret.json &gt; .env<br>      env:<br>        AWS_DEFAULT_REGION: ${{ inputs.aws_region }}</pre><p>Pulls a secret JSON object (e.g., DB credentials) and exports them as environment variables.</p><p><strong>Upload App to EC2 via SSH</strong></p><pre>scp -i private_key.pem -r .env apis/dist apis/package.json apis/package-lock.json ...</pre><p>Transfers only the necessary build output and config files. Also uploads the sites/ folder separately to the EC2 home directory.</p><p><strong>Deploy on EC2</strong></p><pre>ssh -i private_key.pem ...<br>cd nestjs-app<br>npm ci<br>pm2 start or restart ...</pre><p>Installs Node dependencies, then either starts or restarts the NestJS app with PM2.</p><p>The deployment flow for GitLab CI is fundamentally <strong>the same. </strong>Full implementations for both <strong>GitHub Actions</strong> and <strong>GitLab CI</strong> (including reusable jobs, cache setup, deployment logic, and secret management) are available <a href="https://github.com/Open-Web-Src/nginx-scripts">in the source repo</a>.</p><h3>The OIDC-Driven Pipeline in Action</h3><p>Once everything is configured correctly, here’s what a <strong>successful OIDC-powered deployment</strong> looks like:</p><h4>🚀 1. GitHub Workflow Executes Successfully</h4><p>With the OIDC role assumed, the GitHub Actions workflow automatically:</p><ul><li>Detects the correct deployment environment (dev, stg, or prod)</li><li>Caches and installs dependencies</li><li>Lints, builds, and tests the NestJS app</li><li>Deploys the output to EC2</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*2uhhJmPaEEhVXRl9CpMNuA.png" /></figure><h4>🔐 2. OIDC Role Assumption Log</h4><p>Thanks to the permissions:</p><pre>permissions:<br>  id-token: write<br>  contents: read</pre><p>and this configuration:</p><pre>- name: Configure AWS Credentials with OIDC<br>  uses: aws-actions/configure-aws-credentials@v4</pre><p>You will see the workflow <strong>assume your IAM role</strong> with messages like:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*wH-Lmmc4zyyWSgMDRNqhzg.png" /></figure><p>This confirms that <strong>no long-lived credentials</strong> were used. The job was authenticated using a <strong>short-lived federated identity</strong>, the secure way.</p><h4>3. PM2 Launches the App on EC2</h4><p>Once files are transferred, the EC2 deploy step runs:</p><pre>📦 Installing dependencies via npm ci...<br>🔐 Writing .env from secrets...<br>🚀 Restarting app via PM2...</pre><p>And then you should see:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*P2XtvcrwPYPdfpRqtJLbYA.png" /></figure><p>Your NestJS app is now:</p><ul><li>Running via PM2</li><li>Served behind your custom-built, TLS-enabled NGINX</li><li>Automatically provisioned through GitHub Actions + OIDC</li></ul><h3>Conclusion</h3><p>By integrating <strong>OIDC federation with GitHub Actions and GitLab CI</strong>, you’ve built a <strong>modern, secure, and fully automated deployment pipeline</strong> for your NestJS application. This setup eliminates the need for static AWS credentials, simplifies role-based access, and promotes best practices in cloud security.</p><p>You’ve also:</p><ul><li>Compiled and configured <strong>NGINX from source</strong> for full control</li><li>Secured your EC2 instance with <strong>Let’s Encrypt TLS certificates</strong></li><li>Structured your deployment process to be <strong>environment-aware</strong> (dev/stg/prod)</li><li>Used <strong>AWS Secrets Manager</strong> to manage sensitive environment variables cleanly</li><li>Leveraged <strong>PM2</strong> to manage your app lifecycle in production</li></ul><p>This architecture ensures that each deployment is <strong>predictable, secure, and scalable</strong>, while also being easy to maintain and extend across cloud environments or infrastructure teams.</p><h3>Follow Me On Linkedin To Stay Connected</h3><p><a href="https://www.linkedin.com/in/ninh-kim-927571149/">https://www.linkedin.com/in/ninh-kim-927571149/</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8da381251ba0" width="1" height="1" alt=""><hr><p><a href="https://medium.com/deno-the-complete-reference/aws-oidc-federation-secure-authentication-with-github-gitlab-8da381251ba0">AWS OIDC Federation: Secure Authentication with GitHub &amp; GitLab</a> was originally published in <a href="https://medium.com/deno-the-complete-reference">Tech Tonic</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Strengthening Microservices: Implementing mTLS over gRPC for Trusted Communication]]></title>
            <link>https://medium.com/deno-the-complete-reference/strengthening-microservices-implementing-mtls-over-grpc-for-trusted-communication-946b39333880?source=rss----cca06f8d4889---4</link>
            <guid isPermaLink="false">https://medium.com/p/946b39333880</guid>
            <category><![CDATA[security]]></category>
            <category><![CDATA[mutual-tls]]></category>
            <category><![CDATA[nestjs]]></category>
            <category><![CDATA[grpc]]></category>
            <category><![CDATA[diffie-hellman]]></category>
            <dc:creator><![CDATA[BrianKim]]></dc:creator>
            <pubDate>Fri, 28 Mar 2025 17:06:20 GMT</pubDate>
            <atom:updated>2025-03-28T17:06:20.215Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Inafv40YeS2QY_ul4mGNBw.jpeg" /></figure><h3>Introduction</h3><p>In microservices, service-to-service communication must be secure, especially in distributed or cloud environments. While gRPC handles fast binary messaging, it doesn’t protect against untrusted sources by default.</p><p>That’s where <strong>mTLS (mutual TLS)</strong> comes in — a secure handshake where <strong>both services authenticate each other</strong> using certificates from a shared CA.</p><p>In this post, we’ll start by breaking down how TLS works, then dive into the algorithms behind it and how to issue your own certs. From there, we’ll walk through a step-by-step implementation using <strong>NestJS + gRPC + mTLS</strong>, and finish with a working setup, real results, and a wrap-up of best practices.</p><h3>Understanding TLS</h3><p>TLS is the backbone of secure communication on the internet. It protects your data from being read or tampered with by attackers. Let’s break down <strong>how TLS works</strong> and what those mysterious cipher suites actually mean.</p><h4>The TLS Handshake: Securely Switching to Symmetric</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*gKGtv1BOF1hTatKwuVlXng.png" /></figure><ol><li><strong>Client Hello</strong> <br>The client says:<br> <em>“Hi! I support these TLS versions and cipher suites. Let’s agree on how to talk securely.”</em></li><li><strong>Server Hello<br></strong>The server replies with:<br>- The chosen TLS version<strong><br>- </strong>The selected cipher suite<strong><br>- </strong>Its certificate (which includes the public key)</li><li><strong>Certificate Verification</strong><br>The client:<br>- Verifies the server’s certificate (e.g., using a CA it trusts). If this check fails, the handshake terminates immediately.<br>- Extracts the public key from the certificate</li><li><strong>Key Exchange (ECDHE)</strong><br>- The server sends Diffie-Hellman parameters (curve, ECDHE Pubkey, etc.)<br>- These parameters are digitally signed using the server’s private key. Hence, the client can use the public key (from the verified certificate) to validate the signature and ensure the parameters weren’t tampered with (defending against MITM)</li><li><strong>Shared Key Generation</strong><br>- After verifying everything, the client sends its own ECDHE Pubkey<br>- Now both sides compute the same symmetric key using ECDHE. This key is never sent over the wire — it’s generated independently on both ends.</li><li><strong>Secure Session Begins</strong><br>- All future communication is encrypted using the symmetric key (e.g., AES-256-GCM)<br>- Integrity is ensured using hashing (e.g., SHA-384)</li></ol><h4>What’s Inside a Cipher Suite?</h4><p>A <strong>cipher suite</strong> is a collection of algorithms TLS will use for:</p><ul><li>Key exchange (e.g., Diffie-Hellman, ECDHE, RSA)</li><li>Authentication (e.g., RSA, ECDSA)</li><li>Symmetric encryption (e.g., AES-GCM, ChaCha20)</li><li>Message integrity (e.g., SHA256, SHA384)</li></ul><p>Here’s an example:</p><pre>TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256</pre><ul><li><strong>ECDHE</strong>: Elliptic Curve Diffie-Hellman Ephemeral (secure key exchange)</li><li><strong>ECDSA</strong>: Elliptic Curve Digital Signature Algorithm — used for authentication, meaning the server proves its identity using an ECDSA certificate (instead of RSA).</li><li><strong>AES_128_GCM</strong>: Advanced Encryption Standard with 128-bit key in Galois/Counter Mode — used for encrypting the actual data.</li><li><strong>SHA256</strong>: Used for message integrity, ensuring the encrypted message hasn’t been tampered with.</li></ul><p>Before we dive deeper, let’s cover a few key concepts that will help you understand how TLS actually works.</p><h4>Symmetric Encryption</h4><ul><li>Uses <strong>one shared key</strong> for both encrypting and decrypting.</li><li>Much faster and more efficient for large amounts of data.</li><li>Used in: bulk data encryption during a session.</li></ul><p>📦 Example: AES, ChaCha20</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*obGHROEsEMqgZIJ7MIJPVA.png" /></figure><h4>Hashing Algorithms: Ensuring Data Integrity</h4><p>Encryption keeps your data <strong>confidential</strong>, but how do we make sure it wasn’t <strong>tampered with</strong> in transit?</p><p>That’s where <strong>hashing algorithms</strong> come in.</p><p>A <strong>hash</strong> is like a digital fingerprint of your data — a fixed-length string of characters that uniquely represents the original message. Even a tiny change in the input (like changing one letter) results in a completely different hash.</p><p>Example:</p><pre>sha256(&quot;hello&quot;)  =&gt;  2cf24dba5fb0a...<br>sha256(&quot;hello!&quot;) =&gt;  b1946ac92492d...</pre><p>In TLS, a hashing algorithm like <strong>SHA-256</strong> or <strong>SHA-384</strong> is used to generate a <strong>digest</strong> of the handshake data (e.g. key exchange parameters), then be signed by the server’s <strong>private key</strong> (e.g., RSA or ECDSA), we’ll see this in the next section with Asymmetric Encryption</p><p>If the hashes match, the data <strong>hasn’t been modified</strong> — this proves <strong>integrity</strong> and <strong>authenticity</strong>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Amss07vpabpLqDUvxMCkGw.png" /></figure><p>This is a core part of why TLS is secure — even if an attacker intercepts the traffic, they can’t modify anything without breaking the signature or causing a hash mismatch.</p><h4>Asymmetric Encryption</h4><ul><li>Uses <strong>two keys</strong>: a <strong>public key</strong> (shared with the world) and a <strong>private key</strong> (kept secret).</li><li>Messages encrypted with the public key can only be decrypted by the private key.</li><li>Used in: authentication, digital signatures, and secure key exchange.</li></ul><p>📦 Example: RSA, ECDSA</p><p>In <strong>TLS</strong>, <strong>asymmetric encryption</strong> (public/private key crypto) is <em>not</em> used to encrypt all the communication — that would be too <strong>slow </strong>and<strong> computationally expensive, </strong>as well as requiring the public-private keys pair on both sides is considered <strong>complicated</strong>.</p><p>Instead, it’s only used for validating chain of trust of certificates (out of scope for this post) and be used during the <strong>key exchange phase</strong>, specifically to:</p><ul><li>Generate a <strong>digest</strong> of the handshake data (e.g. key exchange parameters)</li><li>Sign this digest using the server’s <strong>private key</strong> (e.g., RSA or ECDSA)</li><li>Send the signed hash along with the server’s certificate</li></ul><p>Then the client:</p><ul><li>Uses the server’s <strong>public key</strong> (from the cert) to verify the signature</li><li>Recomputes the hash of the same handshake data</li><li>Compares it to the signed version</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*vY9zP9x5F5AERxw3pJ-_8A.png" /></figure><p>The encryption of data sent between client and server during communication is performed through Symmetric Encryption — a much faster and more efficient way. But how to create a shared one and securely transmit that in the public network is not that easy. That leads us to the concept of Diffie-Hellman algorithm.</p><p>With <strong>ephemeral key exchange (ECDHE)</strong>, each session gets a <strong>new symmetric key</strong>, so even if it’s compromised later, <strong>past sessions stay safe</strong> → <strong>Perfect Forward Secrecy</strong>.</p><p>Alright, now that we’ve covered the essentials, let’s get back into how TLS actually works.</p><h4>The Magic of Diffie-Hellman (Key Exchange)</h4><p>Diffie-Hellman (especially ECDHE) is how both sides generate the same symmetric key — <strong>without ever sending it directly</strong>.</p><p>Each side generates a private key, computes a public version, exchanges that public data, and ends up with the same secret.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*KL074lWS_lDIaudK.png" /></figure><p>Here’s a snapshot captured using <strong>Wireshark</strong>, showing a packet exchanged during the <strong>TLS key exchange</strong> process.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Jx1h_s6gLU6CeoPQ-E0Yyg.png" /></figure><p>When using <strong>Elliptic Curve Diffie-Hellman Ephemeral (ECDHE)</strong>, the server sends the following:</p><ul><li><strong>Curve Type</strong>: Tells the client the form of the elliptic curve equation. Typically, this is something like y² = x³ + ax + b. The curve type determines the values of a and b.</li><li><strong>Named Curve</strong>: Instead of sending full curve parameters, a predefined curve (like secp256r1) is used. This includes the starting generator point G.</li><li><strong>Public Key (Pubkey)</strong>: This is the result of multiplying the server’s <strong>private key</strong> with the base point G. Think of it as:</li></ul><pre>PubKey = privKey × G</pre><p>The client uses this information, performs a similar operation with its own private key, then send its PubKey back to the server. Eventually, and both end up with the <strong>same shared secret key</strong>.</p><pre>Key = privKey × PubKey</pre><p>Even though the PubKey are exchanged in the open, the private keys are never sent. Thanks to the hardness of the <strong>elliptic curve discrete logarithm problem</strong>, an attacker can’t reverse-engineer the private key from the PubKey. If someone compromises the TLS cert in the future, they still can’t decrypt past messages.</p><h3>How mTLS Works: Algorithms and Certificate Issuance</h3><p>Normally in TLS, the server has a TLS certificate and a public/private key pair, while the client does not.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*_9MlUgoUlgDbCzqi.png" /></figure><p>In mTLS, however, both the client and server have a certificate, and both sides authenticate using their public/private key pair. Compared to regular TLS, there are additional steps in mTLS to verify both parties.</p><ol><li>Client connects to server</li><li>Server presents its TLS certificate</li><li>Client verifies the server’s certificate</li><li><strong>Client presents its TLS certificate</strong></li><li><strong>Server verifies the client’s certificate</strong></li><li>Key Exchange (ECDHE), both sides perform Diffie-Hellman key<strong> </strong>exchange to derive the same shared secret</li><li>Client and server exchange information over encrypted TLS connection</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*iSjmTZy-BItLqMLc.png" /></figure><p>The organization implementing mTLS acts as its own certificate authority. This contrasts with standard TLS, in which the certificate authority is an external organization that checks if the certificate owner legitimately owns the associated domain.</p><p>A “root” TLS certificate is necessary for mTLS; this enables an organization to be their own certificate authority. The certificates used by authorized clients and servers have to correspond to this root certificate. The root certificate is self-signed, meaning that the organization creates it themselves.</p><h3>Step-by-Step Implementing mTLS in NestJS Microservices with gRPC</h3><h4>Init Both Services</h4><pre>nest new user-service<br>nest new order-service</pre><p>Choose:</p><ul><li>Package manager: npm</li><li>Skip testing if you want a clean base</li></ul><p>then install Packages &amp; Setup Auto Proto Generation</p><pre>npm install @nestjs/microservices @grpc/grpc-js @grpc/proto-loader<br>npm install --save-dev @protobuf-ts/protoc @protobuf-ts/plugin</pre><p>and adding this script in package.json</p><pre>&quot;scripts&quot;: {<br>  &quot;proto:gen&quot;: &quot;protoc \<br>    --plugin=protoc-gen-ts=$(pwd)/node_modules/.bin/protoc-gen-ts \<br>    --ts_out=src/generated \<br>    --proto_path=proto \<br>    proto/*.proto&quot;<br>}</pre><p>For user-service/main.ts, it supposes to serve both HTTP and gRPC request:</p><pre>import { NestFactory } from &#39;@nestjs/core&#39;;<br>import { AppModule } from &#39;./app.module&#39;;<br>import { MicroserviceOptions, Transport } from &#39;@nestjs/microservices&#39;;<br>import { join } from &#39;path&#39;;<br>import * as fs from &#39;fs&#39;;<br>import * as grpc from &#39;@grpc/grpc-js&#39;;<br><br>async function bootstrap() {<br>  const app = await NestFactory.create(AppModule);<br><br>  const ca = fs.readFileSync(join(__dirname, &#39;../certs/ca.crt&#39;));<br>  const cert = fs.readFileSync(join(__dirname, &#39;../certs/user.crt&#39;));<br>  const key = fs.readFileSync(join(__dirname, &#39;../certs/user.key&#39;));<br><br>  // gRPC server for user.proto<br>  app.connectMicroservice&lt;MicroserviceOptions&gt;({<br>    transport: Transport.GRPC,<br>    options: {<br>      url: &#39;0.0.0.0:50051&#39;,<br>      package: &#39;user&#39;,<br>      protoPath: join(__dirname, &#39;../proto/user.proto&#39;),<br>      credentials: grpc.ServerCredentials.createSsl(<br>        ca, // root CA<br>        [<br>          {<br>            cert_chain: cert,<br>            private_key: key,<br>          },<br>        ],<br>        true, // require client certificate<br>      ),<br>    },<br>  });<br><br>  await app.startAllMicroservices(); // 👈 start gRPC<br>  await app.listen(3000); // 👈 start HTTP<br>  console.log(<br>    &#39;🚀 user-service running on http://localhost:3000 and gRPC on :50051&#39;,<br>  );<br>}<br>bootstrap();</pre><p>The same setting for order-service, except it does not listen for serving any HTTP API. Next, we register order client in user service app module:</p><pre>import { Module } from &#39;@nestjs/common&#39;;<br>import { ClientsModule, Transport } from &#39;@nestjs/microservices&#39;;<br>import { AppController } from &#39;./app.controller&#39;;<br>import { AppService } from &#39;./app.service&#39;;<br>import * as fs from &#39;fs&#39;;<br>import { join } from &#39;path&#39;;<br>import * as grpc from &#39;@grpc/grpc-js&#39;;<br><br>@Module({<br>  imports: [<br>    ClientsModule.register([<br>      {<br>        name: &#39;ORDER_PACKAGE&#39;,<br>        transport: Transport.GRPC,<br>        options: {<br>          url: &#39;order-service:50051&#39;,<br>          package: &#39;order&#39;,<br>          protoPath: join(__dirname, &#39;../proto/order.proto&#39;),<br>          credentials: grpc.credentials.createSsl(<br>            fs.readFileSync(join(__dirname, &#39;../certs/ca.crt&#39;)), // root CA<br>            fs.readFileSync(join(__dirname, &#39;../certs/user.key&#39;)), // client key<br>            fs.readFileSync(join(__dirname, &#39;../certs/user.crt&#39;)), // client cert<br>          ),<br>        },<br>      },<br>    ]),<br>  ],<br>  controllers: [AppController],<br>  providers: [AppService],<br>})<br>export class AppModule {}</pre><p>From now on, we can inject that client to any service that needs to communicate with the order service. That’s all we need to bring microservices to live. The rest of the implementation for controllers, services, or any app behaviours are up to you, <a href="https://docs.nestjs.com/microservices/grpc">or you can follow this Nest official document.</a></p><h4>Setup mTLS with Certs</h4><p>From project root:</p><pre>mkdir -p certs &amp;&amp; cd certs<br><br># 1. Generate CA<br>openssl genrsa -out ca.key 4096<br>openssl req -x509 -new -key ca.key -sha256 -days 365 -out ca.crt -subj &quot;/CN=ca.local&quot;<br><br># 2. order-service (with SAN = order-service)<br>openssl genrsa -out order.key 2048<br>openssl req -new -key order.key -out order.csr -subj &quot;/CN=order-service&quot;<br>openssl x509 -req -in order.csr -CA ca.crt -CAkey ca.key -CAcreateserial \<br>  -out order.crt -days 365 \<br>  -extfile &lt;(printf &quot;subjectAltName=DNS:order-service&quot;)<br><br># 3. user-service (with SAN = user-service)<br>openssl genrsa -out user.key 2048<br>openssl req -new -key user.key -out user.csr -subj &quot;/CN=user-service&quot;<br>openssl x509 -req -in user.csr -CA ca.crt -CAkey ca.key -CAcreateserial \<br>  -out user.crt -days 365 \<br>  -extfile &lt;(printf &quot;subjectAltName=DNS:user-service&quot;)</pre><p>Then copy certs into each service:</p><pre>cp ca.crt user.crt user.key user-service/certs/<br>cp ca.crt order.crt order.key order-service/certs/</pre><p>Private keys (*.key) and certificates (*.crt) <strong>must not be committed to Git</strong>. Doing so would expose your infrastructure to severe security risks, we’ll create environment secrets later on for storing them in Github and retrieving once the deployment happens.</p><h4>Creating Dockerfile &amp; Docker Compose</h4><p>Same structure for both user and order services but only exposes port 50051 for the later. And, we will treat the user-service as a gateway of handling client requests passing from Nginx web server.</p><pre>FROM node:18-alpine<br><br>WORKDIR /app<br>COPY package*.json ./<br>RUN npm install<br><br>COPY . .<br>RUN npm run proto:gen &amp;&amp; npm run build<br><br>EXPOSE 3000<br>EXPOSE 50051<br><br>CMD [&quot;node&quot;, &quot;dist/main&quot;]</pre><p>For local development, create docker-compose.yaml</p><pre>version: &quot;3.9&quot;<br>services:<br>  user-service:<br>    build: ./user-service<br>    ports:<br>      - &quot;3000:3000&quot; # HTTP (for NGINX / REST clients)<br>      - &quot;50052:50051&quot; # gRPC (for internal microservice comms)<br>    depends_on:<br>      - order-service<br><br>  order-service:<br>    build: ./order-service<br>    ports:<br>      - &quot;50051:50051&quot;<br><br>  nginx:<br>    image: nginx:latest<br>    ports:<br>      - &quot;80:80&quot;<br>    volumes:<br>      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro<br>    depends_on:<br>      - user-service</pre><p>But for the production, we will replace images configured in docker-compose file with the latest one built in the CD step, then create another one for prod:</p><pre>version: &quot;3.9&quot;<br><br>services:<br>  user-service:<br>    image: 123456789012.dkr.ecr.us-east-1.amazonaws.com/user-service:latest # 🔁 Will be replaced by GitHub Actions (sed)<br>    ports:<br>      - &quot;3000:3000&quot; # HTTP (for NGINX / REST clients)<br>      - &quot;50052:50051&quot; # gRPC (for internal microservice comms)<br>    restart: always<br>    environment:<br>      - NODE_ENV=production<br>    depends_on:<br>      - order-service<br><br>  order-service:<br>    image: 123456789012.dkr.ecr.us-east-1.amazonaws.com/order-service:latest # 🔁 Will be replaced by GitHub Actions (sed)<br>    ports:<br>      - &quot;50051:50051&quot;<br>    restart: always<br>    environment:<br>      - NODE_ENV=production<br><br>  nginx:<br>    image: nginx:latest<br>    ports:<br>      - &quot;80:80&quot;<br>    volumes:<br>      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro<br>    depends_on:<br>      - user-service</pre><h4>NGINX Reverse Proxy</h4><p>Simply passing all request to the user-service, from that, the user-service will internally talks to the order-service.</p><pre>events {}<br><br>http {<br>  upstream user_api {<br>    server user-service:3000;<br>  }<br><br>  server {<br>    listen 80;<br><br>    location / {<br>      proxy_pass http://user_api;<br>    }<br>  }<br>}</pre><h4>GitHub Actions: CI/CD with ECR + EC2</h4><p>In this configuration, we authenticate github with AWS by OIDC, then build images and push to ECR. Later on, login to EC2 with SSH and pull the newest images to EC2 instance and initialize docker container with docker-compose.prod.yaml file.</p><pre>name: Deploy Services to EC2 via ECR<br><br>on:<br>  push:<br>    branches: [dev]<br><br>jobs:<br>  deploy:<br>    name: Deploy to EC2 (Dev)<br>    runs-on: ubuntu-latest<br>    environment: dev<br><br>    permissions:<br>      id-token: write<br>      contents: read<br><br>    env:<br>      IMAGE_TAG: ${{ github.sha }}<br>      APP_DIR: /home/ec2-user<br><br>    steps:<br>      - name: Checkout Repository<br>        uses: actions/checkout@v4<br><br>      - name: Configure AWS OIDC Credentials<br>        uses: aws-actions/configure-aws-credentials@v2<br>        with:<br>          role-to-assume: ${{ secrets.ROLE_ARN }}<br>          aws-region: ${{ secrets.AWS_REGION }}<br><br>      - name: Log in to Amazon ECR<br>        run: |<br>          aws ecr get-login-password --region ${{ secrets.AWS_REGION }} | \<br>          docker login --username AWS --password-stdin \<br>          ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.${{ secrets.AWS_REGION }}.amazonaws.com<br><br>      - name: Inject mTLS Certs into user-service and order-service<br>        run: |<br>          mkdir -p user-service/certs order-service/certs<br><br>          echo &quot;${{ secrets.USER_CRT }}&quot; &gt; user-service/certs/user.crt<br>          echo &quot;${{ secrets.USER_KEY }}&quot; &gt; user-service/certs/user.key<br>          echo &quot;${{ secrets.CA_CRT }}&quot;   &gt; user-service/certs/ca.crt<br><br>          echo &quot;${{ secrets.ORDER_CRT }}&quot; &gt; order-service/certs/order.crt<br>          echo &quot;${{ secrets.ORDER_KEY }}&quot; &gt; order-service/certs/order.key<br>          echo &quot;${{ secrets.CA_CRT }}&quot;    &gt; order-service/certs/ca.crt<br><br>      - name: Build &amp; Push Docker Images<br>        run: |<br>          # USER SERVICE<br>          docker build -t user-service:${{ env.IMAGE_TAG }} ./user-service<br>          docker tag user-service:${{ env.IMAGE_TAG }} \<br>            ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.${{ secrets.AWS_REGION }}.amazonaws.com/${{ secrets.ECR_USER_REPO }}:${{ env.IMAGE_TAG }}<br>          docker push ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.${{ secrets.AWS_REGION }}.amazonaws.com/${{ secrets.ECR_USER_REPO }}:${{ env.IMAGE_TAG }}<br><br>          # ORDER SERVICE<br>          docker build -t order-service:${{ env.IMAGE_TAG }} ./order-service<br>          docker tag order-service:${{ env.IMAGE_TAG }} \<br>            ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.${{ secrets.AWS_REGION }}.amazonaws.com/${{ secrets.ECR_ORDER_REPO }}:${{ env.IMAGE_TAG }}<br>          docker push ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.${{ secrets.AWS_REGION }}.amazonaws.com/${{ secrets.ECR_ORDER_REPO }}:${{ env.IMAGE_TAG }}<br><br>      - name: 📦 Copy docker-compose and nginx config to EC2<br>        uses: appleboy/scp-action@v0.1.4<br>        with:<br>          host: ${{ secrets.EC2_HOST }}<br>          username: ec2-user<br>          key: ${{ secrets.EC2_SSH_KEY }}<br>          source: &quot;docker-compose.prod.yaml,nginx/nginx.conf&quot;<br>          target: ${{ env.APP_DIR }}<br>          debug: true<br>      <br>      - name: 🚀 SSH Deploy to EC2<br>        uses: appleboy/ssh-action@v1.0.0<br>        with:<br>          host: ${{ secrets.EC2_HOST }}<br>          username: ec2-user<br>          key: ${{ secrets.EC2_SSH_KEY }}<br>          script: |<br>            cd $APP_DIR<br><br>            # Update docker-compose.prod.yaml with the latest image tags<br>            sed -i &quot;s|image:.*user-service.*|image: ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.${{ secrets.AWS_REGION }}.amazonaws.com/${{ secrets.ECR_USER_REPO }}:${{ env.IMAGE_TAG }}|&quot; docker-compose.prod.yaml<br>            sed -i &quot;s|image:.*order-service.*|image: ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.${{ secrets.AWS_REGION }}.amazonaws.com/${{ secrets.ECR_ORDER_REPO }}:${{ env.IMAGE_TAG }}|&quot; docker-compose.prod.yaml<br><br>            # 🚀 Log in to ECR using EC2 IAM role<br>            aws ecr get-login-password --region ${{ secrets.AWS_REGION }} | \<br>              docker login --username AWS --password-stdin \<br>              ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.${{ secrets.AWS_REGION }}.amazonaws.com<br><br>            docker-compose -f docker-compose.prod.yaml pull<br>            docker-compose -f docker-compose.prod.yaml down<br>            docker-compose -f docker-compose.prod.yaml up -d</pre><p>Don’t forget to assign a role for your EC2 instance so that it can pull images from your ECR. Below is the recommended minimal ECR Pull Policy for EC2:</p><pre>{<br>  &quot;Version&quot;: &quot;2012-10-17&quot;,<br>  &quot;Statement&quot;: [<br>    {<br>      &quot;Effect&quot;: &quot;Allow&quot;,<br>      &quot;Action&quot;: [<br>        &quot;ecr:GetAuthorizationToken&quot;,<br>        &quot;ecr:BatchCheckLayerAvailability&quot;,<br>        &quot;ecr:GetDownloadUrlForLayer&quot;,<br>        &quot;ecr:BatchGetImage&quot;<br>      ],<br>      &quot;Resource&quot;: &quot;*&quot;<br>    }<br>  ]<br>}</pre><h3>Practical Demo: Running and Verifying Secure Communication</h3><p>After completing the deployment process, we verified that secure mTLS communication between services was working correctly.</p><p>To test the system:</p><ol><li><strong>Deployed</strong> both user-service and order-service to the EC2 instance using GitHub Actions and Docker Compose.</li><li><strong>Ensured mTLS certificates</strong> were injected properly into each service during the CI/CD pipeline.</li><li><strong>Verified logs</strong> confirmed mutual authentication between services on startup.</li></ol><h4>Postman Test</h4><p>We sent a request to the user-service endpoint using Postman:</p><pre>GET http://&lt;your-domain&gt;/user/1</pre><ul><li>The user-service made a secure gRPC call to order-service.</li><li>The order-service responded with the user&#39;s order history.</li><li>The response was returned to Postman successfully.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*jg8btH9A579emgxmgN4M6g.png" /></figure><p>If you try with an invalid certificate, for example, re-sign the order certificate by changing subjectAltName=DNS:localhost. Rebuild and test it with Postman, you’ll encounter the following error:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*mrkV1cDB_JjlGmSkidggUg.png" /></figure><h4>🛡️ Verified Security</h4><ul><li>All inter-service communication is encrypted and authenticated using <strong>mutual TLS</strong>.</li><li>Unauthorized services without valid certificates are <strong>rejected</strong> during the handshake.</li></ul><p>This confirms the setup is secure, resilient, and production-ready for handling internal service communication via gRPC and mTLS.</p><h3>Conclusion</h3><p>In this guide, we explored how to protect microservice communication using <strong>mutual TLS (mTLS)</strong> over <strong>gRPC</strong>. We covered:</p><ul><li>The need for <strong>trust</strong> in service-to-service communication</li><li>How <strong>TLS and mTLS</strong> work under the hood</li><li>Generating and managing certificates with <strong>OpenSSL</strong></li><li>Implementing a real-world setup using <strong>NestJS</strong>, <strong>Docker</strong>, and <strong>GitHub Actions</strong></li></ul><p>By enforcing mTLS, you’re not just encrypting traffic — you’re ensuring that <strong>only verified services</strong> can talk to each other. This protects against internal threats, spoofed services, and unauthorized access.</p><p>As your system grows, securing internal APIs with mTLS becomes just as important as securing public ones. It’s a small effort for a massive gain in trust and resilience.</p><blockquote>Start small, secure early — and scale with confidence.</blockquote><h3>Follow Me On Linkedin To Stay Connected</h3><p><a href="https://www.linkedin.com/in/ninh-kim-927571149/">https://www.linkedin.com/in/ninh-kim-927571149/</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=946b39333880" width="1" height="1" alt=""><hr><p><a href="https://medium.com/deno-the-complete-reference/strengthening-microservices-implementing-mtls-over-grpc-for-trusted-communication-946b39333880">Strengthening Microservices: Implementing mTLS over gRPC for Trusted Communication</a> was originally published in <a href="https://medium.com/deno-the-complete-reference">Tech Tonic</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Go: The fastest web framework in 2025]]></title>
            <link>https://medium.com/deno-the-complete-reference/go-the-fastest-web-framework-in-2025-dfa2ddfd09e9?source=rss----cca06f8d4889---4</link>
            <guid isPermaLink="false">https://medium.com/p/dfa2ddfd09e9</guid>
            <category><![CDATA[go]]></category>
            <dc:creator><![CDATA[Mayank C]]></dc:creator>
            <pubDate>Tue, 28 Jan 2025 20:45:09 GMT</pubDate>
            <atom:updated>2025-01-28T20:45:09.717Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*07gtxarj17zBxDbUCHsfBg.png" /></figure><p>After receiving the ground-breaking response for a similar <a href="https://medium.com/deno-the-complete-reference/go-the-fastest-web-framework-in-2024-dcda4f9e54e6">article</a> published in 2024, and catering to the requests for a follow-up, I’m writing a similar article in 2025. This is the exact same comparison, but executed on latest of everything (Go and frameworks).</p><p>In this article, we’ll run a hello world comparison using several popular go frameworks such as Gin, Fiber, Echo, HTTP router, chi, beego, and Iris. Yes, this one is a dummy case of hello world. If you’re looking for a more solid and real-world comparison, this article is not for you.</p><p>Let’s jump directly into to the article.</p><h3>Test setup</h3><p>All tests are executed on MacBook Pro M2 with 16G RAM. There are 8+4 CPU cores. The load tester is Bombardier (also written in Go). The Go version is v1.23.5 (latest at the time of writing).</p><p>The code for each application is as follows:</p><h4>Gin</h4><pre>package main<br><br>import (<br>  &quot;net/http&quot;<br>  &quot;github.com/gin-gonic/gin&quot;<br>)<br><br>func main() {<br>  r := gin.New()<br><br>  r.GET(&quot;/&quot;, func(c *gin.Context) {<br>    c.String(http.StatusOK, &quot;Hello world!&quot;)<br>  })<br><br>  r.Run(&quot;:3000&quot;)<br>}</pre><h4>Fiber</h4><pre>package main<br><br>import (<br>  &quot;github.com/gofiber/fiber/v2&quot;<br>)<br><br>func main() {<br>  app := fiber.New()<br>  port := &quot;:3000&quot;<br><br>  app.Get(&quot;/&quot;, func(c *fiber.Ctx) error {<br>    return c.SendString(&quot;Hello World!&quot;)<br>  })<br><br>  app.Listen(port)<br>}</pre><h4>Echo</h4><pre>package main<br><br>import (<br>  &quot;net/http&quot;<br>  &quot;github.com/labstack/echo/v4&quot;<br>)<br><br>func main() {<br>  e := echo.New()<br>  e.GET(&quot;/&quot;, func(c echo.Context) error {<br>    return c.String(http.StatusOK, &quot;Hello World!&quot;)<br>  })<br>  e.Start(&quot;:3000&quot;)<br>}</pre><h4>Iris</h4><pre>package main<br><br>import &quot;github.com/kataras/iris/v12&quot;<br><br>func main() {<br>  app := iris.New()<br><br>  app.Get(&quot;/&quot;, func(ctx iris.Context) {<br>    ctx.Text(&quot;Hello World!&quot;)<br>  })<br><br>  app.Listen(&quot;:3000&quot;)<br>}</pre><h4>Chi</h4><pre>package main<br><br>import (<br>  &quot;net/http&quot;<br>  &quot;github.com/go-chi/chi/v5&quot;<br>)<br><br>func main() {<br>  r := chi.NewRouter()<br>  r.Get(&quot;/&quot;, func(w http.ResponseWriter, r *http.Request) {<br>    w.Write([]byte(&quot;Hello World!&quot;))<br>  })<br>  http.ListenAndServe(&quot;:3000&quot;, r)<br>}</pre><h4>Http Router</h4><pre>package main<br><br>import (<br>  &quot;fmt&quot;<br>  &quot;net/http&quot;<br>  &quot;log&quot;<br>  &quot;github.com/julienschmidt/httprouter&quot;<br>)<br><br>func Hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {<br>  fmt.Fprintf(w, &quot;Hello World!&quot;)<br>}<br><br>func main() {<br>  router := httprouter.New()<br>  router.GET(&quot;/&quot;, Hello)<br><br>  log.Fatal(http.ListenAndServe(&quot;:3000&quot;, router))<br>}</pre><h4>Beego</h4><pre>package main<br><br>import web &quot;github.com/beego/beego/v2/server/web&quot;<br><br>type MainController struct {<br>  web.Controller<br>}<br><br>func (c *MainController) Get() {<br>  c.Ctx.WriteString(&quot;Hello world!&quot;)<br>}<br><br>func main() {<br>  web.Router(&quot;/&quot;, &amp;MainController{})<br>  web.Run()<br>}</pre><h3>Results</h3><p>A total of 10M requests are executed for 100 concurrent connections. Along with latency, I’ve also collected CPU and memory usage (as always the case). This gives a complete picture. Just having high RPS is not enough. We also need to know the cost of the RPS. A framework giving a high performance by hogging the CPU may not be useful for production applications.</p><p>The results in chart form are as follows:</p><h4>Time taken</h4><p><em>(Lesser is better)</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/1*xW3kcByXdVU4l-KKkhi-SQ.png" /></figure><h4>Requests per second</h4><p><em>(More is better)</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/1*febKcZ71niv-D4WG4QASoQ.png" /></figure><h4>Latencies</h4><p><em>(Lesser are better)</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/1*0COeN4-bGanYOAdxzVGoMw.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/1*fPsulgnW4nWL9CJtQ_gkkg.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/1*vjrXq_Islp_LU37ymEEetA.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/1*4xbfdTpvQF2pa9Iu2M7tHw.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/1*NSscq7u0GAubQ0G6caq8Nw.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/1*HjRsMWUwFSnQwBkz4z5SCQ.png" /></figure><h3>Resources</h3><p><em>(Lesser are better)</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/1*I9Q9vHZ-VEHlKGrXgetp0w.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/1*9OKMSHPQgA2m4NbKBeChKQ.png" /></figure><h3>Conclusion</h3><p>Numbers show that <strong>Echo</strong> is the best performer.</p><p>I also understand &amp; heard (many times) that <em>TechEmpower</em> benchmarks show fiber demonstrating the highest performance. These are my results, and they show Fiber to be lacking in performance. I won’t be changing my results because <em>TechEmpower</em> demonstrated different results in 2023.</p><p>Thanks for reading!</p><p>If you see any missing framework, please let me know.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=dfa2ddfd09e9" width="1" height="1" alt=""><hr><p><a href="https://medium.com/deno-the-complete-reference/go-the-fastest-web-framework-in-2025-dfa2ddfd09e9">Go: The fastest web framework in 2025</a> was originally published in <a href="https://medium.com/deno-the-complete-reference">Tech Tonic</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>