<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Debod — Tech, dev &amp; more, explained simply</title>
    <link>https://debod.id/en/</link>
    <description>Straightforward guides, comparisons and tutorials on technology, development, gaming and general topics.</description>
    <language>en</language>
    <lastBuildDate>Thu, 03 Sep 2026 17:19:49 GMT</lastBuildDate>
    <image>
      <url>https://debod.id/logo.png</url>
      <title>Debod — Tech, dev &amp; more, explained simply</title>
      <link>https://debod.id/en/</link>
      <width>1024</width>
      <height>1024</height>
    </image>
    <atom:link href="https://debod.id/en/feed.xml" rel="self" type="application/rss+xml"/>
    <generator>debod.id — static Next.js export</generator>
    <item>
      <title>What is a SQL query? How SELECT, JOIN and aggregation work</title>
      <link>https://debod.id/en/posts/sql-queries-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/sql-queries-explained/</guid>
      <pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate>
      <category>database</category>
      <category>sql</category>
      <category>database</category>
      <category>backend</category>
      <description>A SQL query is a declarative statement that asks a relational database for data. This guide explains the SELECT statement, WHERE filtering, JOIN across tables, GROUP BY aggregation, and how to read a query plan.</description>
      <content:encoded><![CDATA[<section><p>A <strong>SQL query</strong> is a declarative statement you send to a relational database to ask for data. You describe <em>what</em> you want — a result set of rows matching some criteria — and the database engine decides <em>how</em> to compute it. The most common query is the <code>SELECT</code> statement: it has no persistent effect on the data and is read-only, which is why it is the workhorse of analytics, reporting, dashboards, and almost every application&#39;s data layer. The same database that runs your queries also stores them; the language is &quot;Structured Query Language&quot; (pronounced &quot;ess-cue-ell&quot; or &quot;sequel&quot;) and the standard that defines it is ISO/IEC 9075, last revised in 2023.</p>
<p>SQL was developed at IBM by Donald D. Chamberlin and Raymond F. Boyce in the early 1970s, after they learned about Edgar F. Codd&#39;s relational model from his 1970 paper, &quot;A Relational Model of Data for Large Shared Data Banks.&quot; The first version, originally called SEQUEL, was designed to manipulate and retrieve data from IBM&#39;s System R prototype. The language became an ANSI standard in 1986 and an ISO standard in 1987, and has been revised in 1989, 1992, 1996, 1999, 2003, 2006, 2008, 2011, 2016, and most recently 2023. Despite those standards, virtually no implementation adheres to the full specification — most SQL code requires at least small changes before being ported between database systems, which is why cross-database libraries (ORMs, query builders) remain popular.</p></section>
<section><h2 id="what-a-sql-query-does"><a href="#what-a-sql-query-does" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What a SQL query does</h2><p>A query reads from one or more tables and returns a result set of rows and columns. It does not change the data; that is the job of <code>INSERT</code>, <code>UPDATE</code>, and <code>DELETE</code>. A query can be nested inside another query (a <em>subquery</em>) and can reference a previous query in the same <code>FROM</code> clause (a <em>derived table</em> or <em>inline view</em>). Since SQL:1999 you can also name a subquery with a <code>WITH</code> clause — a <em>common table expression</em> (CTE) — and CTEs can reference themselves recursively, which is how SQL implements tree and graph traversals.</p>
<p>Three properties distinguish a query from a procedure:</p>
<ul>
<li><strong>Declarative</strong>: you describe the desired result, not the steps to produce it. The database&#39;s <em>query optimizer</em> chooses a plan.</li>
<li><strong>Set-based</strong>: the result is a set (or multi-set) of rows, not a stream of records to be processed one at a time. The same row may appear more than once unless you ask for <code>DISTINCT</code>.</li>
<li><strong>Closed under composition</strong>: the output of a query is itself a table, so you can pipe one query into another, nest them, or join the result of one query against a real table.</li>
</ul>
</section>
<section><h2 id="the-select-statement"><a href="#the-select-statement" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The SELECT statement</h2><p>The <code>SELECT</code> statement has several optional clauses. The full syntax (in the order they are evaluated) is:</p>
<pre><code class="language-sql">SELECT [DISTINCT] select_list
FROM   table_source
[WHERE predicate]
[GROUP BY columns]
[HAVING group_predicate]
[ORDER BY columns [ASC | DESC]]
[LIMIT n | FETCH FIRST n ROWS ONLY];
</code></pre>
<p>Each clause has a distinct job:</p>
<ul>
<li><strong><code>SELECT</code></strong> — the columns or expressions to return. <code>SELECT *</code> returns every column. <code>SELECT count(*)</code> is an aggregate expression, not a column reference.</li>
<li><strong><code>FROM</code></strong> — the table or tables the data comes from. With more than one table and no join condition, you get a Cartesian product.</li>
<li><strong><code>WHERE</code></strong> — filters rows before grouping. The predicate is evaluated for each row, and only rows where it is <code>TRUE</code> survive.</li>
<li><strong><code>GROUP BY</code></strong> — collapses rows that share the same values in the listed columns into a single group. Combined with aggregate functions, this is how you compute sums, counts, and averages per category.</li>
<li><strong><code>HAVING</code></strong> — filters groups after <code>GROUP BY</code>. Aggregate functions are allowed here but not in <code>WHERE</code>.</li>
<li><strong><code>ORDER BY</code></strong> — sorts the result. Without it, row order is undefined.</li>
<li><strong><code>LIMIT</code> / <code>FETCH FIRST</code></strong> — caps the number of rows returned. <code>LIMIT</code> is non-standard (MySQL, PostgreSQL, SQLite); <code>FETCH FIRST n ROWS ONLY</code> is the SQL:2008 standard, supported by DB2, PostgreSQL, SQL Server 2012+, Oracle 12c, and H2.</li>
</ul>
<p>A simple example, adapted from the Wikipedia SELECT page:</p>
<pre><code class="language-sql">SELECT title, price, price * 0.06 AS sales_tax
FROM   Book
WHERE  price &gt; 100.00
ORDER BY title;
</code></pre>
<p>The expression <code>price * 0.06 AS sales_tax</code> projects a computed column — a value the database evaluates on the fly. The alias <code>sales_tax</code> gives it a name in the result set.</p>
</section>
<section><h2 id="filtering-with-where"><a href="#filtering-with-where" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Filtering with WHERE</h2><p><code>WHERE</code> is applied row by row before any grouping. The predicate uses three-valued logic: a comparison can be <code>TRUE</code>, <code>FALSE</code>, or <code>UNKNOWN</code> (when either side is <code>NULL</code>). Rows that are not <code>TRUE</code> are dropped, which means <code>NULL</code> values silently disappear from comparisons. To find rows where a column is or is not null, you use <code>IS NULL</code> and <code>IS NOT NULL</code> — <code>= NULL</code> is never true.</p>
<p>A filter can combine conditions with <code>AND</code>, <code>OR</code>, and <code>NOT</code>, and it can use pattern matching (<code>LIKE</code>), set membership (<code>IN</code>), and range checks (<code>BETWEEN</code>). All of these are evaluated by the same engine and typically use an index when one is available; the role of a <em>database index</em> is to let the engine find matching rows without scanning the entire table.</p>
</section>
<section><h2 id="joining-tables"><a href="#joining-tables" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Joining tables</h2><p>A <code>JOIN</code> clause combines columns from two or more tables into a single result set. It corresponds to a join operation in relational algebra: the engine matches rows from each table according to a predicate, then stitches the matched columns together. The standard join types are:</p>
<table>
<thead>
<tr>
<th>Type</th>
<th>What it returns</th>
</tr>
</thead>
<tbody><tr>
<td><code>INNER JOIN</code></td>
<td>Rows that have a match in both tables. Williams (no department) and Marketing (no employees) disappear.</td>
</tr>
<tr>
<td><code>LEFT OUTER JOIN</code></td>
<td>All rows from the left table, plus matching columns from the right where they exist; right-table columns are <code>NULL</code> when no match is found. Williams appears with a <code>NULL</code> department name.</td>
</tr>
<tr>
<td><code>RIGHT OUTER JOIN</code></td>
<td>Mirror of <code>LEFT</code>. All rows from the right table, with left columns <code>NULL</code> when no match. Marketing appears with no employees.</td>
</tr>
<tr>
<td><code>FULL OUTER JOIN</code></td>
<td>All rows from both tables; unmatched columns are <code>NULL</code> on whichever side is missing. Both Williams and Marketing appear.</td>
</tr>
<tr>
<td><code>CROSS JOIN</code></td>
<td>The Cartesian product — every row of the left combined with every row of the right. Six employees times four departments = 24 rows.</td>
</tr>
</tbody></table>
<p>Using the canonical example from the Wikipedia JOIN page, with employees and departments:</p>
<pre><code class="language-sql">SELECT e.LastName, e.DepartmentID, d.DepartmentName
FROM   employee e
INNER JOIN department d ON e.DepartmentID = d.DepartmentID;
</code></pre>
<p>This returns every employee with their department name. Williams, who has a <code>NULL</code> <code>DepartmentID</code>, is excluded because <code>NULL</code> never matches anything (not even <code>NULL</code> itself). The <code>USING</code> shorthand is available when the join columns share a name and you do not need to qualify them:</p>
<pre><code class="language-sql">SELECT * FROM employee INNER JOIN department USING (DepartmentID);
</code></pre>
<p><code>USING</code> is not supported by SQL Server or Sybase. <code>NATURAL JOIN</code> is a stricter version that auto-matches every column with the same name — concise but fragile, because adding a column to either table silently changes the join.</p>
<p>A <em>self-join</em> joins a table to itself, which is the standard way to model hierarchies (employees and their managers, threads and their parent posts) in a flat schema.</p>
</section>
<section><h2 id="aggregating-with-group-by"><a href="#aggregating-with-group-by" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Aggregating with GROUP BY</h2><p><code>GROUP BY</code> collapses rows that share the same value in the listed columns, then lets you apply aggregate functions — <code>count(*)</code>, <code>sum(amount)</code>, <code>avg(price)</code>, <code>min(date)</code>, <code>max(score)</code> — to each group. The classic example, again from the SELECT page, counts authors per book:</p>
<pre><code class="language-sql">SELECT b.title, count(*) AS Authors
FROM   Book b
JOIN   Book_author ba ON b.isbn = ba.isbn
GROUP BY b.title;
</code></pre>
<p>The order of evaluation matters: <code>WHERE</code> runs first (row filter), then <code>GROUP BY</code> (collapse), then aggregate functions compute one value per group, then <code>HAVING</code> (group filter), then <code>SELECT</code> (project), then <code>ORDER BY</code>, then <code>LIMIT</code>.</p>
<p>To filter on the result of an aggregate, use <code>HAVING</code>, not <code>WHERE</code>. <code>WHERE count(*) &gt; 5</code> is a syntax error in standard SQL because the aggregate does not exist yet when <code>WHERE</code> is evaluated. The correct pattern:</p>
<pre><code class="language-sql">SELECT g.GroupName, count(*) AS NumberOfMembers
FROM   users u
INNER JOIN groups g ON g.Userid = u.Userid
GROUP BY GroupName
HAVING count(*) &gt; 5;
</code></pre>
<p>The ISO standard evaluation order for a <code>SELECT</code> is, in sequence: evaluate <code>FROM</code> (build a virtual table from the Cartesian product and the <code>ON</code> clause), apply <code>WHERE</code>, apply <code>GROUP BY</code>, apply <code>HAVING</code>, evaluate the <code>SELECT</code> list, apply <code>DISTINCT</code>, then apply <code>ORDER BY</code>. Modern optimizers may reorder operations internally, but the result must match this sequence.</p>
</section>
<section><h2 id="subqueries-and-common-table-expressions"><a href="#subqueries-and-common-table-expressions" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Subqueries and common table expressions</h2><p>A <em>subquery</em> is a query nested inside another. The most common placement is in the <code>WHERE</code> clause, where it can feed a value to a comparison:</p>
<pre><code class="language-sql">SELECT isbn, title, price
FROM   Book
WHERE  price &lt; (SELECT AVG(price) FROM Book)
ORDER BY title;
</code></pre>
<p>A <em>correlated subquery</em> references a column from the outer query and is re-evaluated for each outer row. Correlated subqueries are expressive but often slow; a <code>JOIN</code> with the same intent is usually faster.</p>
<p>A <em>common table expression</em> (CTE) names a subquery so it can be referenced more than once, which is essential for recursive queries:</p>
<pre><code class="language-sql">WITH RECURSIVE subordinates AS (
  SELECT id, name, manager_id FROM employees WHERE id = 1
  UNION ALL
  SELECT e.id, e.name, e.manager_id
  FROM   employees e
  INNER JOIN subordinates s ON e.manager_id = s.id
)
SELECT * FROM subordinates;
</code></pre>
<p>This walks the employee hierarchy starting from id 1 and is a common pattern for organizational trees, threaded comments, and graph data stored in tables.</p>
</section>
<section><h2 id="how-a-query-is-executed"><a href="#how-a-query-is-executed" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How a query is executed</h2><p>When you submit a query, the database parses the SQL, validates it against the schema, and hands the result to a <em>query optimizer</em>. The optimizer&#39;s job is to pick a <em>query plan</em> — the actual sequence of operations the engine will run — that produces the correct result as cheaply as possible. The optimizer has two main degrees of freedom:</p>
<ul>
<li><strong>Join order</strong>. Because joins are commutative and associative, the mathematical result is the same no matter which order the engine joins tables. The cost, however, can differ by orders of magnitude. A bad plan on a 1M-row table joined to a 1M-row table can take hours; the right plan finishes in milliseconds. Optimizers estimate intermediate row counts using table and index statistics, then choose the cheapest plan.</li>
<li><strong>Join algorithm</strong>. Three fundamental algorithms exist: <em>nested loop join</em> (good when one side is small or has an index on the join key), <em>sort-merge join</em> (good when both sides are already sorted on the join key), and <em>hash join</em> (good for large unsorted inputs that fit in memory). Modern databases may also use <em>bitmap join indexes</em> (Teradata, Oracle) for low-cardinality columns.</li>
</ul>
<p>The actual operations a database performs — sequential scans, index lookups, sorts, hash aggregations, parallel workers — are recorded in an <code>EXPLAIN</code> or <code>EXPLAIN ANALYZE</code> plan, which is the first thing to look at when a query is slow.</p>
</section>
<section><h2 id="common-sql-pitfalls"><a href="#common-sql-pitfalls" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Common SQL pitfalls</h2><p>A few mistakes that catch almost everyone:</p>
<ul>
<li><strong>Forgetting that <code>NULL</code> never matches.</strong> <code>WHERE DepartmentID = NULL</code> returns zero rows; you need <code>WHERE DepartmentID IS NULL</code>.</li>
<li><strong>Confusing <code>WHERE</code> and <code>HAVING</code>.</strong> <code>WHERE</code> runs before grouping and cannot reference aggregates; <code>HAVING</code> runs after grouping and almost always does.</li>
<li><strong>Implicit cross joins.</strong> Listing several tables in <code>FROM</code> separated by commas without a <code>WHERE</code> clause produces a Cartesian product, which is rarely what you want and can be catastrophically expensive.</li>
<li><strong>Non-deterministic order without <code>ORDER BY</code>.</strong> Without an explicit sort, the database is free to return rows in any order, and that order can change between runs.</li>
<li><strong>Vendor-specific syntax.</strong> <code>LIMIT 10</code> (MySQL/PostgreSQL/SQLite), <code>FETCH FIRST 10 ROWS ONLY</code> (SQL:2008, DB2/PostgreSQL/SQL Server 2012+/Oracle 12c), <code>TOP 10</code> (SQL Server, Access), and <code>WHERE ROWNUM &lt;= 10</code> (Oracle) all do the same thing, but the syntax differs. Writing portable SQL means preferring <code>FETCH FIRST</code>.</li>
<li><strong>Subtle inner-join NULLs.</strong> As the JOIN page notes, inner joins on columns that may contain <code>NULL</code> silently drop rows. For nullable foreign-key columns, a left outer join is usually safer.</li>
</ul>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-is-a-sql-query">What is a SQL query?</h3><p>A SQL query is a declarative statement sent to a relational database that asks for a result set of rows and columns, typically written with the <code>SELECT</code> keyword. The statement describes what data you want, not how to retrieve it; the database engine chooses the execution plan. Queries are read-only and have no persistent effect on the data.</p>
<h3 id="what-is-the-difference-between-where-and-having">What is the difference between WHERE and HAVING?</h3><p><code>WHERE</code> filters individual rows <em>before</em> grouping and cannot reference aggregate functions. <code>HAVING</code> filters <em>groups</em> produced by <code>GROUP BY</code> and is the only place aggregates can appear in a filter clause. Use <code>WHERE</code> for column conditions like <code>price &gt; 100</code>, and <code>HAVING</code> for conditions on the result of an aggregate like <code>count(*) &gt; 5</code>.</p>
<h3 id="what-is-the-difference-between-inner-join-and-left-join">What is the difference between INNER JOIN and LEFT JOIN?</h3><p><code>INNER JOIN</code> returns only rows where the join condition matches in both tables. <code>LEFT OUTER JOIN</code> returns all rows from the left table, with the right table&#39;s columns populated when a match exists and <code>NULL</code> when it does not. The rule of thumb: if you might be missing matching rows on the right side but still want every left-side row, use <code>LEFT JOIN</code>. If unmatched rows are bugs, use <code>INNER JOIN</code>.</p>
<h3 id="how-does-group-by-work">How does GROUP BY work?</h3><p><code>GROUP BY</code> collapses rows that share the same value in the listed columns into a single group, then lets aggregate functions like <code>count(*)</code>, <code>sum(amount)</code>, and <code>avg(price)</code> compute one value per group. The collapsed rows are no longer individually addressable in the result; only the grouping columns and the aggregate values appear. Filter on the aggregate with <code>HAVING</code>, not <code>WHERE</code>.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>What is a relational database? How relational databases work</title>
      <link>https://debod.id/en/posts/relational-databases-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/relational-databases-explained/</guid>
      <pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate>
      <category>database</category>
      <category>database</category>
      <category>sql</category>
      <description>A relational database stores data in tables of rows and columns connected by keys, using SQL to query and manage it. This guide covers the relational model, normalization, transactions, and when to choose an RDBMS.</description>
      <content:encoded><![CDATA[<section><p>A relational database is a database that organizes data into one or more tables (also called relations) of rows and columns, where each row is a record with a unique key and each column is a field with a defined type. Relationships between tables are expressed by referencing keys, and the dominant language for querying and modifying the data is SQL. The relational model was proposed by Edgar F. Codd in 1970, and most operational databases in use today still follow it.</p></section>
<section><h2 id="what-is-a-relational-database"><a href="#what-is-a-relational-database" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What is a relational database?</h2><p>A relational database is a database whose logical schema is based on the relational model introduced by Edgar F. Codd in his 1970 paper &quot;A Relational Model of Data for Large Shared Data Banks&quot;. In this model:</p>
<ul>
<li>A <strong>table</strong> (relation) is a set of rows that share the same structure.</li>
<li>A <strong>row</strong> (tuple) is one record, a single instance of what the table describes.</li>
<li>A <strong>column</strong> (attribute) is a named field with a defined data type (integer, text, date, etc.).</li>
</ul>
<p>Every row in a relation must be unique, and uniqueness is enforced by a <strong>primary key</strong> — one or more columns whose values never repeat inside the table. References to rows in other tables are stored as <strong>foreign keys</strong>, and referential integrity rules keep those references valid.</p>
<p>The language used to define, query, and manipulate these tables is SQL (Structured Query Language). SQL is declarative: a query states what data to retrieve, and the database engine decides how to retrieve it. The key distinction from earlier file systems and from many NoSQL stores is that the relational model enforces a <strong>structured schema before data is inserted</strong>. Types, constraints, and keys are declared up front, and the database rejects writes that violate them.</p>
</section>
<section><h2 id="how-tables-and-relationships-work"><a href="#how-tables-and-relationships-work" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How tables and relationships work</h2><p>Three kinds of relationships can exist between tables:</p>
<ul>
<li><strong>One-to-one</strong> — each row in table A corresponds to at most one row in table B (for example, a <code>users</code> table and a <code>users_private</code> table that holds sensitive data split off for access control).</li>
<li><strong>One-to-many</strong> — each row in table A can relate to many rows in table B, but each row in B relates to only one row in A. The classic example is a <code>customers</code> table and an <code>orders</code> table: one customer can place many orders, but each order belongs to one customer.</li>
<li><strong>Many-to-many</strong> — rows in A can relate to many rows in B and vice versa. Students enroll in many courses; each course has many students. Many-to-many relationships are implemented with a third <strong>junction table</strong> whose rows pair primary keys from both sides.</li>
</ul>
<p>The mechanics look like this:</p>
<pre><code class="language-sql">CREATE TABLE customers (
  id    INTEGER PRIMARY KEY,
  name  TEXT NOT NULL,
  email TEXT UNIQUE
);

CREATE TABLE orders (
  id         INTEGER PRIMARY KEY,
  customer_id INTEGER REFERENCES customers(id),
  amount     NUMERIC(10,2) NOT NULL,
  created_at TIMESTAMP NOT NULL
);

-- Insert a customer
INSERT INTO customers (id, name, email) VALUES (1, &#39;Alice&#39;, &#39;alice@example.com&#39;);

-- Insert an order that references that customer
INSERT INTO orders (id, customer_id, amount, created_at)
VALUES (100, 1, 49.99, &#39;2026-09-04 10:00:00&#39;);

-- Retrieve each order with the customer&#39;s name
SELECT o.id, c.name, o.amount, o.created_at
FROM   orders o
JOIN   customers c ON c.id = o.customer_id
WHERE  c.email = &#39;alice@example.com&#39;;
</code></pre>
<p>The <code>REFERENCES</code> clause declares the foreign key; the database engine refuses to insert an order whose <code>customer_id</code> does not already exist in <code>customers</code>. The <code>JOIN</code> clause combines rows from both tables based on the key match. Tools such as dbt build layered SQL models on top of an RDBMS like postgres, and cloud services like supabase expose a managed postgres instance with auth and APIs layered on top.</p>
</section>
<section><h2 id="normalization-reducing-redundancy"><a href="#normalization-reducing-redundancy" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Normalization: reducing redundancy</h2><p>Normalization is the process of restructuring a relational schema to minimize redundancy and avoid certain update anomalies. Edgar F. Codd defined a series of <strong>normal forms</strong>, each one stricter than the last:</p>
<ul>
<li><strong>First Normal Form (1NF)</strong> — every column holds an atomic (indivisible) value, there are no repeating groups, and each row is unique. A column that stores a comma-separated list of phone numbers violates 1NF; the fix is a separate <code>phone_numbers</code> table.</li>
<li><strong>Second Normal Form (2NF)</strong> — the table is in 1NF and every non-key column depends on the <em>whole</em> primary key, not just part of it. This matters only for tables with composite primary keys. A <code>(student_id, course_id) → instructor</code> dependency violates 2NF if <code>instructor</code> depends only on <code>course_id</code>.</li>
<li><strong>Third Normal Form (3NF)</strong> — the table is in 2NF and has no <strong>transitive dependencies</strong>: non-key columns do not depend on other non-key columns. If <code>course_id → department → department_head</code>, then <code>department_head</code> should move to a <code>departments</code> table.</li>
<li><strong>Boyce–Codd Normal Form (BCNF)</strong> — a stricter version of 3NF that handles the rare cases 3NF misses, where a non-key attribute determines part of a candidate key.</li>
</ul>
<p>The practical trade-off is between write-time integrity and read-time performance. A fully normalized schema removes duplication, so updating a value (a customer&#39;s address, say) happens in exactly one place. But heavy normalization means more joins at query time, which can be slow on large datasets. Many production systems therefore <strong>denormalize selectively</strong> — for example, storing a cached <code>total_orders</code> count on a customer row to avoid a <code>COUNT(*)</code> join on every page load — while keeping the rest of the schema normalized. Analytics warehouses built on platforms like snowflake typically denormalize further, using wide star or snowflake schemas for query speed.</p>
</section>
<section><h2 id="acid-transactions"><a href="#acid-transactions" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>ACID transactions</h2><p>A relational database is almost always paired with <strong>ACID transactions</strong>: the atomicity, consistency, isolation, and durability guarantees covered in detail in our <a href="/en/posts/database-transactions-explained">transactions guide</a>. In practice this means:</p>
<ul>
<li>A set of related writes either all happen or none of them do.</li>
<li>The database never leaves constraints in a broken state.</li>
<li>Concurrent transactions don&#39;t see each other&#39;s uncommitted changes.</li>
<li>Committed changes survive crashes and power loss.</li>
</ul>
<p>Consider a bank transfer:</p>
<pre><code class="language-sql">BEGIN;
  UPDATE accounts SET balance = balance - 100 WHERE id = 1;   -- debit
  UPDATE accounts SET balance = balance + 100 WHERE id = 2;   -- credit
COMMIT;
</code></pre>
<p>If the second <code>UPDATE</code> fails (network error, constraint violation, system crash), the <code>BEGIN/COMMIT</code> pair is not completed and the database engine rolls the whole transaction back. Account 1 is not debited. This is the property that makes relational databases the default choice for finance, e-commerce inventory, and any system where partial writes are unacceptable.</p>
</section>
<section><h2 id="sql-the-query-language"><a href="#sql-the-query-language" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>SQL: the query language</h2><p>SQL evolved from SEQUEL, a language developed by Donald D. Chamberlin and Raymond F. Boyce at IBM in the early 1970s for their System R prototype. The first commercially available RDBMS was <strong>Oracle V2</strong>, released in 1979. SQL was standardized by ANSI in 1986 and by ISO in 1987, and the standard has been revised several times since — most recently as <strong>SQL:2023</strong>.</p>
<p>Conceptually SQL is grounded in <strong>relational algebra</strong>, which defines a small set of operators on relations: selection (filter rows), projection (pick columns), join (combine tables by key), union (merge compatible rows), and so on. Real SQL also includes grouping, aggregation, and subqueries.</p>
<p>The four core data-manipulation statements are:</p>
<ul>
<li><code>SELECT</code> — retrieve rows from one or more tables.</li>
<li><code>INSERT</code> — add new rows.</li>
<li><code>UPDATE</code> — modify existing rows.</li>
<li><code>DELETE</code> — remove rows.</li>
</ul>
<p>Schema definition uses <code>CREATE TABLE</code>, <code>ALTER TABLE</code>, and <code>DROP TABLE</code>, plus constraint clauses such as <code>PRIMARY KEY</code>, <code>FOREIGN KEY</code>, <code>NOT NULL</code>, <code>UNIQUE</code>, and <code>CHECK</code>. SQL is the common surface across virtually all RDBMSs, including postgres, MySQL, SQLite, SQL Server, and Oracle.</p>
</section>
<section><h2 id="popular-relational-database-systems"><a href="#popular-relational-database-systems" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Popular relational database systems</h2><p>A few systems dominate the landscape:</p>
<ul>
<li><strong>PostgreSQL</strong> — open-source RDBMS widely considered the most feature-complete. Advanced types, indexing, JSON support, extensions, and strict standards conformance. Often run on its own, on top of a managed provider like supabase, or as the engine behind a cloud warehouse product.</li>
<li><strong>MySQL</strong> — the most widely deployed open-source RDBMS, especially in web stacks. Owned by Oracle; the MariaDB fork is also popular.</li>
<li><strong>SQLite</strong> — a serverless, embedded library. Not a separate process — the engine is linked into the application. The most widely deployed database engine in the world by install count, found in phones, browsers, and countless desktop apps.</li>
<li><strong>Microsoft SQL Server</strong> — a commercial RDBMS with deep Windows / Azure integration and a long history in enterprise IT.</li>
<li><strong>Oracle Database</strong> — the original commercial RDBMS lineage (from Oracle V2 in 1979); still dominant in large enterprise deployments.</li>
</ul>
<p>Most modern analytics and transformation tooling targets these engines. dbt compiles SQL into version-controlled models that run on any of them; snowflake is a cloud-native SQL data warehouse that separates storage from compute.</p>
</section>
<section><h2 id="when-to-use-a-relational-database"><a href="#when-to-use-a-relational-database" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>When to use a relational database</h2><p>A relational database is the right choice when:</p>
<ul>
<li>The data has a clear, stable structure with relationships between entities (users, orders, products, invoices, and the joins between them).</li>
<li>You need ACID transactions — money movement, inventory, anything where partial writes are unacceptable.</li>
<li>The workload needs complex joins across multiple tables.</li>
<li>Data integrity, defined constraints, and a single source of truth matter more than schema flexibility.</li>
</ul>
<p>NoSQL alternatives (document, key-value, graph, columnar) win when the data is unstructured, the workload is write-heavy at massive scale and can tolerate eventual consistency, or the access patterns don&#39;t fit a tabular model. In practice most production systems use both: a relational database for the system of record, plus specialized stores on the side.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-is-a-relational-database-1">What is a relational database?</h3><p>A relational database is a database that stores data in tables of rows and columns, enforces a predefined schema with typed columns, and uses primary and foreign keys to express relationships between tables. The standard language for working with these databases is SQL.</p>
<h3 id="what-is-normalization-in-a-relational-database">What is normalization in a relational database?</h3><p>Normalization is the process of restructuring tables so that each piece of data lives in exactly one place, which avoids update, insertion, and deletion anomalies. It is graded into normal forms — 1NF, 2NF, 3NF, BCNF — each with stricter rules about keys and dependencies.</p>
<h3 id="what-is-the-difference-between-sql-and-nosql">What is the difference between SQL and NoSQL?</h3><p>SQL databases (postgres, MySQL, Oracle, SQL Server, SQLite) use a structured tabular schema and SQL to query it, and typically provide ACID transactions. NoSQL databases (document, key-value, wide-column, graph) use flexible schemas, scale horizontally, and usually trade strict consistency for availability under the BASE model.</p>
<h3 id="when-should-you-use-a-relational-database">When should you use a relational database?</h3><p>Use a relational database when the data has a clear structure with relationships between entities, when ACID transactions are required, when you need joins across multiple tables, and when data integrity matters more than schema flexibility. For unstructured or rapidly changing data at very large scale, a NoSQL store is often a better fit.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>Object Storage Explained: How Object Storage Works and When to Use It</title>
      <link>https://debod.id/en/posts/object-storage-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/object-storage-explained/</guid>
      <pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate>
      <category>devops</category>
      <category>cloud</category>
      <category>devops</category>
      <description>Object storage is a data architecture that stores files as objects with metadata and a unique ID, instead of as files in folders or fixed-size blocks. Learn how S3 buckets work, how object storage compares to block and file storage, and when to choose it.</description>
      <content:encoded><![CDATA[<section><p>When you upload a photo to a cloud app, stream a video from a service like Netflix, or back up a database, the bytes are almost certainly ending up in object storage. Object storage has become the default architecture for cloud data, but the name obscures how different it is from the file system on your laptop or the disk attached to a server. This guide explains what makes object storage different, how services like Amazon S3 implement it, and when to choose it over block or file storage.</p></section>
<section><h2 id="what-is-object-storage"><a href="#what-is-object-storage" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What is object storage</h2><p>Object storage (also called object-based storage or blob storage) is a data storage architecture that manages data as &quot;blobs&quot; or &quot;objects&quot;, as opposed to other storage architectures like file systems, which manage data as a file hierarchy, and block storage, which manages data as blocks within sectors and tracks.</p>
<p>Each object is typically associated with a variable amount of metadata, and a globally unique identifier. Object storage can be implemented at multiple levels, including the device level (object-storage device), the system level, and the interface level. In each case, object storage seeks to enable capabilities not addressed by other storage architectures, like interfaces that are directly programmable by the application, a namespace that can span multiple instances of physical hardware, and data-management functions like data replication and data distribution at object-level granularity.</p>
<p>The term <em>blob</em> was coined by Jim Starkey while working at Digital Equipment Corporation, and was later adopted for the Rdb/VMS database. The humorous backronym &quot;binary large object&quot; was retroactively applied to it. The architectural research that shaped modern object storage was led by Garth Gibson at Carnegie Mellon in 1995, in a project called Network-Attached Secure Disks (NASD), which promoted the idea of splitting namespace operations from bulk reads and writes to optimize scale. Gibson later co-founded Panasas to commercialize these ideas, and one of his collaborators, Howard Gobioff, went on to co-design the Google File System.</p>
</section>
<section><h2 id="how-objects-differ-from-files-and-blocks"><a href="#how-objects-differ-from-files-and-blocks" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How objects differ from files and blocks</h2><p>A traditional file system organises data in a tree of folders and files. Each file has a path, a fixed set of metadata (name, size, timestamps, permissions), and a set of bytes. To find a file, you walk the tree. A block storage system breaks data into fixed-size blocks, each addressed by a logical block number, and reassembles them on read. Block storage is what an operating system mounts as a local disk.</p>
<p>An object is a different kind of unit. Instead of a path, each object has a unique identifier (often a URL-like key) and a rich, application-defined set of metadata attributes. The container that holds objects is usually called a <em>bucket</em> (in S3) or a <em>container</em> (in Azure Blob Storage). Within a bucket there is no folder hierarchy: all objects live in a flat namespace, and the application gives each object a key. The combination of a key, a value (the bytes), and a set of attributes is what object storage calls an &quot;object&quot;.</p>
<p>Object storage also allows the addressing and identification of individual objects by more than just file name and file path. Object storage adds a unique identifier within a bucket, or across the entire system, to support much larger namespaces and eliminate name collisions.</p>
</section>
<section><h2 id="rich-metadata-and-custom-attributes"><a href="#rich-metadata-and-custom-attributes" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Rich metadata and custom attributes</h2><p>File systems have a small, fixed set of metadata: filename, creation date, type, owner. Object storage explicitly separates file metadata from data to support additional capabilities. Object storage provides for full function, custom, object-level metadata, which can be used to:</p>
<ul>
<li>capture application-specific or user-specific information for better indexing</li>
<li>support data-management policies (for example, a policy to drive object movement from one storage tier to another)</li>
<li>centralise management across many nodes and clusters</li>
<li>optimise metadata storage (encapsulated, database, or key-value) and caching/indexing independently from the data storage</li>
</ul>
<p>This is the part that makes object storage useful for unstructured data: you can tag a video with its encoding, duration, and content rating; you can tag a backup with the database it came from and the time of the snapshot; and the storage system can use those tags to drive lifecycle policy without the application having to re-read the object.</p>
</section>
<section><h2 id="programmatic-access-through-rest-apis"><a href="#programmatic-access-through-rest-apis" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Programmatic access through REST APIs</h2><p>Object storage provides programmatic interfaces to allow applications to manipulate data. At the base level, this includes create, read, update, and delete (CRUD) functions for basic read, write, and delete operations. Some object storage implementations go further, supporting object versioning, object replication, life-cycle management, and movement of objects between different tiers and types of storage. Most API implementations are REST-based, allowing the use of many standard HTTP calls.</p>
<p>That is why the same HTTP verbs that power the web (GET, PUT, POST, DELETE) are also the operations you use to read, write, and delete objects. From an application&#39;s point of view, an object store is just an HTTP service.</p>
</section>
<section><h2 id="how-amazon-s3-implements-object-storage"><a href="#how-amazon-s3-implements-object-storage" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How Amazon S3 implements object storage</h2><p>Amazon S3 is the service that made object storage the default. Launched in the United States on March 14, 2006, and in Europe in November 2007, S3 is built on the same scalable storage infrastructure that runs Amazon&#39;s e-commerce network.</p>
<p>The basic storage units of Amazon S3 are objects which are organised into buckets. Each object is identified by a unique, user-assigned key. Buckets can be managed using the console, programmatically with the AWS SDK, or the REST API. Objects can be up to five terabytes in size. If an object is larger than 5 GB, it must be divided into chunks prior to uploading, and any object larger than 5 GB must be uploaded via the S3 multipart upload API.</p>
<p>Requests are authorised using an access control list associated with each object bucket, and S3 supports versioning, which is disabled by default. The AWS authentication mechanism also allows the creation of authenticated URLs valid for a specified amount of time, so a private object can be shared temporarily without making the bucket public.</p>
<p>S3 offers nine different storage classes with different levels of durability, availability, and performance requirements. The default is S3 Standard, for frequently accessed data. Other classes target infrequent access, archival, and lower-cost long-term retention. S3 can also be used to replace static web-hosting infrastructure: an S3 bucket can be configured with an index document and an error document, and every object can be served as a public HTTP URL. As of 2025, S3 stores 500 trillion objects across hundreds of exabytes and serves 200 million requests per second, peaking at about one petabyte per second in bandwidth.</p>
</section>
<section><h2 id="object-storage-vs-key-value-storage"><a href="#object-storage-vs-key-value-storage" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Object storage vs key-value storage</h2><p>The border between an object store and a key-value store is blurred, with key-value stores being sometimes loosely referred to as object stores. They are similar in two respects: the object identifier (the equivalent of a key) can be an arbitrary string, and the data can be of an arbitrary size.</p>
<p>The key differences are these. Object stores allow one to associate a limited set of attributes (metadata) with each piece of data; the combination of a key, value, and set of attributes is the object. Object stores are optimised for large amounts of data (hundreds of megabytes or even gigabytes), whereas key-value stores are typically used for relatively small values (kilobytes). Finally, object stores usually offer weaker consistency guarantees, such as eventual consistency, whereas key-value stores often offer strong consistency. This is the trade-off that lets object stores scale to trillions of objects across many regions.</p>
</section>
<section><h2 id="the-s3-compatible-ecosystem"><a href="#the-s3-compatible-ecosystem" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The S3-compatible ecosystem</h2><p>The broad adoption of Amazon S3 and related tooling has given rise to competing services based on the S3 API. These services use the standard programming interface but are differentiated by their underlying technologies and business models. A standard interface enables better competition from rival providers and allows economies of scale in implementation.</p>
<p>Several storage providers such as Cloudian, Backblaze B2, and Wasabi offer S3-compatible storage with options of on-premises and private cloud deployments. Several open-source implementations of S3-compatible object-store servers can be self-hosted, such as Garage. IBM Cloud Object Storage also offers a cloud-based S3-compatible option. Cloud providers like Google Cloud Storage (May 2010) and Microsoft Azure Blob Storage have object products of their own; the S3 API has effectively become the de facto interface that most clients are written against.</p>
</section>
<section><h2 id="where-object-storage-is-used"><a href="#where-object-storage-is-used" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Where object storage is used</h2><p>Object storage systems allow retention of massive amounts of unstructured data in which data is written once and read once (or many times). Object storage is used for purposes such as storing videos and photos, songs on streaming services, and files in online collaboration services. The vast majority of cloud storage available in the market leverages an object-storage architecture.</p>
<p>Concretely, object storage is the right choice when the workload is dominated by large, write-once-read-many (or read-many-times) files: media assets, backups, log archives, software binaries, data lakes, and the static files behind a web or mobile application. Cloud functions read configuration and templates from object storage; CDNs source content from object storage; data lake analytics query Parquet files in object storage; and machine learning training pipelines read datasets from object storage.</p>
</section>
<section><h2 id="when-object-storage-is-the-wrong-choice"><a href="#when-object-storage-is-the-wrong-choice" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>When object storage is the wrong choice</h2><p>One of the limitations with object storage is that it is not intended for transactional data, as object storage was not designed to replace NAS file access and sharing; it does not support the locking and sharing mechanisms needed to maintain a single, accurately updated version of a file. Editing a single byte in an object means re-uploading the whole object, and the object does not provide the random-access read/write pattern a database or a block device needs.</p>
<p>If your application needs random writes, low-latency reads of small records, or a shared filesystem with locking, you want a block device (for example, an EBS volume), a relational database, or a network file system, not an object store.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-is-the-difference-between-object-storage-and-a-file-system">What is the difference between object storage and a file system?</h3><p>A file system organises data in a hierarchy of folders and files, with a fixed set of metadata per file. Object storage organises data as flat objects in buckets; each object has a unique key, the bytes, and a rich set of application-defined metadata attributes, and there are no folders.</p>
<h3 id="is-amazon-s3-the-same-as-object-storage">Is Amazon S3 the same as object storage?</h3><p>Amazon S3 is one specific implementation of object storage, and the one that made the architecture mainstream. Many other services (Google Cloud Storage, Azure Blob Storage, Backblaze B2, Wasabi, Cloudflare R2, and self-hosted options like MinIO) implement the same general model and often expose the same S3 API.</p>
<h3 id="can-i-modify-part-of-an-object">Can I modify part of an object?</h3><p>No. Object storage treats each object as an opaque blob. To change any byte, the application has to upload a new object (typically with the same key) and replace the old one. This is the price of the architecture&#39;s simplicity and scale; it is also why databases, which need in-place updates, are built on top of block storage rather than object storage.</p>
<h3 id="how-large-can-an-object-be">How large can an object be?</h3><p>In Amazon S3, an object can be between 0 bytes and 5 TB. Objects larger than 5 GB must be uploaded using the multipart upload API. Other object services have their own limits, but the order of magnitude is the same.</p>
<h3 id="how-is-object-storage-different-from-block-storage">How is object storage different from block storage?</h3><p>Block storage breaks data into fixed-size blocks addressed by number and reassembles them on read; it is what an operating system mounts as a local disk and is the right choice for databases, boot volumes, and any workload that needs random read/write access. Object storage treats each file as an opaque object addressed by key; it is cheaper per gigabyte at scale, but you cannot edit part of an object in place.</p>
</section>
<section><h2 id="sources"><a href="#sources" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Sources</h2><ul>
<li>Wikipedia — Object storage (en.wikipedia.org/wiki/Object_storage, CC BY-SA 4.0)</li>
<li>Wikipedia — Amazon S3 (en.wikipedia.org/wiki/Amazon_S3, CC BY-SA 4.0)</li>
</ul>
</section>]]></content:encoded>
    </item>
    <item>
      <title>Microservice Architecture Explained — Decomposition, Communication, Data, and Trade-offs</title>
      <link>https://debod.id/en/posts/microservice-architecture-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/microservice-architecture-explained/</guid>
      <pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate>
      <category>architecture</category>
      <category>programming</category>
      <category>devops</category>
      <category>async</category>
      <description>Microservice architecture splits an application into small independent services. Learn how decomposition, inter-service communication, data management, and the microservice premium work.</description>
      <content:encoded><![CDATA[<section><p>When an application starts with five developers and one codebase, a monolithic architecture works fine. When it grows to fifty engineers, five teams, and a hundred thousand users, the monolith becomes a constraint. Microservice architecture offers an alternative: instead of one big deployable unit, you build many small ones, each owning a piece of the business, each scaling and failing independently. This guide explains the patterns that make decomposition work, how services actually communicate, what happens to your data, and when the trade-off is worth it.</p></section>
<section><h2 id="how-microservices-decompose-a-system"><a href="#how-microservices-decompose-a-system" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How microservices decompose a system</h2><p>The hardest part of microservices is not writing the services — it is deciding where the boundaries go. A bad boundary creates coupling between services that defeats the whole point; a good boundary isolates change and lets teams work independently.</p>
<h3 id="bounded-context-and-domain-driven-design">Bounded Context and Domain-Driven Design</h3><p>The primary tool for drawing boundaries is Domain-Driven Design&#39;s <em>Bounded Context</em> pattern. DDD recognizes that a large domain model cannot be unified across a whole organisation — different teams use different vocabulary, and forcing everyone onto one schema causes conflicts and slowdowns. A Bounded Context defines a clear boundary within which a single model, called a <em>Ubiquitous Language</em>, applies. Microservices typically map one-to-one onto a Bounded Context: the &quot;orders&quot; service owns the &quot;order&quot; model and nothing outside it can read or write that data directly.</p>
<p>Martin Fowler describes this as the key strategic insight of DDD: instead of one massive unified model, you build several smaller models, each coherent within its boundary, with explicit rules for how the boundaries relate to each other.</p>
<p>Wikipedia adds that this one-to-one mapping between Bounded Context and microservice is &quot;typically ideal as it maintains clear boundaries, reduces coupling, and enables independent deployment and scaling.&quot;</p>
<h3 id="the-strangler-fig-pattern">The Strangler Fig Pattern</h3><p>When you have a working monolith and want to migrate to microservices, the Strangler Fig pattern makes it gradual. Coined by Martin Fowler after the rainforest vine that grows around a host tree, it means: build new functionality as separate services, route traffic to them using an API gateway, and slowly migrate existing behaviour from the monolith until the monolith can be switched off. Microsoft Learn describes it as supporting &quot;incremental migration from a legacy system by gradually replacing specific pieces of functionality with new services. Consumers continue to use the same interface, unaware that the migration is taking place.&quot;</p>
<h3 id="granularity-finding-the-right-size">Granularity: finding the right size</h3><p>Microservices are fine-grained by definition, but how fine? Wikipedia warns that very fine-grained services are easier to test and deploy independently but suffer lower performance and reduced reliability due to more network hops. Coarse-grained services are more robust but harder to test in isolation. The right size depends on team structure, fault isolation needs, and scalability requirements — there is no universal formula.</p>
</section>
<section><h2 id="how-services-communicate"><a href="#how-services-communicate" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How services communicate</h2><h3 id="smart-endpoints-and-dumb-pipes">Smart endpoints and dumb pipes</h3><p>The microservice community follows the principle of <em>smart endpoints and dumb pipes</em>. Services own their domain logic and act as filters — they receive a request, apply logic, and produce a response. The infrastructure between them, whether an HTTP bus or a message queue, stays simple. Martin Fowler puts it simply: &quot;Applications built from microservices aim to be as decoupled and as cohesive as possible.&quot;</p>
<h3 id="synchronous-and-asynchronous-patterns">Synchronous and asynchronous patterns</h3><p>Wikipedia identifies the three main approaches: synchronous HTTP/REST, asynchronous messaging, and GraphQL. Synchronous calls are simple but couple services in time — if the callee is slow, the caller waits. Asynchronous messaging (RabbitMQ, Kafka, Cloudflare Queues) decouples in time: the producer sends a message and moves on; consumers pick it up when ready.</p>
<h3 id="resiliency-in-distributed-systems">Resiliency in distributed systems</h3><p>Microsoft Learn lists the three patterns every microservice developer must handle: <em>Retry</em> (a transient failure should be retried a few times before giving up), <em>Circuit Breaker</em> (&quot;too many failed requests can cause a bottleneck, as pending requests accumulate in the queue... The Circuit Breaker pattern can prevent a service from repeatedly trying an operation that is likely to fail&quot;), and <em>Distributed Tracing</em> (&quot;a single transaction might span multiple services. That can make it hard to monitor the overall performance and health of the system&quot;).</p>
</section>
<section><h2 id="data-in-a-microservices-world"><a href="#data-in-a-microservices-world" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Data in a microservices world</h2><h3 id="database-per-service">Database-per-service</h3><p>The defining data principle of microservices is that each service owns its own database. This lets every service choose the right data store for its workload — a search service might use Elasticsearch, an orders service PostgreSQL, a recommendation service Redis. Wikipedia summarises the benefit: services &quot;can be monitored and scaled independently&quot; because they run in independent processes.</p>
<h3 id="eventual-consistency">Eventual consistency</h3><p>When each service owns its own database, a business transaction that touches three services cannot use a single local transaction. Instead, the system tolerates temporary inconsistency. Wikipedia defines <em>eventual consistency</em> as a model &quot;used in distributed computing to achieve high availability. An eventually consistent system ensures that if no new updates are made to a given data item, eventually all read accesses to that item will return the last updated value.&quot;</p>
<p>The trade-off is expressed as BASE versus ACID. Wikipedia contrasts the two: &quot;Eventually-consistent services are often classified as providing BASE semantics (basically-available, soft-state, eventual consistency), in contrast to traditional ACID (atomicity, consistency, isolation, durability).&quot; Critics note that eventual consistency provides only a liveness guarantee without safety guarantees — any intermediate value can be read before convergence.</p>
<h3 id="the-saga-pattern">The Saga pattern</h3><p>When a business operation spans several services, you replace a distributed ACID transaction with a Saga. Microsoft Learn defines it precisely: &quot;The Saga pattern manages transactions by breaking them into a sequence of local transactions. Each local transaction: completes its work atomically within a single service; updates the service&#39;s database; initiates the next transaction via an event or message. If a local transaction fails, the saga performs a series of compensating transactions to reverse the changes.&quot;</p>
<p>Two approaches exist. <em>Choreography</em> distributes control — each service reacts to events from the previous one. <em>Orchestration</em> centralises control through a single orchestrator that tells each service what to do. Microsoft Learn notes that choreography works well for simple workflows (&quot;no single point of failure; responsibilities distributed&quot;) but becomes hard to follow for complex flows and makes integration testing difficult. Orchestration suits complex workflows better but introduces a central point of failure.</p>
</section>
<section><h2 id="common-challenges"><a href="#common-challenges" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Common challenges</h2><p>Microservices introduce complexity that does not exist in a monolith. Wikipedia categorises the main concerns:</p>
<ul>
<li><strong>Information barriers</strong> — services cannot query each other&#39;s data directly, so reports and analytics require additional data-sharing mechanisms.</li>
<li><strong>Network cost</strong> — &quot;Inter-service calls over a network have a higher cost in terms of network latency and message processing time than in-process calls within a monolithic service process.&quot;</li>
<li><strong>Testing complexity</strong> — testing one service in isolation is straightforward, but testing interactions between services requires contracts, mocks, or a full integration environment.</li>
<li><strong>Two-phase commit is an anti-pattern</strong> — &quot;Two-phased commits are regarded as an anti-pattern in microservices-based architectures, resulting in a tighter coupling of all the participants within the transaction.&quot; The alternative is Saga.</li>
<li><strong>Timeout antipattern</strong> — &quot;Short timeouts may fail legitimate requests prematurely, while long timeouts can result in slow error responses.&quot;</li>
</ul>
<p>Martin Fowler adds that the sheer number of services requires &quot;decentralized continuous delivery and DevOps with holistic service monitoring&quot; to operate effectively. Microservices also need a <em>Bulkhead</em> pattern — Microsoft Learn describes it as isolating &quot;critical resources, such as connection pools, memory, and CPU, for each workload or service. This isolation prevents a single workload or service from consuming all resources and prevents cascading failures.&quot;</p>
</section>
<section><h2 id="when-microservices-make-sense"><a href="#when-microservices-make-sense" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>When microservices make sense</h2><p>This is the most important question, and Martin Fowler&#39;s <em>Microservice Premium</em> gives the clearest guidance. His central thesis: &quot;The microservices approach is all about handling a complex system, but in order to do so the approach introduces its own set of complexities.&quot; The costs are real — automated deployment, monitoring, failure handling, eventual consistency.</p>
<p>His primary guideline: &quot;don&#39;t even consider microservices unless you have a system that&#39;s too complex to manage as a monolith. The majority of software systems should be built as a single monolithic application. Do pay attention to good modularity within that monolith, but don&#39;t try to separate it into separate services.&quot;</p>
<p>The complexity that drives teams toward microservices can come from large teams, multi-tenancy, many user interaction models, independent business-function evolution, or sheer size — a monolith that is &quot;too big to modify and deploy.&quot; Until you hit that wall, a well-modularised monolith is faster to build and easier to operate.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="how-does-microservice-architecture-differ-from-service-oriented-architecture-soa">How does microservice architecture differ from service-oriented architecture (SOA)?</h3><p>Microservices are a specific form of SOA that emphasises extreme decentralisation. Where traditional SOA often uses an enterprise service bus (ESB) as a smart intermediary, microservices prefer &quot;dumb pipes&quot; and smart endpoints. Microservices also favour independent deployment of each service and often different programming languages per service, whereas SOA tends toward more integration at the bus. The microservices movement grew partly as a reaction to heavyweight ESB-centric SOA.</p>
<h3 id="what-is-the-difference-between-microservices-and-a-modular-monolith">What is the difference between microservices and a modular monolith?</h3><p>A modular monolith is one deployable unit with enforced internal boundaries — modules or packages that separate concerns. A microservices architecture has the same separation but also runs each module as an independent process with its own database. The modular monolith is simpler to develop and deploy; the microservices architecture allows independent scaling and different technology stacks per module. If you can enforce module boundaries in a monolith, you can migrate to microservices later without rewriting the logic.</p>
<h3 id="how-do-you-handle-data-consistency-across-microservices">How do you handle data consistency across microservices?</h3><p>The pragmatic answer is eventual consistency via the Saga pattern or event-driven updates. Instead of a single ACID transaction, you accept that services will be briefly out of sync and use compensating transactions or event subscriptions to bring them back in sync. For read-heavy scenarios, CQRS (Command Query Responsibility Segregation) separates the write model from the read model, letting read services maintain their own query-optimised data copies without blocking writes.</p>
<h3 id="when-should-you-not-use-microservices">When should you not use microservices?</h3><p>Avoid microservices for simple CRUD applications, small teams, or early-stage products where you are still discovering the domain model. The overhead of managing many services — CI/CD pipelines, service discovery, distributed tracing, separate databases — is only worth it when the scale or team complexity genuinely requires it. Fowler&#39;s Microservice Premium is the test: if your monolith is not already causing problems, the migration cost will outweigh the benefits.</p>
</section>
<section><h2 id="sources"><a href="#sources" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Sources</h2><ul>
<li><a href="https://en.wikipedia.org/wiki/Microservices">Wikipedia — Microservices (CC BY-SA 4.0)</a></li>
<li><a href="https://martinfowler.com/articles/microservices.html">Martin Fowler — Microservices</a></li>
<li><a href="https://martinfowler.com/bliki/BoundedContext.html">Martin Fowler — Bounded Context</a></li>
<li><a href="https://martinfowler.com/bliki/StranglerFigApplication.html">Martin Fowler — Strangler Fig Application</a></li>
<li><a href="https://learn.microsoft.com/en-us/azure/architecture/patterns/saga">Microsoft Learn — Saga Pattern</a></li>
<li><a href="https://learn.microsoft.com/en-us/azure/architecture/microservices/design/patterns">Microsoft Learn — Microservice Design Patterns</a></li>
<li><a href="https://en.wikipedia.org/wiki/Eventual_consistency">Wikipedia — Eventual consistency (CC BY-SA 4.0)</a></li>
<li><a href="https://martinfowler.com/bliki/MicroservicePremium.html">Martin Fowler — Microservice Premium</a></li>
<li><a href="https://learn.microsoft.com/en-us/azure/architecture/microservices/interservice-communication">Microsoft Learn — Interservice Communication</a></li>
</ul>
</section>]]></content:encoded>
    </item>
    <item>
      <title>What is lazy loading? How deferring resources speeds up the web</title>
      <link>https://debod.id/en/posts/lazy-loading-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/lazy-loading-explained/</guid>
      <pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate>
      <category>performance</category>
      <category>performance</category>
      <category>web</category>
      <category>frontend</category>
      <description>Lazy loading is a performance strategy that defers non-critical resources (images, iframes, JavaScript, CSS) until they are needed. Learn what can be lazy-loaded, the native loading attribute, the Intersection Observer API, and when lazy loading helps or hurts.</description>
      <content:encoded><![CDATA[<section><p>Lazy loading is a performance strategy that defers the loading of non-critical resources until they are actually needed. The web has grown heavier over time: between 2011 and 2019, the median resource weight increased from about 100 KB to 400 KB on desktop and 50 KB to 350 KB on mobile, while image size grew from 250 KB to 900 KB on desktop. Lazy loading helps by shortening the critical rendering path: the browser only fetches what the first viewport needs, then loads the rest on demand. This guide explains what can be lazy-loaded, the native browser mechanisms, the JavaScript APIs that fill the gaps, and the trade-offs to watch for.</p></section>
<section><h2 id="what-can-be-lazy-loaded"><a href="#what-can-be-lazy-loaded" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What can be lazy-loaded</h2><p>Lazy loading applies to any resource that is non-critical for the first render. The MDN performance guide groups the candidates into several categories.</p>
<table>
<thead>
<tr>
<th>Resource type</th>
<th>Lazy-load mechanism</th>
<th>Typical use case</th>
</tr>
</thead>
<tbody><tr>
<td>JavaScript</td>
<td><code>type=&quot;module&quot;</code> (deferred by default), dynamic <code>import()</code></td>
<td>Code that runs only after a user action</td>
</tr>
<tr>
<td>CSS</td>
<td>Media queries (<code>@media print</code>), <code>preload</code> with <code>onload</code> swap</td>
<td>Print styles, non-critical theming</td>
</tr>
<tr>
<td>Fonts</td>
<td><code>font-display</code>, preloading, Font Loading API</td>
<td>Decorative webfonts that block initial text</td>
</tr>
<tr>
<td>Images</td>
<td><code>loading=&quot;lazy&quot;</code> attribute, Intersection Observer</td>
<td>Below-the-fold photos, galleries</td>
</tr>
<tr>
<td>iframes</td>
<td><code>loading=&quot;lazy&quot;</code> attribute</td>
<td>Offscreen video embeds, ads, social widgets</td>
</tr>
<tr>
<td>Video / audio</td>
<td><code>preload=&quot;none&quot;</code>, Intersection Observer</td>
<td>Players far down the page</td>
</tr>
</tbody></table>
<p>Each of these mechanisms shifts work off the critical path, but only when the resource is truly non-critical for the first paint. Lazy-loading the LCP image, for example, can delay the most important render and hurt perceived performance.</p>
</section>
<section><h2 id="the-native-loading-attribute"><a href="#the-native-loading-attribute" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The native <code>loading</code> attribute</h2><p>Modern browsers ship a built-in lazy-loading mechanism for images and iframes: the <code>loading</code> HTML attribute. It accepts two values: <code>lazy</code> defers the resource until it is near the viewport, while <code>eager</code> (the default) loads it normally. Adding the attribute is enough; no JavaScript library is required, and lazy-loading still works when JavaScript is disabled (though the load is only deferred, not skipped, when JS is off).</p>
<pre><code class="language-html">&lt;img src=&quot;photo.jpg&quot; loading=&quot;lazy&quot; alt=&quot;…&quot; width=&quot;600&quot; height=&quot;400&quot;&gt;
&lt;iframe src=&quot;https://example.com&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;400&quot;&gt;&lt;/iframe&gt;
</code></pre>
<p>Browsers determine how far ahead of the viewport to fetch by using an effective-connection-type heuristic. On a slow 2G connection the threshold is tighter (the browser fetches only when the user is very close), while on a fast 4G connection the threshold is much wider, so images are usually already loaded by the time the user reaches them. Chrome&#39;s thresholds have been tuned over time; the July 2020 update, for example, widened them on 4G to better match developer expectations.</p>
<p>The <code>loading</code> attribute is independent of fetch priority. An image marked <code>loading=&quot;lazy&quot; fetchpriority=&quot;high&quot;</code> is still delayed while it is off-screen, then fetched with high priority once it is near the viewport. The combination is rarely needed because the browser would have prioritized that image anyway. For above-the-fold images, use <code>fetchpriority=&quot;high&quot;</code> (without <code>loading=&quot;lazy&quot;</code>) to mark them as the LCP candidate.</p>
</section>
<section><h2 id="the-intersection-observer-api"><a href="#the-intersection-observer-api" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The Intersection Observer API</h2><p>The <code>loading</code> attribute is built on top of the Intersection Observer API, which is the general-purpose primitive for &quot;tell me when this element enters the viewport&quot;. Before Intersection Observer, sites had to wire up <code>scroll</code>, <code>resize</code>, and <code>orientationchange</code> event handlers and call <code>getBoundingClientRect()</code> on the main thread for every element being observed. On a long infinite-scroll page with many observers, that work compounded into noticeable jank.</p>
<p>Intersection Observer runs the visibility check off the main thread. The browser fires a callback when an observed element intersects a root element (the viewport by default) or a specified ancestor, or when the intersection ratio crosses a threshold. The API exposes the <code>root</code>, <code>rootMargin</code> (a CSS-like offset to grow or shrink the trigger area), and a list of <code>threshold</code> ratios. A typical lazy-load setup watches an element with a <code>rootMargin</code> of <code>200px</code> so the image starts loading a little before the user scrolls it into view.</p>
</section>
<section><h2 id="code-splitting-and-dynamic-imports"><a href="#code-splitting-and-dynamic-imports" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Code splitting and dynamic imports</h2><p>JavaScript bundles can be split so that only the code needed for the current view is fetched up front. Entry-point splitting separates code by route, while dynamic splitting uses the <code>import()</code> expression to load a module on demand. A practical example is a route that only loads a chart library when the user navigates to a &quot;Reports&quot; tab; users who never visit that tab never download the chart code at all.</p>
<p>Scripts declared with <code>type=&quot;module&quot;</code> are deferred by default, which is another form of lazy loading. The browser fetches them in parallel with HTML parsing but only executes them after the document has been parsed. <code>async</code> scripts execute as soon as they finish downloading, which is useful for independent functionality (analytics, ads) but can interfere with the order of execution.</p>
</section>
<section><h2 id="when-lazy-loading-helps-and-when-it-hurts"><a href="#when-lazy-loading-helps-and-when-it-hurts" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>When lazy loading helps and when it hurts</h2><p>Lazy loading is a net win for resources that are clearly below the fold and unlikely to be needed immediately, but it can backfire on a few patterns. Lazy-loading the LCP image pushes the most important render later, which is the opposite of what you want. Lazy-loading above-the-fold images forces the browser to wait for two fetches (the lazy trigger, then the image) where one eager fetch would have sufficed. Lazy-loading content in a carousel the user is likely to scroll through often wastes time on the first interaction, because the user sees an empty placeholder while the first slide is being fetched.</p>
<p>Always pair <code>loading=&quot;lazy&quot;</code> images with explicit <code>width</code> and <code>height</code> attributes (or a CSS <code>aspect-ratio</code> container). Without reserved dimensions, the image slot is zero-sized until the file loads, and the surrounding content snaps downward as the image paints in — a classic source of Cumulative Layout Shift. The same lesson applies to iframes: a <code>width</code> and <code>height</code> (or styled container) prevents the page from jumping when the iframe finally appears.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-is-the-difference-between-loading-lazy-and-the-intersection-observer-api">What is the difference between <code>loading=&quot;lazy&quot;</code> and the Intersection Observer API?</h3><p><code>loading=&quot;lazy&quot;</code> is the browser&#39;s built-in lazy-loading attribute for images and iframes; the browser handles the visibility detection internally. The Intersection Observer API is a general-purpose JavaScript primitive that lets you detect when any element enters or exits the viewport, which you can use to lazy-load arbitrary resources (background images, video posters, custom markup) and to drive patterns like infinite scroll.</p>
<h3 id="does-lazy-loading-hurt-seo">Does lazy loading hurt SEO?</h3><p>Lazy loading itself does not hurt SEO as long as the content becomes available to crawlers and the page does not have to wait on user interaction. Googlebot renders pages with a headless browser that executes JavaScript, and Google has confirmed that the <code>loading</code> attribute is safe for crawl. The real SEO risk is performance: if lazy-loading the LCP image delays the most important render, the page scores worse on Core Web Vitals and may rank lower. Always keep the LCP image eager.</p>
<h3 id="should-i-lazy-load-the-first-image-on-a-page">Should I lazy-load the first image on a page?</h3><p>No. The first (and largest) image in the initial viewport is typically the LCP element, and delaying it hurts perceived performance. Mark it with <code>fetchpriority=&quot;high&quot;</code> and let it load eagerly. Save <code>loading=&quot;lazy&quot;</code> for images that are clearly below the fold and unlikely to be visible on first paint.</p>
<h3 id="can-i-lazy-load-css">Can I lazy-load CSS?</h3><p>Yes, but the common techniques are different from <code>loading=&quot;lazy&quot;</code>. You can scope non-critical CSS to a media query (<code>@media (min-width: 800px)</code>), preload it with <code>&lt;link rel=&quot;stylesheet&quot; media=&quot;print&quot; onload=&quot;this.media=&#39;all&#39;&quot;&gt;</code>, or split CSS by route. CSS that is render-blocking for the first paint should not be deferred.</p>
<h3 id="why-does-my-lazy-loaded-image-still-load-immediately">Why does my lazy-loaded image still load immediately?</h3><p>The browser has already decided the image is close enough to the viewport that deferring would not save anything. The <code>loading</code> attribute is a hint, not a hard promise. On fast connections with a generous <code>rootMargin</code>, the image starts fetching as soon as the parser reaches it.</p>
</section>
<section><h2 id="summary"><a href="#summary" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Summary</h2><p>Lazy loading defers non-critical resources until they are needed, shrinking the critical rendering path and reducing the data a page consumes on first load. The browser&#39;s <code>loading</code> attribute handles images and iframes; the Intersection Observer API covers anything else; <code>import()</code> and code splitting cover JavaScript. Always reserve dimensions on lazy-loaded media to avoid layout shift, and never lazy-load the LCP image. Used carefully, lazy loading is one of the highest-leverage performance techniques on the modern web.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>What is Edge Computing? How it works and when to use it</title>
      <link>https://debod.id/en/posts/edge-computing-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/edge-computing-explained/</guid>
      <pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate>
      <category>networking</category>
      <category>cloud</category>
      <category>networking</category>
      <description>Edge computing moves computation closer to users, reducing latency and bandwidth compared to centralized cloud. This guide explains how it works, its benefits, challenges, and how it compares to traditional cloud computing.</description>
      <content:encoded><![CDATA[<section><p>Edge computing is a distributed computing model that brings computation and data storage closer to the sources of data — reducing the distance data must travel and cutting the latency of each response. Where a traditional web application runs entirely in a centralized cloud data center (perhaps thousands of kilometres from the user), an edge deployment runs code on servers distributed across the globe, near the users who need it. The result is faster responses, lower bandwidth consumption, and the ability to keep working even when a central cloud connection is interrupted.</p>
<p>The idea dates to the 1990s, when content delivery networks began placing servers near end users to serve website and video content faster. By the early 2000s, these systems expanded to host applications beyond just static files, marking the first true edge computing services. The modern wave is driven by the Internet of Things (IoT), which produces massive amounts of data at the network edge that cannot realistically all be sent back to a central cloud.</p></section>
<section><h2 id="what-is-edge-computing"><a href="#what-is-edge-computing" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What is edge computing?</h2><p>Edge computing runs computer programs close to where requests are made, rather than in a distant data center. Karim Arabi, during an IEEE keynote in 2014, described it as computing that occurs outside the cloud, at the network&#39;s edge — particularly for applications that need immediate data processing.</p>
<p>The defining characteristic is <strong>physical proximity to the user</strong>. A centralized cloud data center might be on another continent; an edge node may be in the same city or even on the same local network. This proximity determines the latency advantage: round-trip times from a nearby edge node are measured in single-digit milliseconds, while the same request to a distant cloud region may take ten times longer.</p>
<p>In cloud gaming, for example, edge nodes — sometimes called &quot;gamelets&quot; — are typically within one or two network hops from the client, which is fast enough for real-time interactive gaming. In contrast, a centralized cloud game server might add 100–200 ms of round-trip latency, making fast-paced games unplayable.</p>
</section>
<section><h2 id="how-it-works"><a href="#how-it-works" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How it works</h2><p>A conventional web application follows this path: a user&#39;s request travels over the internet to a central cloud region, the server processes it, and the response travels back. With edge computing, the request is intercepted and handled by a server that sits between the user and the origin cloud.</p>
<p>Modern platforms make this practical. Cloudflare Workers, for example, runs your code across Cloudflare&#39;s global network of over 200 data centers — you write a single JavaScript function and it executes on the nearest node to each incoming request, without you managing any servers. The platform provides key-value storage (Cloudflare KV) for fast, edge-cached reads and built-in observability.</p>
<p>The architecture typically involves:</p>
<ul>
<li><strong>Edge nodes</strong> — geographically distributed servers that run the application code.</li>
<li><strong>The origin</strong> — a central cloud environment that holds the authoritative data store and business logic.</li>
<li><strong>The network path</strong> — requests first hit an edge node; if the data is cached, it is served immediately; otherwise the edge node forwards the request to the origin and caches the response for subsequent requests.</li>
</ul>
<p>This is sometimes called a <strong>tiered architecture</strong>: the edge tier serves cached or static content, while the origin tier handles personalized, dynamic, or write operations that cannot be cached.</p>
</section>
<section><h2 id="benefits-of-edge-computing"><a href="#benefits-of-edge-computing" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Benefits of edge computing</h2><h3 id="lower-latency">Lower latency</h3><p>The closer the computation is to the user, the faster the response. For applications where every millisecond matters — video streaming, online gaming, financial trading, real-time communications — edge computing can mean the difference between a smooth experience and a frustrating one. Research cited on Wikipedia notes that human perception for tasks such as facial recognition falls in the 370–620 ms range; a well-designed edge platform can respond faster than this threshold, which is critical for augmented reality and other perception-sensitive applications.</p>
<h3 id="reduced-bandwidth">Reduced bandwidth</h3><p>Sending all raw data from edge devices to a central cloud wastes bandwidth. If a user&#39;s device performs video analysis locally and sends only the recognized text to the cloud, it transmits a fraction of the data it would otherwise send. This is a significant advantage for IoT deployments, where devices may be on constrained networks or have limited data plans.</p>
<h3 id="reliability">Reliability</h3><p>An edge node can continue serving cached content even if the connection to the central cloud is disrupted. A voice assistant, for example, may continue providing local services to nearby users during an internet outage. The distributed nature of edge systems also means that the failure of a single node does not take down the entire service — traffic is routed to the nearest healthy node.</p>
<h3 id="privacy">Privacy</h3><p>Processing data at the edge can keep sensitive information local rather than transmitting it to a remote cloud. User data stays on or near the device, which shifts data ownership partially from service providers toward end users.</p>
</section>
<section><h2 id="challenges"><a href="#challenges" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Challenges</h2><h3 id="security">Security</h3><p>The distributed nature of edge computing introduces new security concerns. Data travels between distributed nodes over the public internet, which requires encryption mechanisms that operate independently of the central cloud. Edge nodes themselves may be resource-constrained devices with limited capacity for cryptographic operations. A shift from a centralized trust model to a decentralized one is required, and managing security updates across hundreds of geographically distributed nodes is more complex than updating a single cloud server.</p>
<h3 id="scalability">Scalability</h3><p>Scaling a distributed edge network must account for the heterogeneity of devices — different performance levels, energy constraints, dynamic connectivity conditions, and reliability variations compared to a centralized cloud environment. Adding a new node to the edge network requires coordination, and security mechanisms on constrained devices can introduce latency in inter-node communication.</p>
<h3 id="operational-complexity">Operational complexity</h3><p>Running code across hundreds of edge locations is operationally different from managing a handful of cloud servers. Debugging issues that appear only in specific geographic regions, managing deployments across heterogeneous infrastructure, and maintaining consistent configuration require specialized tooling. &quot;Serverless&quot; is a misnomer — servers are still there; the management complexity is shifted from the operations team to the development team, which must reason about distributed execution, partial failures, and eventual consistency.</p>
</section>
<section><h2 id="applications"><a href="#applications" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Applications</h2><p>Edge computing is used across a wide range of real-world scenarios:</p>
<ul>
<li><strong>Internet of Things (IoT)</strong> — sensor data from connected devices is processed at the edge, reducing the volume of data that must be transmitted to the cloud. Smart grid systems, connected cars, and industrial automation all rely on edge processing for real-time responsiveness.</li>
<li><strong>Cloud gaming</strong> — game logic executes on edge nodes close to players, keeping round-trip latency below the threshold of human perception.</li>
<li><strong>Smart cities</strong> — traffic cameras, environmental sensors, and utility monitors process data locally and only forward exceptions or summaries to a central system.</li>
<li><strong>Augmented and virtual reality</strong> — object recognition and scene analysis run on the edge device or a nearby node to minimize the delay between movement and visual feedback.</li>
<li><strong>Edge AI</strong> — machine learning models run directly on edge devices or nearby servers, enabling real-time inference without round-tripping to a cloud API. This is sometimes called &quot;on-device AI&quot; or &quot;local AI.&quot;</li>
<li><strong>Content delivery</strong> — the original form of edge computing, still widely used: CDN servers cache and serve images, videos, and static assets from locations near the user.</li>
</ul>
</section>
<section><h2 id="edge-computing-vs-cloud-computing"><a href="#edge-computing-vs-cloud-computing" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Edge computing vs cloud computing</h2><p>Edge computing and cloud computing are not competing approaches — they complement each other. The cloud provides virtually unlimited compute and storage for batch processing, complex analytics, and durable data storage. The edge provides low-latency, high-availability execution for real-time, user-facing operations. The most resilient architectures use both: edge for immediate responses, cloud for authoritative data and complex processing.</p>
<table>
<thead>
<tr>
<th></th>
<th>Edge computing</th>
<th>Cloud computing</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Location</strong></td>
<td>Servers near the user, distributed globally</td>
<td>Centralized data centers, may be far from users</td>
</tr>
<tr>
<td><strong>Latency</strong></td>
<td>Single-digit milliseconds</td>
<td>50–200+ ms depending on distance</td>
</tr>
<tr>
<td><strong>Bandwidth</strong></td>
<td>Reduced: data processed locally</td>
<td>Full data transmitted to and from cloud</td>
</tr>
<tr>
<td><strong>Reliability</strong></td>
<td>High: continues working if cloud is unreachable</td>
<td>Depends on internet connectivity</td>
</tr>
<tr>
<td><strong>Scalability</strong></td>
<td>Complex: heterogeneous distributed nodes</td>
<td>Simple: add more cloud instances</td>
</tr>
<tr>
<td><strong>Security model</strong></td>
<td>Decentralized trust, encrypted inter-node</td>
<td>Centralized, simpler trust model</td>
</tr>
<tr>
<td><strong>Best for</strong></td>
<td>Real-time interactions, IoT, low-latency apps</td>
<td>Batch processing, analytics, durable storage</td>
</tr>
</tbody></table>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-is-edge-computing-1">What is edge computing?</h3><p>Edge computing is a distributed computing model that places computation and data storage physically close to the devices and users that need them, rather than running everything in a centralized cloud data center. It reduces latency by eliminating the round trip to a distant server and reduces bandwidth by processing data locally.</p>
<h3 id="what-is-the-difference-between-edge-computing-and-cloud-computing">What is the difference between edge computing and cloud computing?</h3><p>Cloud computing runs applications in centralized data centers that can be far from users, offering virtually unlimited compute and storage at the cost of higher latency. Edge computing distributes computation across many geographically dispersed nodes close to users, minimizing latency and bandwidth at the cost of greater operational complexity and a decentralized trust model.</p>
<h3 id="what-are-the-main-advantages-of-edge-computing">What are the main advantages of edge computing?</h3><p>The three primary advantages are latency (responses in single-digit milliseconds rather than tens or hundreds), bandwidth (data is processed locally rather than transmitted to the cloud), and reliability (edge nodes can continue serving cached content even when the connection to the cloud is interrupted). Privacy is a secondary benefit: sensitive data can be processed at the edge rather than sent to a remote server.</p>
<h3 id="when-should-you-use-edge-computing">When should you use edge computing?</h3><p>Use edge computing when your application needs response times faster than a round trip to a centralized cloud can provide — for real-time interactions such as gaming, AR, video streaming, IoT sensor processing, or financial trading. It is also valuable when bandwidth is constrained (IoT devices on limited networks) or when reliability during internet outages matters. For batch processing, complex analytics, and operations that benefit from virtually unlimited centralized compute, cloud computing remains the better choice.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>What is database normalization? 1NF, 2NF, 3NF, and BCNF explained</title>
      <link>https://debod.id/en/posts/database-normalization-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/database-normalization-explained/</guid>
      <pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate>
      <category>database</category>
      <category>sql</category>
      <category>database</category>
      <category>backend</category>
      <description>Database normalization is the process of structuring a relational database according to a series of normal forms to reduce data redundancy and improve data integrity. This guide explains why normalization matters, walks through 1NF, 2NF, 3NF, BCNF, 4NF, and 5NF with worked examples, and when denormalization is the right trade-off.</description>
      <content:encoded><![CDATA[<section><p>A <strong>normalized</strong> relational database is one whose tables are organized to minimize duplicated data and to prevent the inconsistencies that duplication causes. The process of getting there is called <strong>normalization</strong>, and it works by applying a sequence of <em>normal forms</em> — progressively stricter rules about how columns may depend on keys. The first three normal forms, 1NF, 2NF, and 3NF, were introduced by Edgar F. Codd in 1970 and 1971; the Boyce–Codd form (BCNF) followed in 1974 from Codd and Raymond F. Boyce; later forms (4NF, 5NF, 6NF) were added in the 1970s and 2000s by Ronald Fagin and C. J. Date to handle subtler kinds of redundancy.</p>
<p>In practice, &quot;normalized&quot; most often means <em>in third normal form</em> (3NF), and most well-designed OLTP schemas stop there. Going further is mainly of academic interest, except in data warehouses and column-store engines where 6NF-style representations are used internally for compression and range-scan speed.</p></section>
<section><h2 id="why-normalization-matters"><a href="#why-normalization-matters" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Why normalization matters</h2><p>Codd stated the objectives of normalization beyond 1NF as: freeing the collection of relations from undesirable insertion, update, and deletion dependencies; reducing the need to restructure relations when new types of data are introduced; making the relational model more informative to users; and making relations neutral to the query statistics that change over time.</p>
<p>The motivation is concrete. Tables that are <em>not</em> sufficiently normalized suffer three classic anomalies:</p>
<ul>
<li><strong>Insertion anomaly.</strong> Facts cannot be recorded at all unless other, unrelated facts are also present. In a single &quot;Faculty and Their Courses&quot; table, a newly hired professor who has not yet been assigned to teach any course cannot be inserted, because the <code>Course Code</code> column is part of the row and would have to be set to <code>NULL</code>.</li>
<li><strong>Update anomaly.</strong> The same fact appears on multiple rows, and a change has to be applied everywhere. If a professor&#39;s office address lives in every row of an &quot;Employees&#39; Skills&quot; table (one row per skill), then a single address change must touch all of that professor&#39;s skill rows. If the update is partial, the table gives conflicting answers to &quot;what is this person&#39;s address?&quot;.</li>
<li><strong>Deletion anomaly.</strong> Deleting one fact inadvertently deletes another. In the Faculty/Courses table, removing the last course a professor teaches also removes the professor from the table — unless the course column is set to <code>NULL</code>, which is its own kind of anomaly.</li>
</ul>
<p>A fully normalized database can be extended to accommodate new types of data with minimal changes to existing structure, so applications interacting with the database are minimally affected. Normalized relations also tend to mirror real-world concepts and their interrelationships more closely than denormalized ones.</p>
</section>
<section><h2 id="the-normal-forms-at-a-glance"><a href="#the-normal-forms-at-a-glance" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The normal forms at a glance</h2><p>A <em>normal form</em> is a rule that a table must satisfy. Each form is strictly stronger than the previous one, and the forms are progressive: a table in 3NF is automatically in 2NF and 1NF; a table in BCNF is automatically in 3NF; and so on. The full list, ordered from least to most normalized, is:</p>
<ul>
<li><strong>UNF</strong> — Unnormalized form (repeating groups or composite values in a column).</li>
<li><strong>1NF</strong> — First normal form.</li>
<li><strong>2NF</strong> — Second normal form.</li>
<li><strong>3NF</strong> — Third normal form.</li>
<li><strong>EKNF</strong> — Elementary key normal form (rarely discussed; sits between 3NF and BCNF).</li>
<li><strong>BCNF</strong> — Boyce–Codd normal form.</li>
<li><strong>4NF</strong> — Fourth normal form.</li>
<li><strong>5NF</strong> — Fifth normal form.</li>
<li><strong>DKNF</strong> — Domain-key normal form.</li>
<li><strong>6NF</strong> — Sixth normal form.</li>
</ul>
<p>The informal meaning of &quot;the table is normalized&quot; in everyday database work is almost always &quot;the table is in 3NF&quot;. C. J. Date has argued that only a database in 5NF is truly &quot;normalized&quot;, but the practical convention is 3NF.</p>
</section>
<section><h2 id="first-normal-form-1nf"><a href="#first-normal-form-1nf" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>First normal form (1NF)</h2><p>A table is in <strong>1NF</strong> when every field contains a single, atomic value — no sets of values, no nested records, no repeating groups. A column like <code>Subject</code> containing <code>MySQL, Database, Design</code> for one row violates 1NF because the column contains multiple values.</p>
<p>The fix is to extract the repeated values into a separate table. If a <code>Book</code> table has a <code>Subject</code> column that holds several subject names, the subjects are moved into a <code>Title – Subject</code> table with one row per (book, subject) pair. The original <code>Book</code> table loses the <code>Subject</code> column, and the relation between books and subjects is now in a separate, properly atomic table.</p>
<p>This is the level at which SQL itself imposes structure: a relational table is, by definition, a set of rows, and a column cannot legally contain a sub-relation.</p>
</section>
<section><h2 id="second-normal-form-2nf"><a href="#second-normal-form-2nf" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Second normal form (2NF)</h2><p>A table is in <strong>2NF</strong> when it is in 1NF <em>and</em> every non-key column depends on the <em>whole</em> of every candidate key, not just part of it. 2NF only matters when the candidate key is composite — a primary key made of two or more columns.</p>
<p>Consider a <code>Book</code> table with a composite key of <code>{Title, Format}</code> and a <code>Price</code> column. <code>Price</code> depends on <code>Title</code> <em>and</em> <code>Format</code> (the same title in hardcover costs more than the same title in e-book). But other columns, like <code>Author</code>, <code>Pages</code>, or <code>Publisher</code>, depend on <code>Title</code> alone. Because those non-key columns depend on only <em>part</em> of the candidate key, the table is not in 2NF.</p>
<p>The standard fix is to split: keep <code>Title, Author, Pages, Publisher, …</code> in the <code>Book</code> table (keyed on <code>Title</code> alone), and move <code>Price</code> into a separate <code>Price</code> table keyed on <code>{Title, Format}</code>. After the split, every non-key column in each table depends on the whole key of its own table.</p>
<p>In databases where every table is keyed on a single column (a synthetic <code>id</code>, for example), 2NF violations are usually impossible by construction — a non-key column trivially depends on a single-column key. This is one reason synthetic keys are so common in modern schemas: they make 2NF concerns moot.</p>
</section>
<section><h2 id="third-normal-form-3nf"><a href="#third-normal-form-3nf" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Third normal form (3NF)</h2><p>A table is in <strong>3NF</strong> when it is in 2NF <em>and</em> no non-key column depends on <em>another</em> non-key column. This is the rule that removes transitive dependencies.</p>
<p>After the 2NF split, the <code>Book</code> table has <code>Title</code> as its key, but <code>Author Nationality</code> depends on <code>Author</code>, and <code>Author</code> depends on <code>Title</code>. That is a transitive dependency: <code>Title → Author → Author Nationality</code>. The same transitive chain exists for <code>Publisher Country</code> (depends on <code>Publisher</code>, which depends on <code>Title</code>) and for <code>Genre Name</code> (depends on <code>Genre ID</code>, which depends on <code>Title</code>).</p>
<p>To reach 3NF, those transitively-dependent columns are pulled out into their own tables:</p>
<ul>
<li><code>Author (Author, Nationality)</code></li>
<li><code>Publisher (Publisher, Country)</code></li>
<li><code>Genre (Genre ID, Name)</code></li>
</ul>
<p>The <code>Book</code> table is left with <code>Title, Author, Pages, Thickness, Publisher, Genre ID</code> — only columns that depend on <code>Title</code> directly. After the split, changing a publisher&#39;s country is a one-row update in the <code>Publisher</code> table, not a sweep across the <code>Book</code> table; a new genre can be added without inserting a new book; and a book can be inserted before its author is recorded by leaving <code>Author</code> <code>NULL</code> (or empty), without losing the book record itself.</p>
<p>Informally, a relational database is often described as &quot;normalized&quot; if it meets 3NF, and most 3NF relations are free of insertion, update, and deletion anomalies.</p>
</section>
<section><h2 id="boyce-codd-normal-form-bcnf"><a href="#boyce-codd-normal-form-bcnf" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Boyce–Codd normal form (BCNF)</h2><p><strong>BCNF</strong> is a slightly stricter version of 3NF. A table is in BCNF if and only if, for every one of its functional dependencies <code>X → Y</code>, either <code>X → Y</code> is trivial (<code>Y</code> is a subset of <code>X</code>) or <code>X</code> is a superkey for the table. Since BCNF is strictly stronger than 3NF, every BCNF table is automatically in 3NF, but not every 3NF table is in BCNF.</p>
<p>The classical example is a &quot;Today&#39;s court bookings&quot; table for a tennis club with one hard court (<code>Court 1</code>) and one grass court (<code>Court 2</code>). Each row stores the booking plus a <code>Rate type</code> (SAVER, STANDARD, PREMIUM-A, PREMIUM-B), where each rate type implies a specific court and a specific member/non-member flag. The candidate keys of the table are <code>{Court, Start time}</code>, <code>{Court, End time}</code>, <code>{Rate type, Start time}</code>, and <code>{Rate type, End time}</code>. All four attributes are prime (each belongs to some candidate key), so 2NF and 3NF are both satisfied.</p>
<p>The table is <em>not</em> in BCNF because of the dependency <code>Rate type → Court</code>. <code>Rate type</code> is not a superkey (a single rate type can be in many rows), and <code>Court</code> is not a subset of <code>Rate type</code>. The fix is to split the table into a <code>Rate types</code> table (keyed on <code>Rate type</code>, with the implied <code>Court</code> and member flag) and a <code>Today&#39;s bookings</code> table that no longer carries the redundant <code>Rate type → Court</code> information. Both resulting tables are in BCNF.</p>
<p>BCNF has a known cost: not every non-BCNF table can be decomposed into BCNF tables <em>and</em> preserve all the functional dependencies of the original. Beeri and Bernstein showed in 1979 that a set of functional dependencies such as <code>{AB → C, C → B}</code> cannot be represented by any BCNF schema. In practice, this is rare, but it explains why some teams stop at 3NF even when BCNF is theoretically reachable.</p>
<p>BCNF also has a computational footnote: it is NP-complete, given a database schema in 3NF, to determine whether the schema actually violates BCNF. The check is cheap for any one schema but the worst case is hard.</p>
</section>
<section><h2 id="beyond-3nf-4nf-5nf-6nf"><a href="#beyond-3nf-4nf-5nf-6nf" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Beyond 3NF: 4NF, 5NF, 6NF</h2><p>The higher normal forms address redundancy that 3NF and BCNF do not catch.</p>
<p><strong>Fourth normal form (4NF)</strong> removes redundancy caused by independent multivalued dependencies in the same table. The Wikipedia 4NF example has a <code>Franchisee – Book – Location</code> table where each franchisee offers certain books in certain locations; the combination is not constrained — a franchisee may carry any subset of books in any subset of locations. The table has no non-key attributes and is therefore in BCNF, but the same <code>(Franchisee ID, Title)</code> pair appears with every location, and the same <code>(Franchisee ID, Location)</code> pair appears with every title. Decomposing into <code>Franchisee – Book</code> and <code>Franchisee – Location</code> removes the redundancy and the table is in 4NF.</p>
<p><strong>Fifth normal form (5NF)</strong> removes redundancy that surfaces only when a table can be reconstructed losslessly from a join of three or more projections, but not from any two of them. The classic example, again from the Wikipedia article, involves a supplier / book / franchisee relationship where the constraint is that a supplier supplies a title to a franchisee <em>if and only if</em> the supplier supplies the title, the title is supplied to the franchisee, and the franchisee is supplied by the supplier. Without that constraint, the three-way join produces spurious rows; with the constraint encoded, the table cannot be decomposed into two tables without losing information, and is already in 5NF.</p>
<p><strong>Sixth normal form (6NF)</strong> is the strictest in practical use: &quot;a table is in 6NF when the row contains the Primary Key, and at most one other attribute&quot;. Every non-key column is isolated in its own table, joined back to the key when needed. This is rarely used in OLTP because of the proliferation of tables and the need to update many tables for a single conceptual change, but in data warehouses — Sybase IQ, Microsoft SQL Server columnstore indexes, and similar systems — an internal 6NF representation is exactly what a <em>columnar store</em> does. Each column is stored separately, queries that scan a single column read only that column&#39;s data, and range filters on a low-cardinality column benefit from the compression that isolated storage enables.</p>
<p>Codd&#39;s progressive property still holds: 6NF implies 5NF, 5NF implies 4NF, 4NF implies BCNF, BCNF implies 3NF, 3NF implies 2NF, and 2NF implies 1NF. The practical sweet spot for most transactional systems is 3NF or BCNF; analytics workloads often deliberately denormalize back toward 1NF, or even use a star schema, which knowingly violates 2NF/3NF for read performance.</p>
</section>
<section><h2 id="denormalization-deliberately-breaking-the-rules"><a href="#denormalization-deliberately-breaking-the-rules" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Denormalization: deliberately breaking the rules</h2><p>Denormalization is the strategy of adding redundant copies of data to a previously normalized schema in order to improve read performance, at the cost of write performance and consistency overhead. Wikipedia frames it as a read-vs-write trade-off: a normalized design often has to join many tables to answer a single question, and joins are expensive; denormalization adds precomputed or copied data so a question can be answered from one table.</p>
<p>There are two broad approaches:</p>
<ul>
<li><strong>DBMS-managed denormalization.</strong> The database engine stores redundant copies and keeps them in sync. SQL Server&#39;s indexed views and Oracle/PostgreSQL&#39;s materialized views are the standard examples. The logical schema stays normalized; the physical layout does not.</li>
<li><strong>Designer-managed denormalization.</strong> The database administrator adds the redundancy directly to the schema (extra columns that could be derived from a join, replicated lookup values, precomputed counts). The DBMS does not keep these copies in sync; the application or a trigger has to, which adds a new class of bugs to manage.</li>
</ul>
<p>A denormalized data model is <em>not</em> the same as a data model that was never normalized. Denormalization only helps if it is applied on top of a schema that was at least 3NF, with the resulting anomalies and constraints accounted for. The data-warehouse use case is the canonical one: heavy analytical reads, infrequent writes, and the operational discipline to keep the denormalized views consistent.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-does-it-mean-for-a-database-to-be-normalized">What does it mean for a database to be normalized?</h3><p>A database is <em>normalized</em> when its tables satisfy the rules of the normal forms — most commonly up to 3NF or BCNF. A normalized schema stores each fact in one place, so updates do not have to be made in many rows, and inserts or deletes cannot accidentally lose unrelated facts. The opposite is <em>denormalized</em>, where redundant copies of the same fact are stored in multiple places to make reads faster.</p>
<h3 id="what-is-the-difference-between-1nf-2nf-and-3nf">What is the difference between 1NF, 2NF, and 3NF?</h3><p>1NF requires every column to hold a single, atomic value — no sets, no nested records. 2NF requires that, on top of 1NF, every non-key column depends on the <em>whole</em> of every candidate key (no partial key dependencies). 3NF requires that, on top of 2NF, no non-key column depends on <em>another</em> non-key column (no transitive dependencies). Each form is strictly stronger than the previous, so a 3NF table is automatically in 2NF and 1NF.</p>
<h3 id="why-is-3nf-usually-the-stopping-point">Why is 3NF usually the stopping point?</h3><p>Most OLTP schemas aim for 3NF because most insertion, update, and deletion anomalies disappear once transitive dependencies are removed, and 3NF is much cheaper to reason about and maintain than BCNF or the higher forms. Higher normal forms (4NF, 5NF, 6NF) handle subtler kinds of redundancy — multivalued dependencies, join dependencies, isolated columns — that rarely appear in transactional data models, so the additional complexity is not usually worth it for an OLTP workload.</p>
<h3 id="what-is-the-difference-between-3nf-and-bcnf">What is the difference between 3NF and BCNF?</h3><p>BCNF is a stricter form of 3NF: a table is in BCNF if every non-trivial functional dependency starts with a superkey. A 3NF table can violate BCNF when there are overlapping candidate keys and a non-key dependency follows from one of them, as in the tennis-club &quot;Rate type → Court&quot; example. The fix is to split the table so the non-superkey determinant becomes a key in its own table. Some non-BCNF tables cannot be decomposed into BCNF <em>and</em> preserve all original functional dependencies; in those cases 3NF is the practical ceiling.</p>
<h3 id="when-should-you-denormalize-a-database">When should you denormalize a database?</h3><p>Denormalize when the workload is read-heavy and the cost of repeated joins is the bottleneck, especially in reporting and analytics use cases. The most common pattern is to keep the normalized schema as the source of truth and expose denormalized views (materialized views, indexed views, or pre-aggregated tables) that the DBMS keeps in sync. Manual denormalization is also possible but adds consistency-management overhead: every redundant copy must be updated whenever the underlying data changes, or the database will quietly return inconsistent answers.</p>
<h3 id="is-database-normalization-the-same-as-sql">Is database normalization the same as SQL?</h3><p>No. Normalization is a property of a <em>schema</em> (the table definitions, keys, and relationships). SQL is the language used to <em>query</em> a schema. You can run SQL against a denormalized schema, and you can use a non-SQL system to store a normalized schema. The two are independent — the choice of query language does not determine the schema&#39;s normal form, and the schema&#39;s normal form does not dictate the query language.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>What is a webhook? How webhooks work and how to secure them</title>
      <link>https://debod.id/en/posts/webhooks-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/webhooks-explained/</guid>
      <pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate>
      <category>programming</category>
      <category>api</category>
      <category>http</category>
      <category>automation</category>
      <category>devops</category>
      <description>A webhook is a user-defined HTTP callback: one service sends an HTTP request to a URL you control when an event happens. Learn how they work, how they differ from polling and regular APIs, and how to verify signatures and handle retries safely.</description>
      <content:encoded><![CDATA[<section><p>A webhook is a user-defined HTTP callback: instead of you asking a service for updates, the service sends an HTTP request to a URL you provide whenever an event happens. When a payment succeeds on Stripe, when someone pushes code to a GitHub repository, or when a CI build finishes, the platform calls your endpoint and hands you the event. Webhooks are the standard way services push real-time notifications to your application without you polling for them.</p></section>
<section><h2 id="what-a-webhook-actually-is"><a href="#what-a-webhook-actually-is" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What a webhook actually is</h2><p>A webhook is a method for one web application to alter its behavior based on events from another. You register a URL (the endpoint) on the source service, choose which events you care about, and the service sends an HTTP request — usually a POST with a JSON payload — to that URL whenever one of those events fires.</p>
<p>Webhooks are built on HTTP, so they integrate into web services without adding new infrastructure. You only need a public endpoint that can receive a POST request. Because they reuse the plain web stack, they are the closest thing to a universal way for one service to notify another in real time.</p>
<p>The term was coined by Jeff Lindsay in 2007 from the general programming concept of a hook — a place where you can attach custom code to run when something happens.</p>
</section>
<section><h2 id="webhooks-vs-polling-vs-a-regular-api"><a href="#webhooks-vs-polling-vs-a-regular-api" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Webhooks vs polling vs a regular API</h2><p>The key idea is the direction of the request.</p>
<ul>
<li><strong>Polling</strong> — your application repeatedly asks the service &quot;anything new?&quot; on a schedule. You control timing, but you usually get no real-time notification: the freshest data appears at the next poll, and a lot of requests return no new events.</li>
<li><strong>A regular API (question-answer)</strong> — your application calls the service when <em>you</em> decide, such as a REST call to <code>GET /users</code>. The service answers on demand, but it will not reach out to you.</li>
<li><strong>A webhook (event-driven)</strong> — the service calls <em>your</em> endpoint the moment an event happens. You get real-time delivery and only receive data when there is actually something to process.</li>
</ul>
<p>None is universally better. Webhooks give you real-time push and avoid wasted polling, but they require a reachable, secured endpoint and careful handling of retries and failures. For a deeper look at the request side, see the <a href="/en/posts/api-explained/">API explained</a> guide and the <a href="/en/posts/http-methods-explained/">HTTP methods explained</a> guide.</p>
</section>
<section><h2 id="how-a-webhook-delivery-works"><a href="#how-a-webhook-delivery-works" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How a webhook delivery works</h2><p>A typical webhook flow has three stages:</p>
<ol>
<li><strong>Subscription</strong> — you create a webhook on the source service and give it the URL of your endpoint (for example <code>https://api.example.com/webhooks/stripe</code>). You select the events you want and, for many services, a shared secret.</li>
<li><strong>Event</strong> — something happens on the source side: a charge succeeds, a pull request opens, a deploy finishes.</li>
<li><strong>Delivery</strong> — the source makes an HTTP request to your endpoint. The request is almost always a POST with a JSON body describing the event, plus headers that identify the delivery. Your server returns a <code>2xx</code> status to acknowledge it.</li>
</ol>
<pre><code class="language-http">POST /webhooks/stripe HTTP/1.1
Host: api.example.com
Content-Type: application/json
Stripe-Signature: t=1724860000,v1=abcdef...
</code></pre>
<pre><code class="language-json">{
  &quot;id&quot;: &quot;evt_1QxY2example&quot;,
  &quot;type&quot;: &quot;payment_intent.succeeded&quot;,
  &quot;data&quot;: { &quot;object&quot;: { &quot;amount&quot;: 4999, &quot;currency&quot;: &quot;usd&quot; } }
}
</code></pre>
<p>The <code>type</code> (or a header) tells you what happened, and the body carries the details. Because the webhook is a plain HTTP POST, the same endpoint can serve events from many different providers — a single route can fan out by event type to the right handler.</p>
</section>
<section><h2 id="verifying-webhook-signatures"><a href="#verifying-webhook-signatures" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Verifying webhook signatures</h2><p>Because a webhook is an unauthenticated-style inbound HTTP request, anyone who discovers your endpoint could POST fake events to it. To know the request is genuine, verify its signature — this is the single most important thing you do with an inbound webhook.</p>
<p>Most providers sign the request body with a shared secret using HMAC. GitHub sends <code>X-Hub-Signature-256</code>, Stripe sends <code>Stripe-Signature</code>, Slack sends <code>X-Slack-Signature</code>, Shopify sends <code>X-Shopify-Hmac-Sha256</code>. Your server computes the expected HMAC over the raw body with the secret and compares it to the header using a timing-safe comparison.</p>
<pre><code class="language-js">const https = require(&quot;node:https&quot;);
const crypto = require(&quot;node:crypto&quot;);

const secret = process.env.WEBHOOK_SECRET; // never hardcode secrets

function verify(payload, header) {
  if (!header) return false;
  const expected = &quot;sha256=&quot; + crypto
    .createHmac(&quot;sha256&quot;, secret)
    .update(payload)
    .digest(&quot;hex&quot;);
  // timing-safe compare — never plain ===
  const a = Buffer.from(expected);
  const b = Buffer.from(header);
  return a.length === b.length &amp;&amp; crypto.timingSafeEqual(a, b);
}
</code></pre>
<p>A few extra rules:</p>
<ul>
<li><strong>Use HTTPS</strong> for your endpoint — never deliver or receive webhook payloads over plain HTTP.</li>
<li><strong>Check the timestamp</strong> — many providers include the delivery time so you can reject requests that are too old (a replay attack).</li>
<li><strong>Store secrets in environment variables</strong> or a secret manager, never in source code. For the surrounding pattern, see the <a href="/en/posts/environment-variables-nodejs/">environment variables</a> guide.</li>
<li><strong>Log rejections</strong> — invalid signatures are often the first sign of probing or misconfiguration.</li>
</ul>
</section>
<section><h2 id="handling-retries-and-making-your-handler-idempotent"><a href="#handling-retries-and-making-your-handler-idempotent" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Handling retries and making your handler idempotent</h2><p>Webhooks are delivered over HTTP, which can fail: a network blip, your server restarting, an endpoint that responds too slowly. Providers handle this by retrying. Stripe retries failed deliveries for up to three days, and Shopify retries repeatedly over a window. That means <strong>you may receive the same event more than once</strong>, in any order.</p>
<p>Two practices make this safe:</p>
<ol>
<li><strong>Respond fast</strong> — return a <code>2xx</code> within a few seconds, then process the payload asynchronously. If your handler is slow, the provider may time out and retry, which produces duplicates.</li>
<li><strong>Make the handler idempotent</strong> — processing the same event twice must give the same result as processing it once. The standard approach is to check a deduplication key (the event <code>id</code> or a delivery GUID header) and skip events you have already processed.</li>
</ol>
<p>Retries also mean events can arrive <strong>out of order</strong> — a subscription updated event may arrive before the created event it depends on. Where order matters, rely on timestamps or sequence numbers instead of arrival order. For critical integrations, consider a dead-letter queue where persistently failing events go for manual review instead of being silently lost.</p>
</section>
<section><h2 id="webhooks-vs-other-real-time-options"><a href="#webhooks-vs-other-real-time-options" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Webhooks vs other real-time options</h2><p>Webhooks are not the only way to get real-time data:</p>
<ul>
<li><strong>WebSockets</strong> keep an open, two-way connection for live streaming and interactive apps, but are not an event-delivery/retry system. See the <a href="/en/posts/websocket-explained/">WebSocket explained</a> guide.</li>
<li><strong>Message queues and event streams</strong> (like Kafka) give durable, replayable, ordered delivery, at the cost of running more infrastructure.</li>
<li><strong>Webhooks</strong> are the simplest to consume — a plain HTTP POST — and pair well with a small retry/idempotency layer. Stripe, GitHub and Shopify all use this pattern.</li>
</ul>
<p>A hybrid is common and pragmatic: use the webhook as a real-time notification and keep a polling or API endpoint as a recovery path to backfill any event you might have missed. For the request/response API side of the same story, see the <a href="/en/posts/rest-api-nodejs-express/">REST API with Node.js and Express</a> guide.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-is-the-difference-between-a-webhook-and-an-api">What is the difference between a webhook and an API?</h3><p>An API is a request/response interface your application calls on demand — you ask, the service answers. A webhook is the reverse: the service calls your endpoint when an event happens, delivering the data to you without you asking. Both use HTTP. Roughly: an API lets you pull, a webhook lets the service push.</p>
<h3 id="how-do-i-test-a-webhook-without-waiting-for-a-real-event">How do I test a webhook without waiting for a real event?</h3><p>Most providers include a test/demo panel that sends a sample or simulated event to your endpoint. For local development you can use a public tunnel to expose your local server (GitHub, Stripe and many others accept a tunnel URL), or use a tool that can send a crafted POST with the correct body, headers and signature so you can exercise your verification and handler logic.</p>
<h3 id="what-happens-if-my-webhook-endpoint-is-down">What happens if my webhook endpoint is down?</h3><p>The provider detects the failed delivery and retries according to its policy — for example Stripe retries for up to three days with exponential backoff. In the meantime you may miss events, which is why an idempotent handler plus an API or polling recovery path is recommended for anything where missed events matter.</p>
<h3 id="is-a-webhook-secure">Is a webhook secure?</h3><p>A webhook is only as secure as your verification. Anyone who knows your endpoint URL could POST to it, so you must verify the signature of every request and reject anything that does not match, use HTTPS, check timestamps against replay, and keep the shared secret out of source code. With those in place, webhooks are a safe, standard way to receive events.</p>
<h3 id="do-i-need-a-webhook-or-just-an-api">Do I need a webhook or just an API?</h3><p>If your application actively initiates the interaction (fetch a user, submit a form) you need an API. If you want to react to events that happen on another service in real time (a payment, a deploy, a product update) without polling, a webhook is the right tool. Many integrations use both: webhooks for push notification, an API for backfilling and reconciliation.</p>
</section>
<section><h2 id="verdict"><a href="#verdict" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Verdict</h2><p>Webhooks are the standard building block for event-driven integrations, and they are simple to consume because they are plain HTTP POSTs. Use them when you want a service to push real-time events to you. To build them safely, verify every signature with a timing-safe HMAC comparison, use HTTPS, check timestamps, keep secrets out of code, return <code>2xx</code> fast and process asynchronously, and make your handler idempotent so retries and out-of-order deliveries do not corrupt your state. When delivery is critical, pair the webhook with an API or queue recovery path so a missed event is never silently lost.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>How to use the Web Crypto API in the browser: hashing, signing, and encryption</title>
      <link>https://debod.id/en/posts/web-crypto-api/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/web-crypto-api/</guid>
      <pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate>
      <category>security</category>
      <category>security</category>
      <category>encryption</category>
      <description>The Web Crypto API gives every browser a built-in cryptography engine. Learn how to hash data, sign and verify messages, and encrypt or decrypt content using SubtleCrypto — with practical code examples and security notes.</description>
      <content:encoded><![CDATA[<section><p>The Web Crypto API is a browser built-in that gives JavaScript access to cryptographic primitives. You do not need a library to hash a password, sign a message, or encrypt data. Every modern browser ships with it, accessible via <code>window.crypto.subtle</code>. It runs in the main thread and in Web Workers, and since 2015 it has been Baseline Widely Available across all major browsers.</p>
<p>This guide covers what the API actually does, how its key concepts work, and the code patterns you need to hash data, create and verify digital signatures, and encrypt or decrypt content safely.</p></section>
<section><h2 id="what-the-web-crypto-api-is"><a href="#what-the-web-crypto-api-is" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What the Web Crypto API is</h2><p>The Web Crypto API surfaces low-level cryptographic operations through the <a href="https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto"><code>SubtleCrypto</code></a> interface. Its entry point is <code>window.crypto.subtle</code> (or <code>self.crypto.subtle</code> in a Worker). The API is deliberately low-level — it does not hold your hand. You choose the algorithm, manage the key material, and handle the raw bytes. This is both its power and its risk: it is easy to use correctly for standard patterns and easy to misuse in subtle ways.</p>
<p>The API requires a <strong>secure context</strong> — it only works on pages served over HTTPS (or <code>localhost</code>). Any page loaded over plain HTTP throws a <code>DOMException</code>.</p>
<p>The operations it provides fall into three groups:</p>
<ul>
<li><strong>Hashing</strong> — produce a fixed-length digest from arbitrary data (<code>digest()</code>).</li>
<li><strong>Signing</strong> — create or verify a digital signature with a key pair (<code>sign()</code>, <code>verify()</code>).</li>
<li><strong>Encryption</strong> — encrypt or decrypt with a symmetric key (<code>encrypt()</code>, <code>decrypt()</code>).</li>
<li><strong>Key management</strong> — generate, derive, import, export, and wrap keys (<code>generateKey()</code>, <code>deriveKey()</code>, <code>importKey()</code>, <code>exportKey()</code>, <code>wrapKey()</code>, <code>unwrapKey()</code>).</li>
</ul>
<p>All methods return Promises and operate on <code>ArrayBuffer</code> or <code>TypedArray</code> data, not strings. You encode strings with <code>TextEncoder</code> and decode responses with <code>TextDecoder</code>.</p>
</section>
<section><h2 id="key-concepts"><a href="#key-concepts" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Key concepts</h2><h3 id="keys-are-objects-not-strings">Keys are objects, not strings</h3><p>Cryptographic keys in the Web Crypto API are <a href="https://developer.mozilla.org/en-US/docs/Web/API/CryptoKey"><code>CryptoKey</code></a> objects. They carry metadata — the algorithm, the key type (secret, private, or public), and whether they are extractable. You never pass raw bytes as a key; you always create or import a <code>CryptoKey</code> first.</p>
<h3 id="symmetric-vs-asymmetric">Symmetric vs asymmetric</h3><ul>
<li><strong>Symmetric</strong> algorithms use the same key to encrypt and decrypt (AES). The key must be kept secret.</li>
<li><strong>Asymmetric</strong> algorithms use a key pair: a public key for encryption or signature verification, and a private key that stays on your side. RSA and ECDSA are asymmetric.</li>
</ul>
<p>For most browser-based tasks — encrypting local data, signing a token, hashing a password — symmetric (AES) is simpler and faster. Use asymmetric when you need to share the encryption key or when the other party needs a public key for verification.</p>
<h3 id="timing-safe-comparison">Timing-safe comparison</h3><p>Never use <code>===</code> to compare two strings or byte sequences for equality in security-sensitive code. An attacker can measure how long the comparison takes and use the timing difference to deduce the secret. The Web Crypto API provides <code>crypto.timingSafeEqual()</code> for exactly this — always use it when comparing MACs, signatures, or any secret byte sequence.</p>
</section>
<section><h2 id="hashing-data"><a href="#hashing-data" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Hashing data</h2><p>Hashing turns any input into a fixed-length &quot;fingerprint&quot; in a one-way direction. You cannot recover the original from the hash. SHA-256 (256-bit output) is the most widely used browser-safe hash. SHA-384 and SHA-512 are also available. MD5 and SHA-1 are considered broken for security purposes.</p>
<p>A common use is deriving a key from a password, or creating a quick integrity check for a payload.</p>
<pre><code class="language-js">const encoder = new TextEncoder();
const data = encoder.encode(&quot;The quick brown fox&quot;);

const hashBuffer = await crypto.subtle.digest(&quot;SHA-256&quot;, data);
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hashHex = hashArray.map(b =&gt; b.toString(16).padStart(2, &quot;0&quot;)).join(&quot;&quot;);

console.log(hashHex);
// 4d156c9271fd4f5e99e27fc2e84d3f8f3c0e5b8f7d2a6e1c9b3d...
</code></pre>
<p><code>sha-256</code> is the string identifier for SHA-256. The output is an <code>ArrayBuffer</code> of 32 bytes (256 bits). Convert it to hex for readability, or keep it as bytes for further processing.</p>
<p>For hashing passwords before storing them, consider using PBKDF2 (via <code>deriveKey</code>) rather than a plain hash — it applies multiple iterations to slow down brute-force guesses. For a deeper look at hashing algorithms and their properties, see the <a href="https://en.wikipedia.org/wiki/SHA-2">SHA-2</a> article on Wikipedia.</p>
</section>
<section><h2 id="signing-and-verifying-messages"><a href="#signing-and-verifying-messages" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Signing and verifying messages</h2><p>Digital signatures let you prove that a message was created by the holder of a private key. The recipient uses the matching public key to verify. This is asymmetric cryptography: you sign with the private key, anyone can verify with the public key.</p>
<p>The Web Crypto API supports ECDSA (Elliptic Curve Digital Signature Algorithm) and RSASSA-PKCS1-v1_5. ECDSA is faster and produces smaller signatures — use it for new work.</p>
<pre><code class="language-js">// Generate a key pair
const keyPair = await crypto.subtle.generateKey(
  { name: &quot;ECDSA&quot;, namedCurve: &quot;P-256&quot; },
  true, // extractable
  [&quot;sign&quot;, &quot;verify&quot;]
);

const encoder = new TextEncoder();
const message = encoder.encode(&quot;I authorize this transfer&quot;);

// Sign
const sigBuffer = await crypto.subtle.sign(
  { name: &quot;ECDSA&quot;, hash: &quot;SHA-256&quot; },
  keyPair.privateKey,
  message
);

// Export public key to share
const pubKeyBuffer = await crypto.subtle.exportKey(&quot;spki&quot;, keyPair.publicKey);

// Verify
const valid = await crypto.subtle.verify(
  { name: &quot;ECDSA&quot;, hash: &quot;SHA-256&quot; },
  keyPair.publicKey,
  sigBuffer,
  message
);

console.log(&quot;Signature valid:&quot;, valid); // true
</code></pre>
<p>The signature is a fixed-length byte sequence tied to the message and the private key. If the message or the signature changes, verification fails. This pattern is used for signing JWTs, API tokens, and document attestations. For a practical guide on JWT signing in a Node.js API, see the <a href="/en/posts/jwt-authentication-nodejs/">JWT authentication guide</a>.</p>
</section>
<section><h2 id="encrypting-and-decrypting-data"><a href="#encrypting-and-decrypting-data" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Encrypting and decrypting data</h2><p>Encryption turns readable data (plaintext) into unreadable data (ciphertext) using a key. The Web Crypto API supports three AES modes:</p>
<ul>
<li><strong>AES-GCM</strong> — authenticated encryption (AEAD). The recommended mode. It detects tampering automatically. Requires a 12-byte initialization vector (IV) that must be unique per encryption (but does not need to be secret).</li>
<li><strong>AES-CBC</strong> — block cipher chaining. Requires a 16-byte IV. No built-in authentication — you must not rely on it alone for security-critical data.</li>
<li><strong>AES-CTR</strong> — counter mode. Requires an IV and a counter. Rarely needed for new work.</li>
</ul>
<p>Always prefer <strong>AES-GCM</strong>. It is the only AES mode in Web Crypto that provides authenticated encryption out of the box, which protects against chosen-ciphertext attacks where an attacker asks the system to decrypt arbitrary messages and uses the result to learn about the key.</p>
<pre><code class="language-js">const encoder = new TextEncoder();
const plaintext = encoder.encode(&quot;Secret message&quot;);

// Generate a random 256-bit key
const key = await crypto.subtle.generateKey(
  { name: &quot;AES-GCM&quot;, length: 256 },
  true,
  [&quot;encrypt&quot;, &quot;decrypt&quot;]
);

// IV must be unique per encryption — use random bytes
const iv = crypto.getRandomValues(new Uint8Array(12));

const ciphertext = await crypto.subtle.encrypt(
  { name: &quot;AES-GCM&quot;, iv },
  key,
  plaintext
);

// Store or transmit: key (kept secret), iv (can be stored alongside ciphertext), ciphertext
const decrypted = await crypto.subtle.decrypt(
  { name: &quot;AES-GCM&quot;, iv },
  key,
  ciphertext
);

const decoder = new TextDecoder();
console.log(decoder.decode(decrypted)); // &quot;Secret message&quot;
</code></pre>
<p>Store the IV alongside the ciphertext — it is not secret, only unique. For a client-side application that encrypts local data (say, a note-taking app that stores data in IndexedDB), this pattern is all you need: generate a key, encrypt, store the key in <code>localStorage</code> or derive it from a password.</p>
<p>For storing encryption keys securely in a server-side application, see the <a href="/en/posts/environment-variables-nodejs/">environment variables guide</a> — never hardcode secrets. For secret management at scale, HashiCorp Vault is purpose-built for this. See the <a href="/en/tools/vault/">Vault alternative page</a> for a comparison.</p>
</section>
<section><h2 id="key-derivation"><a href="#key-derivation" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Key derivation</h2><p>Deriving a key lets you produce a cryptographic key from a password or another key material. The Web Crypto API supports PBKDF2 (Password-Based Key Derivation Function 2) and HKDF (HMAC-based Key Derivation Function). PBKDF2 is the right choice when you have a password and want a key.</p>
<pre><code class="language-js">const encoder = new TextEncoder();
const password = encoder.encode(&quot;user-password-123&quot;);

// Salt must be unique per password — store it alongside the derived key
const salt = crypto.getRandomValues(new Uint8Array(16));

const derivedKey = await crypto.subtle.deriveKey(
  { name: &quot;PBKDF2&quot;, salt, iterations: 310000, hash: &quot;SHA-256&quot; },
  await crypto.subtle.importKey(&quot;raw&quot;, password, &quot;PBKDF2&quot;, false, [&quot;deriveKey&quot;]),
  { name: &quot;AES-GCM&quot;, length: 256 },
  true,
  [&quot;encrypt&quot;, &quot;decrypt&quot;]
);
</code></pre>
<p><code>310,000</code> iterations is the current OWASP minimum for PBKDF2 with SHA-256 (as of 2023). The salt must be unique per user and stored in plaintext alongside the encrypted data — it is not secret, only makes precomputed attacks impossible.</p>
</section>
<section><h2 id="common-mistakes-to-avoid"><a href="#common-mistakes-to-avoid" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Common mistakes to avoid</h2><p>The Web Crypto API is low-level and easy to misuse. These are the patterns that cause real security incidents:</p>
<ul>
<li><strong>Reusing an IV (nonce) with AES-GCM or AES-CTR</strong> — if the same IV is used twice with the same key, an attacker can recover both ciphertexts. Always generate a fresh random IV with <code>crypto.getRandomValues()</code> for every encryption operation.</li>
<li><strong>Using AES-CBC or AES-CTR without message authentication</strong> — without authentication, an attacker can flip bits in the ciphertext and the decryption will produce modified output with no error. Use AES-GCM to get authentication automatically, or implement HMAC over the ciphertext if you must use CBC/CTR.</li>
<li><strong>Hardcoding a key in source code</strong> — keys in code are in version control and logs. Derive them from a password, store them in <code>localStorage</code> (client-side), or retrieve them from a secret manager. For server-side patterns, see the <a href="/en/posts/environment-variables-nodejs/">environment variables guide</a>.</li>
<li><strong>Using MD5 or SHA-1 for anything security-related</strong> — both are considered broken for collision resistance. SHA-256 is the minimum standard.</li>
<li><strong>Ignoring secure context requirements</strong> — the API throws in non-HTTPS contexts. Do not build features that silently degrade without noticing.</li>
<li><strong>Using <code>===</code> for secret comparison</strong> — always use <code>crypto.timingSafeEqual()</code> to compare MACs, signatures, and derived keys.</li>
</ul>
<p>For a broader view of the surrounding ecosystem — API design, testing, monitoring — see the <a href="/en/posts/api-explained/">API explained</a> guide.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-is-the-difference-between-hashing-and-encryption">What is the difference between hashing and encryption?</h3><p>Hashing is one-way: given data, you produce a fixed digest. You cannot recover the original from the digest. Encryption is two-way: given ciphertext and the correct key, you recover the plaintext. Use hashing for integrity checks and password storage; use encryption when the original data must be readable again.</p>
<h3 id="can-i-use-web-crypto-in-a-web-worker">Can I use Web Crypto in a Web Worker?</h3><p>Yes. <code>crypto.subtle</code> is available in Workers via <code>self.crypto.subtle</code>. Running crypto operations in a Worker keeps them off the main thread and prevents the UI from stuttering during heavy operations like key derivation or large-file encryption.</p>
<h3 id="is-web-crypto-secure-for-production-use">Is Web Crypto secure for production use?</h3><p>Yes, when used correctly. The underlying algorithms (AES-GCM, ECDSA, SHA-256) are well-vetted and implemented in browser-native code that is generally faster than JavaScript polyfills. The risk is in the usage — wrong algorithm choices, IV reuse, missing authentication, or hardcoded keys. The <a href="https://www.w3.org/TR/webcrypto-2/">W3C specification</a> itself carries a warning that the primitives are easy to misuse.</p>
<h3 id="how-does-web-crypto-compare-to-node-js-crypto">How does Web Crypto compare to Node.js crypto?</h3><p>Node.js <code>crypto</code> module (and the <code>node:crypto</code> API since v15) provides roughly the same algorithms and often more. The Web Crypto API is the browser subset of those primitives, designed for the browser&#39;s threat model. Both use the same underlying native implementations on the system — Web Crypto in browsers, Node.js directly in server-side code. If you write code that works with <code>window.crypto.subtle</code>, the same patterns work with <code>crypto.subtle</code> in Node.js v15+.</p>
<h3 id="do-i-need-a-library-for-client-side-encryption">Do I need a library for client-side encryption?</h3><p>For the operations in this guide, no — the built-in API covers hashing, signing, and AES-GCM encryption without any dependency. Libraries like <a href="https://libsodium.org">libsodium.js</a> provide higher-level constructs (sealed boxes, password hashing with Argon2) that are harder to misuse, but for standard AES-GCM + ECDSA the Web Crypto API is sufficient and has no supply-chain risk.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>Web compression: how gzip and Brotli shrink the web</title>
      <link>https://debod.id/en/posts/web-compression-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/web-compression-explained/</guid>
      <pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate>
      <category>web</category>
      <category>performance</category>
      <category>http</category>
      <description>When a browser loads a page, the HTML, CSS and JavaScript are usually sent compressed. Here&apos;s how gzip and Brotli work, and why Brotli often wins for text.</description>
      <content:encoded><![CDATA[<section><p>Web servers rarely send HTML, CSS and JavaScript as-is. They compress the text first, and the browser decompresses it on arrival. The two formats you meet most are <strong>gzip</strong> and <strong>Brotli</strong>. Both make pages smaller and load faster, but they work a little differently — and Brotli is usually better for the text-heavy files that make up most websites.</p></section>
<section><h2 id="why-servers-compress-text"><a href="#why-servers-compress-text" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Why servers compress text</h2><p>Plain text is repetitive. The same words, HTML tags, CSS properties and JavaScript identifiers appear again and again. Compression algorithms replace that repetition with shorter references, so a 200 KB script might arrive as 50 KB and be rebuilt in the browser. Less data over the wire means a faster first paint, especially on slow or mobile connections. Compression is negotiated per request: the browser lists what it accepts in <code>Accept-Encoding</code>, and the server answers with the algorithm it chose in <code>Content-Encoding</code>.</p>
</section>
<section><h2 id="how-gzip-works"><a href="#how-gzip-works" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How gzip works</h2><p>gzip is the older, near-universal format. Technically it wraps the <strong>DEFLATE</strong> algorithm (defined in RFC 1951) with a header and a 32-bit CRC checksum (defined in RFC 1952). DEFLATE combines the LZ77 method — finding repeated strings and pointing back to earlier copies — with Huffman coding, which assigns shorter codes to common bytes. Because gzip is old and simple, every browser and server supports it, and it compresses quickly even for freshly generated, dynamic responses.</p>
</section>
<section><h2 id="how-brotli-works"><a href="#how-brotli-works" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How Brotli works</h2><p>Brotli (defined in RFC 7932 by Google engineers J. Alakuijala and Z. Szabadka) also combines LZ77 with Huffman coding, so it is built on the same ideas as DEFLATE. The difference is a large, pre-defined dictionary: Brotli ships with roughly 1,200 common words and phrases drawn from web text, HTML, CSS and JavaScript. When it meets a familiar fragment — a frequent tag, a property name, a library call — it can reference the dictionary instead of repeating the bytes. RFC 7932 describes its efficiency as &quot;comparable to the best currently available general-purpose compression methods.&quot;</p>
</section>
<section><h2 id="brotli-vs-gzip-the-practical-difference"><a href="#brotli-vs-gzip-the-practical-difference" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Brotli vs gzip: the practical difference</h2><p>The Brotli dictionary pays off most on web content. Wikipedia summarizes independent comparisons showing that, relative to gzip, JavaScript compressed with Brotli is roughly 15% smaller, HTML around 20% smaller, and CSS around 16% smaller. Brotli also has 11 quality levels (0–11); higher levels compress more but take longer, so Brotli is usually applied to static assets that are pre-compressed once at build time. gzip compresses faster at every level, which keeps it the better choice for responses generated on the fly. In short: gzip is the safe, fast default; Brotli is the stronger choice for pre-built CSS, JS and HTML.</p>
</section>
<section><h2 id="browser-support"><a href="#browser-support" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Browser support</h2><p>gzip has been supported by every browser for decades. Brotli (<code>br</code> in the <code>Content-Encoding</code> header) is supported by all current versions of Chrome, Firefox, Safari and Edge. A server that supports both simply sends Brotli to browsers that ask for it and falls back to gzip for the rest.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-is-the-difference-between-gzip-and-brotli">What is the difference between gzip and Brotli?</h3><p>Both are lossless text compressors built on LZ77 and Huffman coding. Brotli adds a large pre-built dictionary of common web words and phrases, which lets it compress HTML, CSS and JavaScript more tightly — Wikipedia cites roughly 15–20% smaller files than gzip for typical web assets.</p>
<h3 id="is-brotli-better-than-gzip">Is Brotli better than gzip?</h3><p>For static, text-heavy assets such as CSS and JavaScript, Brotli usually produces smaller files than gzip. gzip compresses faster and is supported everywhere, so it remains a strong choice for dynamically generated responses. Many servers send Brotli to capable browsers and gzip as a fallback.</p>
<h3 id="how-does-the-browser-know-which-compression-to-use">How does the browser know which compression to use?</h3><p>The browser advertises supported algorithms in the <code>Accept-Encoding</code> request header (for example <code>gzip, br</code>); the server replies with the one it used in the <code>Content-Encoding</code> response header. The browser then decompresses the body before rendering.</p>
<h3 id="can-compression-make-a-website-slower">Can compression make a website slower?</h3><p>Compression itself adds a little CPU work on the server and a little on the client, but the saved bytes almost always outweigh that cost on text. The main caveat is Brotli&#39;s highest quality levels, which are slow; that is why Brotli is normally pre-applied to static files rather than computed per request.</p>
</section>
<section><h2 id="summary"><a href="#summary" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Summary</h2><p>gzip and Brotli both shrink web text using LZ77 and Huffman coding, but Brotli&#39;s built-in dictionary gives it an edge on HTML, CSS and JavaScript. gzip is the universal, fast default; Brotli is the stronger option for pre-compressed static assets, and every modern browser supports it. Pick a host or server that negotiates both, and your pages ship fewer bytes without changing a line of your code.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>TypeScript vs JavaScript: which language should you learn first?</title>
      <link>https://debod.id/en/posts/typescript-vs-javascript/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/typescript-vs-javascript/</guid>
      <pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate>
      <category>development</category>
      <category>javascript</category>
      <category>web-development</category>
      <category>programming</category>
      <category>nodejs</category>
      <description>JavaScript is the language of the web; TypeScript is a typed superset of it that compiles to JavaScript. Compare the trade-offs and learn when each one is the right tool.</description>
      <content:encoded><![CDATA[<section><p>JavaScript is the language of the web. Every browser ships a JavaScript engine, every web page can run it, and Node.js lets it run on servers, build tools, scripts, and the long tail of software that surrounds a modern web stack. TypeScript is a typed superset of JavaScript developed by Microsoft and released in October 2012: it adds optional static type annotations to the language and compiles back to plain JavaScript that any runtime can execute. They are not competitors in the strict sense. TypeScript only exists because JavaScript exists, and the most common way to start a TypeScript project is to rename a <code>.js</code> file to <code>.ts</code>. Choosing between them is therefore not &quot;which language&quot; but &quot;which level of static typing&quot; you want, and when.</p></section>
<section><h2 id="what-javascript-is"><a href="#what-javascript-is" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What JavaScript is</h2><p>JavaScript is a high-level, dynamic, weakly-typed scripting language standardized as ECMAScript and described by MDN as a &quot;lightweight interpreted (or just-in-time compiled) programming language with first-class functions.&quot; It is the default language of the browser, the runtime behind Node.js, and the lingua franca of the modern front-end ecosystem.</p>
<p>The defining properties matter because they shape what you can rely on:</p>
<ul>
<li><strong>Dynamic typing</strong> — variables are not bound to a declared type; any variable can be reassigned to values of any type, and type checks happen at runtime.</li>
<li><strong>Weak typing with implicit coercion</strong> — JavaScript freely converts between types in operations, which is convenient but also the source of many subtle bugs.</li>
<li><strong>Prototype-based object orientation</strong> — objects inherit from other objects directly, and the language has grown class syntax on top of that model.</li>
<li><strong>Multi-paradigm</strong> — it supports object-oriented, functional, and imperative styles; the ecosystem uses all three.</li>
</ul>
<p>JavaScript is also the only language every browser understands natively. That fact alone has made it the most widely deployed programming language in the world.</p>
</section>
<section><h2 id="what-typescript-is"><a href="#what-typescript-is" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What TypeScript is</h2><p>TypeScript is a free and open-source programming language developed and maintained by Microsoft. As Wikipedia describes it, TypeScript is &quot;a high-level programming language that adds static typing with optional type annotations to JavaScript. It is designed for developing large applications. It transpiles to JavaScript.&quot; It is licensed under the Apache License 2.0 and is the language most often used today to write large Angular, VS Code, and a steadily growing slice of the React, Vue, and Node.js ecosystem.</p>
<p>The core idea is <strong>optional static types on top of JavaScript</strong>. A TypeScript file is a JavaScript file with extra annotations:</p>
<pre><code class="language-ts">function greet(name: string): string {
  return &quot;Hello, &quot; + name;
}
</code></pre>
<p>The TypeScript compiler (<code>tsc</code>) reads these annotations, type-checks the program, and emits plain JavaScript that any browser or Node.js can run. Because the types are stripped, TypeScript never reaches the runtime — it is a developer-tooling layer that disappears at build time. The result is that <strong>all valid JavaScript is also valid TypeScript</strong>, which is why migration is incremental: rename <code>.js</code> to <code>.ts</code> and start adding annotations where they help.</p>
<p>TypeScript also adds language features that JavaScript did not have when TypeScript was created (enums, decorators, <code>readonly</code> modifiers, <code>private</code> modifiers, namespaces) and tracks ECMAScript proposals ahead of browser support.</p>
</section>
<section><h2 id="the-key-differences"><a href="#the-key-differences" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The key differences</h2><table>
<thead>
<tr>
<th>Aspect</th>
<th>JavaScript</th>
<th>TypeScript</th>
</tr>
</thead>
<tbody><tr>
<td>Typing</td>
<td>Dynamic, weak — types checked at runtime</td>
<td>Optional static types checked at compile time, erased before run</td>
</tr>
<tr>
<td>Where it runs</td>
<td>Any browser, any Node.js version, any modern JS engine</td>
<td>Compiles to JavaScript first; runs wherever the output runs</td>
</tr>
<tr>
<td>Learning curve</td>
<td>Lower — you can run a script with no setup</td>
<td>Higher — a <code>tsconfig.json</code>, a build step, and a type system to learn</td>
</tr>
<tr>
<td>Tooling</td>
<td>Excellent in modern editors, but most help is heuristic</td>
<td>First-class editor support: autocomplete, refactor, jump-to-definition driven by types</td>
</tr>
<tr>
<td>Error detection</td>
<td>Bugs surface when the code runs</td>
<td>Many bugs surface while typing, before you save the file</td>
</tr>
<tr>
<td>Project size</td>
<td>Best for small scripts, prototypes, throwaway code</td>
<td>Pays off when a codebase grows, has multiple contributors, or models a domain</td>
</tr>
<tr>
<td>Refactoring</td>
<td>Manual and risky in large codebases</td>
<td>The compiler tells you exactly what breaks when you change a signature</td>
</tr>
<tr>
<td>Runtime cost</td>
<td>None beyond what the engine does anyway</td>
<td>None — types are erased at compile time, output is regular JavaScript</td>
</tr>
<tr>
<td>Community</td>
<td>Universal — every web developer uses it</td>
<td>Dominant in larger front-end, Node.js, and library code; smaller in tiny scripts</td>
</tr>
</tbody></table>
</section>
<section><h2 id="when-javascript-is-the-better-choice"><a href="#when-javascript-is-the-better-choice" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>When JavaScript is the better choice</h2><p>JavaScript is the right default in situations where the simplicity of &quot;no build step, no types, run it&quot; matters more than the static guarantees TypeScript provides:</p>
<ul>
<li><strong>First language to learn</strong> — every web developer needs JavaScript, and learning it first means understanding the runtime that TypeScript ultimately compiles to.</li>
<li><strong>Small scripts and prototypes</strong> — a one-off Node.js tool, a five-line snippet in DevTools, a quick browser experiment; the build step is overhead with no payoff.</li>
<li><strong>Learning web fundamentals</strong> — DOM, fetch, events, the event loop. Adding type annotations on top of these concepts adds noise before the underlying ideas land.</li>
<li><strong>Tiny libraries or single-file components</strong> — when the surface is small, dynamic typing is rarely the source of bugs.</li>
<li><strong>Environments without a build pipeline</strong> — some legacy embed systems, microcontrollers, browser userscripts, and quick demos cannot or do not need a compilation step.</li>
</ul>
<p>JavaScript is also the universal common ground. If you write a library you want anyone to consume, publishing plain JavaScript (or at least shipping a JavaScript build alongside TypeScript sources) is still the safest choice.</p>
</section>
<section><h2 id="when-typescript-is-the-better-choice"><a href="#when-typescript-is-the-better-choice" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>When TypeScript is the better choice</h2><p>TypeScript earns its build step when the cost of runtime bugs and the cost of refactoring start to dominate. Common situations:</p>
<ul>
<li><strong>Application code with a domain model</strong> — once a codebase models users, orders, products, or any non-trivial entity, types catch a long tail of &quot;passed the wrong shape&quot; mistakes that JavaScript would only catch by crashing at runtime.</li>
<li><strong>Large codebases with multiple contributors</strong> — type signatures double as documentation and as a check that one team&#39;s expectations match another team&#39;s implementation.</li>
<li><strong>Long-lived projects that will be refactored</strong> — renaming a property, changing a function signature, or splitting a module is dramatically safer when the compiler walks you through every callsite.</li>
<li><strong>Libraries meant to be consumed by other TypeScript users</strong> — types become the public API of the library; consumers get autocomplete and inline errors in their editor.</li>
<li><strong>Frameworks and tooling that already assume types</strong> — Angular has been TypeScript-first since v2; modern React, Vue, Node.js frameworks and ORMs are all designed with TypeScript in mind.</li>
<li><strong>Anywhere the team wants to catch mistakes at edit time</strong> — the single biggest quality-of-life argument for TypeScript is the red squiggly in the editor, before the code is ever run.</li>
</ul>
<p>The cost of TypeScript is real: a <code>tsconfig.json</code> to maintain, a build step, occasional fights with the type system, and some time spent writing annotations. In a project above a few thousand lines or with more than one contributor, that cost is almost always worth paying.</p>
</section>
<section><h2 id="the-honest-downsides"><a href="#the-honest-downsides" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The honest downsides</h2><p>Both choices have real costs that the rosy side usually omits.</p>
<p>TypeScript adds a build step. Every change has to be compiled before it runs in a browser or on a server, which slows down inner-loop development if you do not set up a fast watcher. The type system is also not free to learn: generics, conditional types, and the way TypeScript infers shapes can be a project of their own. Some libraries ship with weak or missing types, and a few patterns (heavily dynamic code, certain meta-programming tricks) are awkward to express and end up needing <code>any</code> or unsafe casts.</p>
<p>JavaScript&#39;s downsides are the flip side of its simplicity. The compiler cannot warn you that you are calling a function with a string where it expects a number; the runtime will, often in production. Refactoring a large JavaScript codebase means grep, manual checking, and faith. Type coercion produces bugs (<code>&quot;1&quot; + 1</code> is <code>&quot;11&quot;</code>, not <code>2</code>) that are obvious in hindsight but invisible in the moment. None of this is a reason to avoid JavaScript — it is the reason to be deliberate about the size of project you are willing to keep dynamic.</p>
</section>
<section><h2 id="how-to-decide"><a href="#how-to-decide" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How to decide</h2><p>There is no universal winner, and the two are not mutually exclusive:</p>
<ul>
<li><strong>Start with JavaScript</strong> when you are new to programming or new to the web, or when the project is small enough that you can hold the whole thing in your head.</li>
<li><strong>Adopt TypeScript</strong> as soon as the project crosses the threshold where one of these becomes true: a domain model with several entities, more than one contributor, a long expected lifetime, or a need to refactor safely.</li>
<li><strong>Mix them</strong> — most teams use TypeScript for application code and JavaScript (or untyped TS) for tiny config files, throwaway scripts, and the like. The TypeScript compiler is happy to let <code>.js</code> files in.</li>
<li><strong>Migrate incrementally</strong> — TypeScript was designed for gradual adoption. You can flip the switch on one folder, then another, without rewriting the codebase.</li>
</ul>
<p>If you are starting a brand-new project today, the default is increasingly TypeScript: most scaffolding tools (<code>create-react-app</code>, <code>npm create vue</code>, <code>create-next-app</code>) offer it out of the box, and the largest libraries in the ecosystem either are written in TypeScript or ship first-class types. If you are learning from zero, start with JavaScript so you understand the runtime, then add types when they start to feel like the missing piece.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="is-typescript-a-different-language-from-javascript">Is TypeScript a different language from JavaScript?</h3><p>TypeScript is a different language in the sense that it has its own syntax, compiler, and tooling, but it is a strict superset of JavaScript. Every valid JavaScript program is also a valid TypeScript program — TypeScript only adds optional type annotations and a few language features on top. The TypeScript compiler emits plain JavaScript, so TypeScript code runs in the same runtimes as JavaScript (browsers, Node.js, Deno, Bun) with no special runtime support.</p>
<h3 id="do-i-need-to-learn-javascript-before-typescript">Do I need to learn JavaScript before TypeScript?</h3><p>Yes. Because TypeScript compiles to JavaScript and the runtime is JavaScript, you need to understand JavaScript fundamentals — the event loop, prototypes, closures, the DOM, async/await, modules — to write useful TypeScript. The typical path is to spend a few weeks with plain JavaScript, then turn on TypeScript and use the types to deepen your understanding of the same code.</p>
<h3 id="does-typescript-replace-javascript-in-browsers">Does TypeScript replace JavaScript in browsers?</h3><p>No. Browsers do not run TypeScript; they run JavaScript. TypeScript&#39;s role is at build time: the compiler checks the types, then emits the equivalent JavaScript, which is what actually ships. This is why every TypeScript project needs a build step before the code can be served to a browser.</p>
<h3 id="is-typescript-slower-than-javascript-at-runtime">Is TypeScript slower than JavaScript at runtime?</h3><p>No. TypeScript has no runtime — types are erased at compile time, and the emitted JavaScript is the same code you would have written by hand (often better, because the compiler optimizes some patterns). Any runtime difference between a TypeScript and a hand-written JavaScript implementation of the same algorithm comes from the code itself, not from the language.</p>
</section>
<section><h2 id="verdict"><a href="#verdict" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Verdict</h2><p>JavaScript and TypeScript are not in competition; TypeScript is a layer on top of JavaScript that you can choose to use or not. Pick JavaScript when you want zero build steps, are writing something small, or are just learning the web platform. Pick TypeScript when the project has a domain worth modeling, a team that will grow, or a lifetime long enough to need safe refactoring. The two coexist on every project you will touch, and the right answer is rarely &quot;always one, never the other&quot; — it is &quot;JavaScript now, TypeScript as soon as the code earns it.&quot;</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>How to set up SSH key authentication (GitHub + Linux)</title>
      <link>https://debod.id/en/posts/ssh-key-setup-guide/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/ssh-key-setup-guide/</guid>
      <pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate>
      <category>security</category>
      <category>ssh</category>
      <category>security</category>
      <category>github</category>
      <category>devops</category>
      <category>authentication</category>
      <description>Set up SSH key authentication for GitHub and Linux servers: generate Ed25519 keys, add to ssh-agent, copy public key, and test connection — step-by-step with open-license sources.</description>
      <content:encoded><![CDATA[<section><p>SSH key authentication replaces password logins with a cryptographic key pair: a private key kept secret on your client machine, and a public key placed on the server (or GitHub). This guide walks through generating an Ed25519 SSH key pair, configuring the ssh-agent, uploading the public key to GitHub, and authorizing key-based login on a Linux server — all using open, verifiable sources.</p></section>
<section><h2 id="about-ssh-key-authentication"><a href="#about-ssh-key-authentication" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>About SSH key authentication</h2><p>SSH (Secure Shell) uses public key cryptography for authenticating users. The protocol never sends your private key over the network; instead, it proves possession of the private key by signing a challenge. This eliminates password brute-force and phishing risks.</p>
<p>A typical SSH key pair consists of:</p>
<ul>
<li><strong>Private key</strong> (<code>~/.ssh/id_ed25519</code>): Never shared; decrypts authentication challenges.</li>
<li><strong>Public key</strong> (<code>~/.ssh/id_ed25519.pub</code>): Shared with servers/GitHub; verifies signatures.</li>
</ul>
<p>When you attempt to connect, the server encrypts a nonce with your public key; only your private key can decrypt it to prove identity.</p>
<blockquote>
<p><strong>Why Ed25519?</strong> Modern Ed25519 keys offer equivalent security to 3072-bit RSA with faster performance and smaller size. GitHub requires Ed25519 or RSA with SHA-2 signatures for keys uploaded after March 15, 2022.</p>
</blockquote>
</section>
<section><h2 id="step-1-check-for-existing-ssh-keys"><a href="#step-1-check-for-existing-ssh-keys" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Step 1 — Check for existing SSH keys</h2><p>Before generating a new key, check whether you already have SSH keys in <code>~/.ssh/</code>:</p>
<pre><code class="language-bash">ls -la ~/.ssh/
</code></pre>
<p>Common filenames: <code>id_rsa</code>, <code>id_ecdsa</code>, <code>id_ed25519</code>. If you see both a private key (no extension) and a <code>.pub</code> file, a key pair already exists. Back up or reuse existing keys if preferred.</p>
</section>
<section><h2 id="step-2-generate-a-new-ed25519-key-pair"><a href="#step-2-generate-a-new-ed25519-key-pair" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Step 2 — Generate a new Ed25519 key pair</h2><p>Run <code>ssh-keygen</code> to create a new key pair. Replace <code>your_email@example.com</code> with your GitHub or account email.</p>
<pre><code class="language-bash">ssh-keygen -t ed25519 -C &quot;your_email@example.com&quot;
</code></pre>
<p>You&#39;ll see prompts:</p>
<ol>
<li><strong>File to save the key</strong>: Press Enter to accept the default location (<code>~/.ssh/id_ed25519</code>).</li>
<li><strong>Passphrase</strong> (optional but recommended): Enter a secure passphrase to encrypt your private key file. You&#39;ll need to enter this whenever the key is used unless loaded into ssh-agent.</li>
<li><strong>Repeat passphrase</strong>: Confirm the passphrase.</li>
</ol>
<p>The output shows the key fingerprint and randomart image — a visual representation of your key.</p>
<blockquote>
<p><strong>Legacy systems note</strong>: If your system doesn&#39;t support Ed25519 (very old OpenSSH), use <code>ssh-keygen -t rsa -b 4096 -C &quot;your_email@example.com&quot;</code> instead. Ensure the RSA key uses SHA-2 signatures (required by GitHub for newer keys).</p>
</blockquote>
</section>
<section><h2 id="step-3-start-ssh-agent-and-add-your-key"><a href="#step-3-start-ssh-agent-and-add-your-key" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Step 3 — Start ssh-agent and add your key</h2><p>The <code>ssh-agent</code> is a background program that holds your private key decrypted in memory, so you don&#39;t enter the passphrase for every connection.</p>
<p>Start the agent and add your key:</p>
<pre><code class="language-bash"># Start the agent in the background
eval &quot;$(ssh-agent -s)&quot;
# Add your private key to the agent
ssh-add ~/.ssh/id_ed25519
</code></pre>
<p>If you set a passphrase, you&#39;ll be prompted to enter it once. The agent will now supply your key for SSH authentication until you log out or kill the agent.</p>
<p>To make this persistent across sessions, add the <code>eval &quot;$(ssh-agent -s)&quot;</code> line to your shell profile (<code>~/.bashrc</code>, <code>~/.zshrc</code>, etc.).</p>
</section>
<section><h2 id="step-4-copy-the-public-key-to-github"><a href="#step-4-copy-the-public-key-to-github" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Step 4 — Copy the public key to GitHub</h2><p>To authenticate with GitHub over SSH, add your public key to your GitHub account.</p>
<ol>
<li><p>Copy the public key to your clipboard:</p>
<pre><code class="language-bash"># macOS
pbcopy &lt; ~/.ssh/id_ed25519.pub

# Linux (requires xclip or wl-clipboard)
xclip -selection clipboard &lt; ~/.ssh/id_ed25519.pub
# or
wl-clipboard &lt; ~/.ssh/id_ed25519.pub

# WSL2
clip.exe &lt; ~/.ssh/id_ed25519.pub
</code></pre>
</li>
<li><p>In GitHub:</p>
<ul>
<li>Click your profile picture → <strong>Settings</strong>.</li>
<li>In the left sidebar, click <strong>SSH and GPG keys</strong>.</li>
<li>Click <strong>New SSH key</strong> or <strong>Add SSH key</strong>.</li>
<li>Paste the key into the <strong>Key</strong> field.</li>
<li>Give it a descriptive title (e.g., &quot;Personal Laptop Ed25519&quot;).</li>
<li>Select <strong>Key type</strong> as <strong>Authentication</strong>.</li>
<li>Click <strong>Add SSH key</strong>.</li>
</ul>
</li>
</ol>
<blockquote>
<p><strong>Verify</strong>: After adding, GitHub shows the key fingerprint and the date added.</p>
</blockquote>
</section>
<section><h2 id="step-5-set-up-key-based-login-on-a-linux-server"><a href="#step-5-set-up-key-based-login-on-a-linux-server" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Step 5 — Set up key-based login on a Linux server</h2><p>To use SSH keys for server login (instead of passwords), copy your public key to the server&#39;s <code>~/.ssh/authorized_keys</code> file.</p>
<h3 id="option-a-using-ssh-copy-id-recommended">Option A — Using <code>ssh-copy-id</code> (recommended)</h3><p>If <code>ssh-copy-id</code> is available (installed with OpenSSH), run:</p>
<pre><code class="language-bash">ssh-copy-id -i ~/.ssh/id_ed25519.pub user@your_server_ip
</code></pre>
<p>Replace <code>user</code> with your username and <code>your_server_ip</code> with the server&#39;s address or hostname. You&#39;ll be prompted for the user&#39;s password one last time. The tool appends your public key to <code>~/.ssh/authorized_keys</code> on the server.</p>
<h3 id="option-b-manual-copy">Option B — Manual copy</h3><p>If <code>ssh-copy-id</code> isn&#39;t available, manually append the key:</p>
<pre><code class="language-bash">cat ~/.ssh/id_ed25519.pub | ssh user@your_server_ip &#39;mkdir -p ~/.ssh &amp;&amp; cat &gt;&gt; ~/.ssh/authorized_keys &amp;&amp; chmod 700 ~/.ssh &amp;&amp; chmod 600 ~/.ssh/authorized_keys&#39;
</code></pre>
<p>Again, replace <code>user@your_server_ip</code> with your target. You&#39;ll be prompted for the password.</p>
<h3 id="set-correct-permissions-critical">Set correct permissions (critical)</h3><p>On the server, ensure the <code>.ssh</code> directory and files have restrictive permissions:</p>
<pre><code class="language-bash">chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
</code></pre>
<p>Incorrect permissions cause SSH to ignore the key file for security reasons.</p>
</section>
<section><h2 id="step-6-test-your-ssh-connection"><a href="#step-6-test-your-ssh-connection" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Step 6 — Test your SSH connection</h2><p>Verify that authentication works without a password prompt.</p>
<h3 id="test-github-ssh">Test GitHub SSH</h3><pre><code class="language-bash">ssh -T git@github.com
</code></pre>
<p>You should see:</p>
<pre><code>Hi &lt;username&gt;! You&#39;ve successfully authenticated, but GitHub does not provide shell access.
</code></pre>
<p>If it&#39;s the first connection, GitHub shows its host key fingerprint. Verify it matches <a href="https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/githubs-ssh-key-fingerprints">GitHub&#39;s published fingerprint</a>, then type <code>yes</code>.</p>
<h3 id="test-linux-server-ssh">Test Linux server SSH</h3><pre><code class="language-bash">ssh user@your_server_ip
</code></pre>
<p>If you set a passphrase on the key, you&#39;ll be prompted to enter it (unless the key is loaded in ssh-agent). Otherwise, you should log in directly.</p>
</section>
<section><h2 id="step-7-optional-add-key-to-ssh-agent-automatically"><a href="#step-7-optional-add-key-to-ssh-agent-automatically" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Step 7 — Optional: Add key to ssh-agent automatically</h2><p>To avoid running <code>ssh-add</code> every session, add your key to the agent automatically.</p>
<p>Add these lines to your shell profile (<code>~/.bashrc</code>, <code>~/.zshrc</code>):</p>
<pre><code class="language-bash"># Start ssh-agent if not running
if [ -z &quot;$SSH_AUTH_SOCK&quot; ]; then
   eval &quot;$(ssh-agent -s)&quot;
   ssh-add ~/.ssh/id_ed25519
fi
</code></pre>
<p>This starts the agent on demand and loads your key.</p>
</section>
<section><h2 id="common-mistakes-to-avoid"><a href="#common-mistakes-to-avoid" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Common mistakes to avoid</h2><ul>
<li><strong>Wrong permissions</strong>: <code>~/.ssh</code> must be <code>700</code>, <code>authorized_keys</code> must be <code>600</code>. SSH ignores insecure permissions.</li>
<li><strong>Forgotten passphrase</strong>: If you set a passphrase and don&#39;t load the key into ssh-agent, you&#39;ll be prompted for it on every connection.</li>
<li><strong>Mixing key types</strong>: Ensure the public key you upload matches the private key you&#39;re using (Ed25519 vs RSA).</li>
<li><strong>Server not allowing pubkey auth</strong>: Check <code>/etc/ssh/sshd_config</code> on the server for <code>PubkeyAuthentication yes</code> and restart sshd after changes.</li>
<li><strong>Agent not running</strong>: If you see <code>Agent admitted failure to sign using the key</code>, the key isn&#39;t loaded in ssh-agent. Run <code>ssh-add ~/.ssh/id_ed25519</code>.</li>
</ul>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="how-do-i-use-different-ssh-keys-for-different-accounts">How do I use different SSH keys for different accounts?</h3><p>Use Host aliases in <code>~/.ssh/config</code>. Example:</p>
<pre><code class="language-bash"># GitHub personal account
Host github.com
   HostName github.com
   User git
   IdentityFile ~/.ssh/id_ed25519_personal
   IdentitiesOnly yes

# Work account (different key)
Host github-work
   HostName github.com
   User git
   IdentityFile ~/.ssh/id_ed25519_work
   IdentitiesOnly yes
</code></pre>
<p>Then use <code>git@github-work:user/repo.git</code> for work repos.</p>
<h3 id="can-i-use-the-same-ssh-key-for-github-and-server-login">Can I use the same SSH key for GitHub and server login?</h3><p>Yes. The same public key can be placed in multiple places: your GitHub account, server <code>authorized_keys</code>, or any service accepting SSH keys. However, for high-security separation, use different key pairs.</p>
<h3 id="what-if-i-lose-my-private-key">What if I lose my private key?</h3><p>Generate a new key pair and repeat the setup process. Immediately remove the lost/public key from all services (GitHub, servers) to prevent unauthorized access.</p>
<h3 id="should-i-encrypt-my-private-key-with-a-passphrase">Should I encrypt my private key with a passphrase?</h3><p>Yes, if the key is stored on a laptop or shared machine. A passphrase encrypts the private key file so theft of the laptop doesn&#39;t grant immediate access. On a secure, isolated workstation you may skip it, but a passphrase is recommended.</p>
<h3 id="how-do-i-rotate-ssh-keys">How do I rotate SSH keys?</h3><p>Generate a new key pair, add the new public key to all services, verify login works, then remove the old public key from GitHub and server <code>authorized_keys</code> files.</p>
</section>
<section><h2 id="summary"><a href="#summary" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Summary</h2><p>SSH key authentication provides secure, passwordless login for GitHub and Linux servers. By generating an Ed25519 key pair, using ssh-agent, and copying the public key to the right places, you replace vulnerable passwords with strong cryptography. Always verify connections, maintain proper file permissions, and rotate keys periodically for best security.</p>
<p>With these steps, you can authenticate to GitHub push/pull and SSH into servers without typing a password — while keeping your private key secret.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>What is a Service Worker? How it works and when to use it</title>
      <link>https://debod.id/en/posts/service-workers-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/service-workers-explained/</guid>
      <pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate>
      <category>web</category>
      <category>web</category>
      <category>frontend</category>
      <category>programming</category>
      <category>javascript</category>
      <description>A service worker is a JavaScript file the browser runs in the background, separate from the page. It sits between your app and the network and lets you cache assets, intercept requests, work offline, and receive push notifications. Here&apos;s how it works, what its lifecycle looks like, and when to reach for one.</description>
      <content:encoded><![CDATA[<section><p>A service worker is a JavaScript file the browser runs in the background, separate from your web page. It registers against an origin and a path, and once active it acts as a programmable network proxy between the page and the network: every request the page makes can be intercepted, rewritten, served from a cache, or short-circuited. That single capability is what unlocks offline experiences, instant repeat-visit loads, background sync, and push notifications on the open web.</p>
<p>The <a href="https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API">MDN Web Docs</a> call it &quot;essentially a proxy server that sits between web applications, the browser, and the network (when available)&quot;. The proxy framing is the cleanest way to think about it: the worker is not part of the page&#39;s JavaScript, does not have access to the DOM, and cannot block the page — it lives in its own thread and only responds to events.</p></section>
<section><h2 id="where-service-workers-fit"><a href="#where-service-workers-fit" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Where service workers fit</h2><p>Service workers are a kind of <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API">web worker</a>, which means they run off the main thread and have no DOM access. Unlike a regular web worker (which is a script you spawn and communicate with), a service worker is registered once, persists across page loads, and runs long after the page that registered it has been closed. The browser starts it on demand whenever an event in its scope fires.</p>
<p>The features a service worker unlocks are the building blocks of the modern app-like web:</p>
<ul>
<li><strong>Offline support</strong> — serve cached HTML, CSS, JavaScript, and images when the network is unavailable.</li>
<li><strong>Faster repeat visits</strong> — short-circuit cached resources instead of going to the network.</li>
<li><strong>Background sync</strong> — replay a request when connectivity returns (e.g. send a queued message).</li>
<li><strong>Push notifications</strong> — wake the worker from a push event even when no tab is open.</li>
<li><strong>Request interception</strong> — rewrite headers, respond with synthesized data, or simulate network conditions.</li>
</ul>
<p>This is why a service worker is the technical core of a <a href="/en/posts/progressive-web-app-guide/">progressive web app</a> — the offline behavior and installability of a PWA are both implemented on top of one.</p>
</section>
<section><h2 id="the-basics"><a href="#the-basics" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The basics</h2><p>A service worker is a plain JavaScript file that lives on your origin. Three rules govern how you write it:</p>
<ul>
<li><strong>It must be served from a secure origin.</strong> Browsers only register service workers on HTTPS, with one exception: <code>http://localhost</code> is treated as secure so local development works without certificates.</li>
<li><strong>It has no DOM access.</strong> No <code>document</code>, no <code>window</code>, no direct access to the page. Communication with the page goes through <code>postMessage</code> and the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Clients">Clients API</a>.</li>
<li><strong>It is fully asynchronous.</strong> APIs that are synchronous on the main thread, like synchronous <code>XMLHttpRequest</code> or <code>localStorage</code>, are not available. The worker is built around promises; the lifecycle events all take a <code>event.waitUntil(promise)</code> call to signal completion.</li>
</ul>
<p>It also matters where the file lives. The browser scopes a service worker to the directory it is served from. A worker at <code>/sw.js</code> controls every page under <code>/</code>, a worker at <code>/blog/sw.js</code> controls only <code>/blog/*</code>, and so on. The convention is to put it at the root so it can act as a catch-all proxy.</p>
</section>
<section><h2 id="the-lifecycle"><a href="#the-lifecycle" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The lifecycle</h2><p>A service worker goes through a deliberately careful lifecycle. According to the <a href="https://web.dev/articles/service-worker-lifecycle">web.dev service worker lifecycle guide</a> by Jake Archibald, the intent of the lifecycle is to:</p>
<ul>
<li>Make offline-first possible.</li>
<li>Allow a new service worker to get itself ready without disrupting the current one.</li>
<li>Ensure an in-scope page is controlled by the same service worker (or no service worker) throughout.</li>
<li>Ensure there is only one version of your site running at once.</li>
</ul>
<p>The states a service worker moves through are:</p>
<ol>
<li><strong>Parsed</strong> — the browser has fetched <code>sw.js</code> and is checking whether it is new (byte-different from the currently installed version, or the first one ever).</li>
<li><strong>Installing</strong> — the <code>install</code> event fires. This is where you typically pre-cache your app shell with <code>caches.open(name).then(cache =&gt; cache.addAll([...]))</code>. <code>event.waitUntil(promise)</code> extends the install until the promise settles.</li>
<li><strong>Installed (waiting)</strong> — the worker has installed successfully but the old version is still controlling open pages. The new worker waits until all pages using the old version are closed.</li>
<li><strong>Activating</strong> — the <code>activate</code> event fires. This is where you typically clean up old caches from previous versions.</li>
<li><strong>Activated</strong> — the worker is now in control of the page. From this point on it receives <code>fetch</code> and <code>push</code> events for any in-scope navigation.</li>
<li><strong>Redundant</strong> — if a newer version installs and activates, the old worker is replaced and becomes redundant.</li>
</ol>
<p>Two details from the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API">MDN service worker API</a> page shape how the lifecycle feels in practice:</p>
<ul>
<li><strong>A page&#39;s fetches do not go through a service worker unless the page request itself went through a service worker.</strong> This means the first time you ship a worker, the current page keeps loading directly from the network; the worker only takes effect on the next navigation or after a refresh. If you need the new worker to control the current page immediately, call <code>clients.claim()</code> from inside <code>activate</code>.</li>
<li><strong>A new service worker does not interrupt an in-progress page.</strong> It waits for the page to close. This is intentional: it prevents two versions of your app from running at the same time, which is the only way to guarantee that IndexedDB, the cache storage, and your other persistent state have a single source of truth.</li>
</ul>
</section>
<section><h2 id="a-minimal-example"><a href="#a-minimal-example" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>A minimal example</h2><p>The shortest end-to-end demonstration is a cache-on-install, network-falling-back-to-cache fetch handler. From MDN&#39;s <a href="https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers">Using Service Workers</a> guide:</p>
<pre><code class="language-js">// sw.js
const CACHE = &#39;app-shell-v1&#39;;

self.addEventListener(&#39;install&#39;, (event) =&gt; {
  event.waitUntil(
    caches.open(CACHE).then((cache) =&gt; cache.addAll([
      &#39;/&#39;,
      &#39;/styles.css&#39;,
      &#39;/app.js&#39;,
    ]))
  );
});

self.addEventListener(&#39;activate&#39;, (event) =&gt; {
  // clean up old caches
  event.waitUntil(
    caches.keys().then((keys) =&gt;
      Promise.all(keys.filter((k) =&gt; k !== CACHE).map((k) =&gt; caches.delete(k)))
    )
  );
});

self.addEventListener(&#39;fetch&#39;, (event) =&gt; {
  event.respondWith(
    fetch(event.request).catch(() =&gt; caches.match(event.request))
  );
});
</code></pre>
<p>Registration from a page looks like this:</p>
<pre><code class="language-js">if (&#39;serviceWorker&#39; in navigator) {
  navigator.serviceWorker.register(&#39;/sw.js&#39;);
}
</code></pre>
<p>A few patterns worth pulling out of the example:</p>
<ul>
<li><strong><code>install</code> is for filling the cache.</strong> Use it to pre-cache the app shell (HTML, CSS, the JS bundle, key images) so the first offline visit can render something useful.</li>
<li><strong><code>activate</code> is for cleaning up old caches.</strong> Bump the cache name on each release (e.g. <code>app-shell-v1</code> → <code>app-shell-v2</code>) and delete the previous one here.</li>
<li><strong><code>fetch</code> is the proxy.</strong> <code>event.respondWith(...)</code> tells the browser &quot;I will handle this request myself.&quot; Returning <code>fetch(event.request).catch(() =&gt; caches.match(event.request))</code> is a common &quot;network-first, fall back to cache&quot; pattern.</li>
<li><strong>A service worker is JavaScript, but with a different scope.</strong> You can split the file into modules and <code>import</code> them statically; you just cannot use dynamic <code>import()</code>.</li>
</ul>
</section>
<section><h2 id="updating-a-deployed-service-worker"><a href="#updating-a-deployed-service-worker" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Updating a deployed service worker</h2><p>Updating a service worker is what most teams get wrong first. The lifecycle is designed to make updates safe but unobtrusive — you ship a new <code>sw.js</code> file, the browser downloads it in the background, runs <code>install</code> (with the new caches), and waits for the old page to close before <code>activate</code>.</p>
<p>A common update flow:</p>
<ol>
<li>Bump a constant in <code>sw.js</code> (e.g. <code>CACHE = &#39;app-shell-v2&#39;</code>) and update the files in <code>cache.addAll([...])</code>.</li>
<li>The browser detects the byte difference on the next navigation and starts the new worker.</li>
<li>The new worker&#39;s <code>install</code> populates the new cache; the old worker continues serving the open page.</li>
<li>When every tab of the old page is closed, the new worker&#39;s <code>activate</code> runs and deletes the old cache.</li>
<li>The next navigation is controlled by the new worker.</li>
</ol>
<p>If you need the new worker to take effect without waiting for users to close every tab, you can call <code>self.skipWaiting()</code> from <code>install</code> and <code>clients.claim()</code> from <code>activate</code>. The trade-off is that two versions of your app can briefly run side-by-side; the <a href="https://web.dev/articles/service-worker-lifecycle">web.dev lifecycle guide</a> walks through the cases where each approach is appropriate.</p>
</section>
<section><h2 id="when-to-use-a-service-worker"><a href="#when-to-use-a-service-worker" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>When to use a service worker</h2><p>Service workers are the right tool when you need any of the following:</p>
<ul>
<li><strong>Offline support for a published website</strong> — the canonical use case. PWAs, documentation sites, news readers, anything users may open on flaky connections.</li>
<li><strong>Faster repeat loads</strong> — serving the app shell and critical assets from the cache instead of the network. Pair with the <a href="/en/posts/browser-cache-explained/">cache</a> and <a href="/en/posts/caching-explained/">HTTP caching</a> headers from your origin for the right balance.</li>
<li><strong>Background sync</strong> — the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Background_Synchronization_API">Background Sync API</a> lets a worker replay a request after the device comes back online.</li>
<li><strong>Push notifications on the web</strong> — the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Push_API">Push API</a> wakes the worker on receipt, and the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Notifications_API">Notifications API</a> shows the actual message.</li>
<li><strong>A programmable network proxy</strong> — rewriting requests, adding headers, or stubbing responses in development.</li>
</ul>
<p>When it is not the right tool:</p>
<ul>
<li><strong>You only need to cache HTTP responses.</strong> A plain <code>Cache-Control</code> header on the response is cheaper and works without JavaScript.</li>
<li><strong>You need to do work on every page load.</strong> A service worker only handles events; if the work is on the critical rendering path it belongs in the page itself.</li>
<li><strong>You need access to the DOM.</strong> A service worker has none; build a regular web worker or put the code in the page.</li>
</ul>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="do-service-workers-have-access-to-the-dom">Do service workers have access to the DOM?</h3><p>No. A service worker is a kind of <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API">web worker</a>, which means it runs in its own thread with no access to <code>document</code>, <code>window</code>, <code>localStorage</code>, or any DOM API. The only way to talk to the page is <code>postMessage</code> from the worker to a controlled client, and <code>message</code> events from the page back to the worker. This is deliberate: it forces the worker to be non-blocking and keeps the page safe from anything the cached worker script might try to do to it.</p>
<h3 id="why-does-my-new-service-worker-not-take-effect-immediately">Why does my new service worker not take effect immediately?</h3><p>A page that loaded without a service worker does not start being controlled by one until the next navigation. MDN&#39;s <a href="https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API">Service Worker API</a> page calls this out explicitly: &quot;by default, a page&#39;s fetches won&#39;t go through a service worker unless the page request itself went through a service worker.&quot; The first registration only takes effect on the next page load, on the next open of the site, or after a refresh. If you need immediate control, call <code>clients.claim()</code> from inside the worker&#39;s <code>activate</code> event.</p>
<h3 id="can-i-use-a-service-worker-on-plain-http">Can I use a service worker on plain HTTP?</h3><p>No, except in two narrow cases. Service workers only register on <a href="https://developer.mozilla.org/en-US/docs/Web/Security/Defenses/Secure_Contexts">secure contexts</a> — origins served over HTTPS. The exception that matters for development is <code>http://localhost</code>: browsers treat localhost as a secure origin so you can test service workers without setting up a certificate. The other exception is browser-internal flags (Firefox has a developer-tools checkbox to allow service workers over HTTP) for debugging only; do not ship a public site over plain HTTP. Service workers over HTTP would be trivially exploitable by network attackers.</p>
<h3 id="what-is-the-difference-between-a-service-worker-and-a-web-worker">What is the difference between a service worker and a web worker?</h3><p>A regular <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API">web worker</a> is a script you spawn from a page with <code>new Worker(&#39;foo.js&#39;)</code>; it lives as long as the spawning page is open, communicates with the page over <code>postMessage</code>, and is destroyed when the page closes. A service worker is registered once with <code>navigator.serviceWorker.register(...)</code>, persists independently of any page, runs events fired by the browser (<code>fetch</code>, <code>push</code>, <code>sync</code>, <code>message</code>), and can outlive every page that registered it. Both are off-main-thread, both have no DOM access, but a service worker is the durable, event-driven one.</p>
</section>
<section><h2 id="summary"><a href="#summary" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Summary</h2><p>A service worker is a small, persistent JavaScript file that lives on the user&#39;s device, sits between the page and the network, and lets the page work offline, load faster, and receive push events. The lifecycle — <code>install</code>, <code>activate</code>, then continuous <code>fetch</code> and other events — is the most subtle part of the API, and it is the part that catches most teams the first time. Once you understand that a new worker does not interrupt an in-progress page and that pages loaded without a worker do not start using one until the next navigation, the rest of the API falls into place: pre-cache on <code>install</code>, clean up old caches on <code>activate</code>, and <code>respondWith</code> from <code>fetch</code> to be the proxy the browser is waiting for.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>What is serverless computing? How serverless and FaaS work</title>
      <link>https://debod.id/en/posts/serverless-computing-guide/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/serverless-computing-guide/</guid>
      <pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate>
      <category>devops</category>
      <category>cloud-computing</category>
      <category>devops</category>
      <description>Serverless computing is a cloud service category where the customer provides application code or data and does not provision, deploy, or manage the underlying hardware or software resources. Functions as a Service (FaaS) is the core execution model: you upload a function and the platform runs it, scales it, and bills you only for actual execution time. This guide explains how serverless works, its benefits and trade-offs, the common anti-patterns, and when it is the right choice.</description>
      <content:encoded><![CDATA[<section><p>Serverless computing is a cloud service category where you provide application code or data, and you do not provision, deploy, or manage the underlying hardware or software resources. Instead of renting a server you maintain, you upload a function and the platform runs it, scales it automatically, and bills you only for the actual execution. This guide explains how serverless works, the Function-as-a-Service model, its benefits and trade-offs, the anti-patterns to avoid, and when it is the right choice for your workload.</p></section>
<section><h2 id="what-is-serverless-computing"><a href="#what-is-serverless-computing" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What is serverless computing?</h2><p>The term &quot;serverless&quot; comes from the fact that, as a developer, you never touch a server. According to the ISO/IEC 22123-2 cloud-computing standard, serverless computing is &quot;a cloud service category where the customer can use different cloud capability types without the customer having to provision, deploy and manage either hardware or software resources, other than providing customer application code or providing customer data.&quot; It is a form of virtualized computing.</p>
<p>Despite the name, servers absolutely still exist — the cloud provider runs your code on them. &quot;Serverless&quot; is a misnomer in the sense that servers are still used to execute code; the difference is that you, the customer, never provision or maintain them. The provider handles capacity, patching, and availability.</p>
<p>Serverless is a broad ecosystem. It includes the cloud provider, Functions as a Service (FaaS), managed services, tools, and frameworks. It does not remove complexity so much as shift it: much operational work moves from an operations team to the development team, though operations still manages identity and access, networking, security policies, and cost optimization.</p>
</section>
<section><h2 id="how-functions-as-a-service-faas-works"><a href="#how-functions-as-a-service-faas-works" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How Functions as a Service (FaaS) works</h2><p>The core execution model behind most serverless applications is Functions as a Service. You write a single unit of business logic — a function — and deploy it to a FaaS platform such as AWS Lambda, Azure Functions, Google Cloud Functions, or Cloudflare Workers. The platform takes care of running it.</p>
<p>A typical mental model:</p>
<ul>
<li><strong>You write and upload a function</strong> — just application code, no web server, no container orchestration.</li>
<li><strong>The platform handles invocation</strong> — an event (an HTTP request, a file upload, a message in a queue, a scheduled timer) triggers the function.</li>
<li><strong>The platform scales it automatically</strong> — from zero to millions of invocations, spinning instances up and down as demand changes. There is no capacity planning and no load balancer to configure.</li>
<li><strong>You pay per execution</strong> — billed on the actual time your code runs (CPU time), not for idle machines waiting around.</li>
</ul>
<p>This is why serverless suits event-driven and bursty workloads: you only pay for the compute you actually use, and scale is handled for you.</p>
</section>
<section><h2 id="key-benefits"><a href="#key-benefits" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Key benefits</h2><ul>
<li><strong>No infrastructure management.</strong> No servers to patch, no capacity to plan, no regional configuration. The provider owns availability and scaling.</li>
<li><strong>Automatic scaling.</strong> Functions scale from zero to millions of requests automatically, matching demand in real time.</li>
<li><strong>Pay-per-use pricing.</strong> You are billed for execution time, not for idle provisioned capacity. Quiet periods cost almost nothing.</li>
<li><strong>Faster delivery.</strong> Because deployment is just uploading code and wiring an event, teams can ship small units quickly.</li>
</ul>
</section>
<section><h2 id="challenges-and-trade-offs"><a href="#challenges-and-trade-offs" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Challenges and trade-offs</h2><p>Serverless is not free of cost — it trades infrastructure concerns for a different set of problems.</p>
<ul>
<li><strong>Monitoring and debugging are harder.</strong> Serverless applications are distributed and event-driven, and run in proprietary environments. Tracking an execution flow across many function invocations is harder than reading a single server&#39;s logs. Distributed tracing (for example AWS X-Ray), centralized logging, and OpenTelemetry are typical mitigations.</li>
<li><strong>Security has its own shape.</strong> OWASP notes that serverless applications are vulnerable to variations of traditional attacks plus serverless-specific ones, including &quot;denial of wallet&quot; — an attacker triggering many executions to inflate your bill. Insecure code and over-permissive permissions are common risks.</li>
<li><strong>Vendor lock-in.</strong> Applications are locked to a specific provider by default. Because of the high level of abstraction, you cannot configure the underlying environment, and a provider&#39;s managed services often only trigger that same provider&#39;s functions. Migrating a serverless workload to another cloud is difficult; designing from the start with multi-cloud in mind helps.</li>
<li><strong>Limits for sustained or heavy compute.</strong> High-performance or long-running workloads can hit resource limits (maximum memory, CPU, runtime). For sustained, predictable resource usage, bulk-provisioned servers are often more cost-effective than a per-execution model.</li>
</ul>
</section>
<section><h2 id="anti-patterns-to-avoid"><a href="#anti-patterns-to-avoid" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Anti-patterns to avoid</h2><ul>
<li><strong>Grain of sand.</strong> Making functions excessively small increases complexity, operational overhead, and performance inefficiency. There is an optimal level of modularization — split functions along real, stable boundaries, not into the smallest possible pieces.</li>
<li><strong>Lambda pinball / distributed monolith.</strong> When functions invoke each other in long fragmented chains, you get added latency, harder debugging, and reduced observability — effectively a distributed monolith. Prefer clear boundaries between functions and native service integrations over long chains of inter-function calls.</li>
</ul>
</section>
<section><h2 id="when-should-you-use-serverless"><a href="#when-should-you-use-serverless" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>When should you use serverless?</h2><p>Serverless is a good fit when:</p>
<ul>
<li>Your workload is <strong>event-driven</strong> — webhooks, queue messages, scheduled jobs, image or file processing.</li>
<li>Your traffic is <strong>bursty or unpredictable</strong> — spikes that would wastefully idle always-on servers.</li>
<li>You want to <strong>ship small units quickly</strong> without managing infrastructure.</li>
<li>You can tolerate <strong>provider lock-in</strong> or design for portability from the start.</li>
</ul>
<p>It is a weaker fit when:</p>
<ul>
<li>You need <strong>sustained, predictable, high-volume compute</strong> where a fixed server is cheaper.</li>
<li>You run <strong>long-running or resource-heavy</strong> workloads close to platform limits.</li>
<li>You have <strong>strict multi-cloud or portability</strong> requirements and cannot accept lock-in.</li>
</ul>
<p>As a rule of thumb, reach for serverless when the platform&#39;s automatic scale and pay-per-use model align with an event-driven workload — and prefer fixed servers when you have steady load that would run 24/7 anyway.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-does-serverless-actually-mean">What does &quot;serverless&quot; actually mean?</h3><p>Serverless means you, the developer, never provision or manage servers. The cloud provider runs your application code and handles capacity, scaling, and availability. The name is a slight misnomer — servers still exist — but they are entirely managed by the provider on your behalf.</p>
<h3 id="what-is-function-as-a-service-faas">What is Function as a Service (FaaS)?</h3><p>FaaS is the core execution model of serverless. You deploy a single function to a platform (such as AWS Lambda, Azure Functions, Google Cloud Functions, or Cloudflare Workers), the platform invokes it on events, scales it automatically, and bills you for actual execution time rather than idle capacity.</p>
<h3 id="is-serverless-cheaper-than-traditional-servers">Is serverless cheaper than traditional servers?</h3><p>Not always. For event-driven or bursty workloads, pay-per-use is often cheaper because you stop paying for idle capacity. But for sustained, predictable, high-volume compute, a traditional provisioned server or VM can be more cost-effective.</p>
<h3 id="what-are-the-main-downsides-of-serverless">What are the main downsides of serverless?</h3><p>The main trade-offs are harder monitoring and debugging in distributed environments, a different security surface (including denial-of-wallet attacks), strong vendor lock-in that makes migration difficult, and resource limits that can hurt long-running or heavy compute workloads.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>REST vs GraphQL: which API style should you choose?</title>
      <link>https://debod.id/en/posts/rest-vs-graphql/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/rest-vs-graphql/</guid>
      <pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate>
      <category>development</category>
      <category>rest</category>
      <category>graphql</category>
      <category>api</category>
      <category>http</category>
      <category>backend</category>
      <category>web-development</category>
      <description>REST and GraphQL are two ways to build web APIs. REST uses fixed endpoints and HTTP methods; GraphQL uses a single endpoint where clients query exactly the fields they need. Compare the trade-offs and learn how to choose.</description>
      <content:encoded><![CDATA[<section><p>REST and GraphQL are the two dominant styles for building web APIs, and they answer the same question — how should a client get data from a server? — in very different ways. REST exposes a set of fixed endpoints, one per resource, that you call with HTTP methods like GET, POST, PUT, and DELETE. GraphQL exposes a single endpoint where the client sends a query describing exactly the fields it wants and gets back a JSON result that mirrors that query. Neither is universally better: each fits different teams, workloads, and client shapes. This guide compares them across the trade-offs that actually matter and gives you a decision path.</p></section>
<section><h2 id="what-rest-is"><a href="#what-rest-is" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What REST is</h2><p>REST (Representational State Transfer) is an architectural style introduced by Roy Fielding in his doctoral dissertation in 2000, built around the constraints that shaped the World Wide Web. An application that adheres to its constraints is called RESTful. In practice, when developers say &quot;REST API,&quot; they usually mean an HTTP-based API organized around <strong>resources</strong>: each thing your system manages (users, orders, products) gets one or more URLs, and you act on it with HTTP methods.</p>
<p>The core idea is the <strong>uniform interface</strong>: your API exposes a stable set of resources identified by URLs, clients interact with them through standardized HTTP verbs, and responses contain representations of those resources.</p>
<p>The HTTP methods a REST API uses have well-defined semantics:</p>
<ul>
<li><strong>GET</strong> requests a representation of the specified resource and should only retrieve data.</li>
<li><strong>POST</strong> submits an entity, often changing state or causing side effects.</li>
<li><strong>PUT</strong> replaces the target resource with the request content.</li>
<li><strong>PATCH</strong> applies partial modifications to a resource.</li>
<li><strong>DELETE</strong> deletes the specified resource.</li>
</ul>
<p>These methods also have guarantees that matter for clients. GET, HEAD, and OPTIONS are <strong>safe</strong> (they do not change server state) and GET and HEAD are <strong>cacheable</strong>. PUT and DELETE are <strong>idempotent</strong> (repeating them has the same effect as doing them once). Understanding these properties is part of what makes a REST API predictable and cacheable at every layer, from the browser to a CDN.</p>
</section>
<section><h2 id="what-graphql-is"><a href="#what-graphql-is" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What GraphQL is</h2><p>GraphQL is a query language and runtime developed at Facebook, released as an open-source specification in 2015, and moved to the GraphQL Foundation under the Linux Foundation in 2018. Instead of many fixed endpoints, a GraphQL API exposes <strong>one endpoint</strong> and a typed <strong>schema</strong>. The schema describes every type and field the API can return, and clients send queries that ask for exactly the data they need.</p>
<p>A GraphQL query mirrors the shape of the result. If a client wants a user&#39;s name and only their latest posts, it writes that shape directly in the query, and the server returns JSON that follows it. The client drives the response; the server does not decide which fields to include. This fixes two problems common in REST:</p>
<ul>
<li><strong>Over-fetching</strong> — a REST endpoint returns everything on a resource even when the client needs one field.</li>
<li><strong>Under-fetching</strong> — a REST client must make several requests (one per resource) or be given a bespoke endpoint to assemble a single view.</li>
</ul>
<p>GraphQL supports reading data (queries), changing data (mutations), and real-time updates (subscriptions, often built on WebSockets). It is statically typed: the schema is the contract between client and server, and clients can validate queries against it before they run.</p>
</section>
<section><h2 id="the-key-differences"><a href="#the-key-differences" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The key differences</h2><table>
<thead>
<tr>
<th>Aspect</th>
<th>REST</th>
<th>GraphQL</th>
</tr>
</thead>
<tbody><tr>
<td>Endpoints</td>
<td>Many — one (or more) per resource</td>
<td>One endpoint for everything</td>
</tr>
<tr>
<td>How you request data</td>
<td>Fixed response per URL; HTTP methods select the action</td>
<td>Client writes a query for exactly the fields it needs</td>
</tr>
<tr>
<td>Data shape</td>
<td>Server decides; may over- or under-fetch</td>
<td>Client decides; result mirrors the query</td>
</tr>
<tr>
<td>Contract</td>
<td>URLs + HTTP methods + media types</td>
<td>Typed schema (single source of truth)</td>
</tr>
<tr>
<td>Caching</td>
<td>Great — HTTP caching works across all layers (CDN, browser)</td>
<td>Harder; usually need a GraphQL-specific cache</td>
</tr>
<tr>
<td>Versioning</td>
<td>Common approach is <code>/v1</code>, <code>/v2</code> paths</td>
<td>Schema evolves; fields can be added or deprecated</td>
</tr>
<tr>
<td>File uploads</td>
<td>Standard (multipart)</td>
<td>Needs extensions or workarounds</td>
</tr>
<tr>
<td>Tooling</td>
<td>Mature across every language</td>
<td>Strong client-side tools (Apollo, Relay, GraphiQL)</td>
</tr>
</tbody></table>
</section>
<section><h2 id="when-rest-is-the-better-choice"><a href="#when-rest-is-the-better-choice" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>When REST is the better choice</h2><p>REST shines when its strengths map cleanly to your situation:</p>
<ul>
<li><strong>Simple, resource-shaped APIs</strong> — if your data maps naturally to a handful of resources and clients mostly fetch whole objects, REST is uncomplicated and boring in the best way.</li>
<li><strong>Heavy caching and CDN use</strong> — because GET is cacheable and responses are per-URL, you can cache aggressively at the CDN, the proxy, and the browser, which is powerful for public, read-heavy content.</li>
<li><strong>Public APIs with unknown clients</strong> — the HTTP semantics (safe, idempotent, status codes) are well understood by every client and tool, and web infrastructure works with them out of the box.</li>
<li><strong>File uploads and simple integrations</strong> — multipart uploads, webhooks, and browser-native forms all work with REST without extensions.</li>
<li><strong>Long-lived, high-traffic services</strong> — a simple, stable REST surface is easy to cache and cheap to operate.</li>
</ul>
<p>Many teams never outgrow REST. If your API has a few resources, consistent shapes, and modest client requirements, REST gives you caching, tooling, and simplicity for free.</p>
</section>
<section><h2 id="when-graphql-is-the-better-choice"><a href="#when-graphql-is-the-better-choice" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>When GraphQL is the better choice</h2><p>GraphQL earns its complexity when REST&#39;s fixed shapes become the bottleneck:</p>
<ul>
<li><strong>Many different clients, very different data needs</strong> — a mobile app wants small, specific payloads; a dashboard wants wide ones. GraphQL lets each client fetch exactly what it needs from one endpoint.</li>
<li><strong>Client-driven, nested data</strong> — when a view needs a user, their posts, and the comments on those posts, GraphQL assembles the whole graph in one query instead of making several round-trips.</li>
<li><strong>Rapidly evolving product</strong> — adding and deprecating fields in the schema is safer than contract changes across many endpoints.</li>
<li><strong>Strong typed contract as a team tool</strong> — one schema doubles as documentation, and clients can validate queries before they run.</li>
<li><strong>Multiple back-end services behind one API</strong> — resolvers can aggregate data from different sources and present it as a unified graph.</li>
</ul>
<p>GraphQL makes the most sense when you control both client and server, clients actively, and the data shape is graph-like — which is exactly the situation where REST APIs tend to grow a pile of bespoke, one-off endpoints.</p>
</section>
<section><h2 id="the-honest-downsides"><a href="#the-honest-downsides" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The honest downsides</h2><p>Both styles have real costs, and choosing well means seeing the other side.</p>
<p>GraphQL is harder to cache. Because every query is different and all requests hit one endpoint, HTTP and CDN caching that works trivially for REST does not directly apply. Teams often add a caching layer (like Apollo or a CDN edge cache designed for GraphQL) and must think about per-query caching and the <strong>N+1 problem</strong> — where resolving a list triggers one query per item unless you batch and deduplicate with a data loader. A carelessly built GraphQL server can also produce heavier queries than intended, so query complexity limits and timeouts matter.</p>
<p>REST&#39;s weaknesses are the flip side of its simplicity. Clients are at the mercy of the server&#39;s chosen shapes: you over-fetch or under-fetch unless you create custom endpoints. Nested data often means several round-trips. Versioning is usually path-based, which leaves old endpoints around. And when different clients need very different views of the same resources, REST surfaces tend to drift from &quot;one clean endpoint per resource&quot; toward a graveyard of special-purpose endpoints.</p>
</section>
<section><h2 id="how-to-decide"><a href="#how-to-decide" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How to decide</h2><p>There is no universal winner — it depends on your clients, your team, and your operationally practical realities:</p>
<ul>
<li><strong>Pick REST when</strong> you have a public or simple API, heavy read traffic, need CDN/browser caching, process file uploads, or value boring predictability.</li>
<li><strong>Pick GraphQL when</strong> you control the clients, they have diverse and nested data needs, your product evolves quickly, and you can invest in tooling and caching up front.</li>
<li><strong>Both can coexist</strong> — many teams run a REST API for public, cacheable read paths and a thin GraphQL layer for rich internal clients.</li>
</ul>
<p>If you are unsure, start simple. REST is the safer default for most new APIs; adopt GraphQL when specific, repeated pain — over-fetching, many round-trips, or a broad multi-client product — makes the investment clearly worth it. The wrong choice is rarely permanent: the two styles can live side by side and you can migrate gradually.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="is-graphql-a-replacement-for-rest">Is GraphQL a replacement for REST?</h3><p>GraphQL is an alternative way to expose an API, not a drop-in replacement that is always better. It removes fixed per-resource endpoints and lets clients query exactly what they need from one typed endpoint. For many use cases REST is still a simpler and better-cached choice; for others GraphQL fixes real pain. Both can serve the same backend.</p>
<h3 id="can-you-use-graphql-and-rest-together">Can you use GraphQL and REST together?</h3><p>Yes, and many teams do. A common pattern is keeping a REST API for public, cacheable, or simple read paths and adding a GraphQL layer for rich internal clients, or exposing both against the same data. They are not mutually exclusive, and you can migrate a service gradually rather than rewriting it.</p>
<h3 id="does-graphql-use-http">Does GraphQL use HTTP?</h3><p>GraphQL conventionally runs over HTTP with a single endpoint, usually accepting POST requests carrying the query, and it returns JSON. Under the hood it still uses HTTP, but it does not reuse standard HTTP caching and the per-resource URL semantics the way REST does, which is one reason caching is handled differently.</p>
<h3 id="which-is-faster-rest-or-graphql">Which is faster: REST or GraphQL?</h3><p>There is no inherent speed advantage — both run over HTTP. The practical difference is in the number of round-trips and the amount of data transferred. GraphQL can reduce both for nested, client-specific views. REST wins where HTTP caching means far fewer requests actually reach the server. Latency depends more on your server and caching than on the style itself.</p>
</section>
<section><h2 id="verdict"><a href="#verdict" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Verdict</h2><p>REST and GraphQL solve the same problem with opposite trade-offs, and the right answer depends on where you feel the pain. Choose REST when you want simple, resource-based endpoints plus free CDN and browser caching — the sensible default for most public and read-heavy APIs. Choose GraphQL when you control the clients, they need diverse and nested data, and the product evolves quickly enough that fixed shapes hurt. Start simple, adopt GraphQL deliberately when a concrete problem demands it, and remember the two styles coexist: you are not locked into one forever.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>How to build a REST API with Node.js and Express</title>
      <link>https://debod.id/en/posts/rest-api-nodejs-express/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/rest-api-nodejs-express/</guid>
      <pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate>
      <category>development</category>
      <category>nodejs</category>
      <category>express</category>
      <category>rest</category>
      <category>api</category>
      <category>javascript</category>
      <category>backend</category>
      <description>Learn to create a production-ready RESTful API using Node.js and Express: setup, routing, middleware, environment variables, error handling, and deployment best practices — with a practical CRUD example for managing resources.</description>
      <content:encoded><![CDATA[<section><p>Express is the most popular web framework for Node.js, providing a minimal, unopinionated layer for building web servers and APIs. Combined with Node.js&#39;s efficient I/O model, Express enables developers to create fast, scalable RESTful services with straightforward routing and middleware. This guide shows how to build a production-ready REST API using Node.js and Express, covering project setup, core concepts, environment configuration, error handling, and deployment practices — illustrated with a practical CRUD API for managing a collection of books.</p></section>
<section><h2 id="why-node-js-and-express"><a href="#why-node-js-and-express" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Why Node.js and Express?</h2><p>Node.js is a server-side JavaScript runtime built on Chrome&#39;s V8 engine, designed for building scalable network applications. Its event-driven, non-blocking I/O model makes it efficient for data-intensive real-time applications. Express, first released in 2010, has become the de facto standard web framework for Node.js due to its simplicity, flexibility, and massive ecosystem. As of September 2024, Express 5.0.0 introduced modern async/await support and improved routing while maintaining backward compatibility for most middleware. The framework remains actively maintained, with the 4.x line receiving long-term support alongside the current 5.x releases.</p>
<p>Understanding REST principles is essential before coding. REST (Representational State Transfer) is an architectural style for networked applications that uses standard HTTP methods (GET, POST, PUT, DELETE) and status codes to manipulate resources identified by URLs. A RESTful API adheres to constraints like statelessness, cacheability, and a uniform interface, making it predictable and easy to consume.</p>
</section>
<section><h2 id="project-setup"><a href="#project-setup" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Project Setup</h2><p>Begin by initializing a new Node.js project and installing Express:</p>
<pre><code class="language-bash">mkdir book-api &amp;&amp; cd book-api
npm init -y
npm install express
</code></pre>
<p>For development convenience, also install nodemon for automatic restarts and dotenv for environment variable management:</p>
<pre><code class="language-bash">npm install --save-dev nodemon dotenv
</code></pre>
<p>Create the project structure:</p>
<pre><code>book-api/
├── src/
│   ├── controllers/
│   ├── middleware/
│   ├── routes/
│   ├── models/
│   └── app.js
├── .env
├── .gitignore
├── package.json
└── server.js
</code></pre>
</section>
<section><h2 id="basic-server-implementation"><a href="#basic-server-implementation" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Basic Server Implementation</h2><p>Create <code>src/app.js</code> to configure the Express application:</p>
<pre><code class="language-javascript">const express = require(&#39;express&#39;);
const app = express();

// Middleware to parse JSON bodies
app.use(express.json());

// Middleware to parse URL-encoded bodies (for form data)
app.use(express.urlencoded({ extended: true }));

// Basic route
app.get(&#39;/&#39;, (req, res) =&gt; {
  res.json({ message: &#39;Welcome to the Book API&#39; });
});

module.exports = app;
</code></pre>
<p>Create <code>src/server.js</code> to start the server:</p>
<pre><code class="language-javascript">const app = require(&#39;./app&#39;);
const PORT = process.env.PORT || 3000;

app.listen(PORT, () =&gt; {
  console.log(`Server running on port ${PORT}`);
});
</code></pre>
<p>Update <code>package.json</code> to include a dev script:</p>
<pre><code class="language-json">&quot;scripts&quot;: {
  &quot;dev&quot;: &quot;nodemon src/server.js&quot;,
  &quot;start&quot;: &quot;node src/server.js&quot;
}
</code></pre>
<p>Run <code>npm dev</code> to start the server in development mode. Visiting <code>http://localhost:3000</code> should return the welcome message.</p>
</section>
<section><h2 id="defining-routes-and-controllers"><a href="#defining-routes-and-controllers" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Defining Routes and Controllers</h2><p>REST APIs organize functionality around resources. For a book collection, typical endpoints include:</p>
<ul>
<li><code>GET /books</code> — Retrieve all books</li>
<li><code>GET /books/:id</code> — Retrieve a specific book</li>
<li><code>POST /books</code> — Create a new book</li>
<li><code>PUT /books/:id</code> — Update a book</li>
<li><code>DELETE /books/:id</code> — Delete a book</li>
</ul>
<p>Create <code>src/controllers/bookController.js</code>:</p>
<pre><code class="language-javascript">// In-memory store for demonstration (use a database in production)
let books = [];
let nextId = 1;

exports.getAllBooks = (req, res) =&gt; {
  res.json(books);
};

exports.getBookById = (req, res) =&gt; {
  const book = books.find(b =&gt; b.id === parseInt(req.params.id));
  if (!book) return res.status(404).json({ error: &#39;Book not found&#39; });
  res.json(book);
};

exports.createBook = (req, res) =&gt; {
  const { title, author, publishedYear } = req.body;
  if (!title || !author) {
    return res.status(400).json({ error: &#39;Title and author are required&#39; });
  }
  const newBook = {
    id: nextId++,
    title,
    author,
    publishedYear: publishedYear || null
  };
  books.push(newBook);
  res.status(201).json(newBook);
};

exports.updateBook = (req, res) =&gt; {
  const book = books.find(b =&gt; b.id === parseInt(req.params.id));
  if (!book) return res.status(404).json({ error: &#39;Book not found&#39; });
  const { title, author, publishedYear } = req.body;
  if (title !== undefined) book.title = title;
  if (author !== undefined) book.author = author;
  if (publishedYear !== undefined) book.publishedYear = publishedYear;
  res.json(book);
};

exports.deleteBook = (req, res) =&gt; {
  const index = books.findIndex(b =&gt; b.id === parseInt(req.params.id));
  if (index === -1) return res.status(404).json({ error: &#39;Book not found&#39; });
  books.splice(index, 1);
  res.status(204).send();
};
</code></pre>
<p>Create <code>src/routes/bookRoutes.js</code>:</p>
<pre><code class="language-javascript">const express = require(&#39;express&#39;);
const router = express.Router();
const bookController = require(&#39;../controllers/bookController&#39;);

router.get(&#39;/&#39;, bookController.getAllBooks);
router.get(&#39;/:id&#39;, bookController.getBookById);
router.post(&#39;/&#39;, bookController.createBook);
router.put(&#39;/:id&#39;, bookController.updateBook);
router.delete(&#39;/:id&#39;, bookController.deleteBook);

module.exports = router;
</code></pre>
<p>Register the routes in <code>src/app.js</code>:</p>
<pre><code class="language-javascript">const express = require(&#39;express&#39;);
const app = express();

app.use(express.json());
app.use(express.urlencoded({ extended: true }));

const bookRoutes = require(&#39;./routes/bookRoutes&#39;);
app.use(&#39;/books&#39;, bookRoutes);

app.get(&#39;/&#39;, (req, res) =&gt; {
  res.json({ message: &#39;Welcome to the Book API&#39; });
});

module.exports = app;
</code></pre>
</section>
<section><h2 id="essential-middleware"><a href="#essential-middleware" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Essential Middleware</h2><p>Production APIs benefit from middleware that enhances security, logging, and functionality.</p>
<h3 id="cors-cross-origin-resource-sharing">CORS (Cross-Origin Resource Sharing)</h3><p>If your API will be accessed from different domains (e.g., a frontend hosted separately), enable CORS:</p>
<pre><code class="language-bash">npm install cors
</code></pre>
<pre><code class="language-javascript">const cors = require(&#39;cors&#39;);
app.use(cors()); // Allows all origins — configure restrictively in production
</code></pre>
<h3 id="security-headers">Security Headers</h3><p>Use Helmet to set various HTTP headers for security:</p>
<pre><code class="language-bash">npm install helmet
</code></pre>
<pre><code class="language-javascript">const helmet = require(&#39;helmet&#39;);
app.use(helmet());
</code></pre>
<h3 id="request-logging">Request Logging</h3><p>Log incoming requests for debugging and monitoring:</p>
<pre><code class="language-javascript">app.use((req, res, next) =&gt; {
  console.log(`${req.method} ${req.path} - ${new Date().toISOString()}`);
  next();
});
</code></pre>
</section>
<section><h2 id="environment-variables"><a href="#environment-variables" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Environment Variables</h2><p>Store configuration like port, database URLs, and API keys in environment variables using dotenv. Create <code>.env</code>:</p>
<pre><code>PORT=3000
NODE_ENV=development
</code></pre>
<p>Load them in <code>src/server.js</code>:</p>
<pre><code class="language-javascript">require(&#39;dotenv&#39;).config();
const app = require(&#39;./app&#39;);
const PORT = process.env.PORT || 3000;

app.listen(PORT, () =&gt; {
  console.log(`Server running in ${process.env.NODE_ENV} mode on port ${PORT}`);
});
</code></pre>
<p>Add <code>.env</code> to <code>.gitignore</code> to prevent committing sensitive data:</p>
<pre><code>node_modules/
.env
.env.*
</code></pre>
</section>
<section><h2 id="error-handling"><a href="#error-handling" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Error Handling</h2><p>Implement centralized error handling to catch and format errors consistently. Add this middleware <em>after</em> all routes:</p>
<pre><code class="language-javascript">// 404 handler
app.use((req, res) =&gt; {
  res.status(404).json({ error: &#39;Route not found&#39; });
});

// Global error handler
app.use((err, req, res, next) =&gt; {
  console.error(err.stack);
  const status = err.status || 500;
  res.status(status).json({
    error: {
      message: err.message || &#39;Internal Server Error&#39;,
      ...(process.env.NODE_ENV === &#39;development&#39; &amp;&amp; { stack: err.stack })
    }
  });
});
</code></pre>
<p>In controllers, pass errors to <code>next(err)</code> for asynchronous operations:</p>
<pre><code class="language-javascript">exports.createBook = async (req, res, next) =&gt; {
  try {
    // ... logic that might throw
  } catch (error) {
    next(error);
  }
};
</code></pre>
</section>
<section><h2 id="running-in-production"><a href="#running-in-production" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Running in Production</h2><p>For production, use a process manager like PM2 to keep the application running and handle clustering:</p>
<pre><code class="language-bash">npm install -g pm2
pm2 start src/server.js --name book-api
</code></pre>
<p>Ensure <code>NODE_ENV=production</code> in your environment to disable development-specific logging and enable optimizations. Use a reverse proxy like Nginx for SSL termination, load balancing, and serving static files.</p>
</section>
<section><h2 id="testing-the-api"><a href="#testing-the-api" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Testing the API</h2><p>Test endpoints using tools like curl, HTTPie, or Postman:</p>
<pre><code class="language-bash"># Get all books
curl http://localhost:3000/books

# Create a book
curl -X POST http://localhost:3000/books \
  -H &quot;Content-Type: application/json&quot; \
  -d &#39;{&quot;title&quot;:&quot;The Great Gatsby&quot;,&quot;author&quot;:&quot;F. Scott Fitzgerald&quot;,&quot;publishedYear&quot;:1925}&#39;

# Get a specific book
curl http://localhost:3000/books/1

# Update a book
curl -X PUT http://localhost:3000/books/1 \
  -H &quot;Content-Type: application/json&quot; \
  -d &#39;{&quot;title&quot;:&quot;Great Gatsby&quot;}&#39;

# Delete a book
curl -X DELETE http://localhost:3000/books/1
</code></pre>
</section>
<section><h2 id="common-best-practices"><a href="#common-best-practices" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Common Best Practices</h2><ol>
<li><strong>Use HTTP status codes correctly</strong>: 200 (OK), 201 (Created), 204 (No Content), 400 (Bad Request), 401 (Unauthorized), 403 (Forbidden), 404 (Not Found), 409 (Conflict), 500 (Internal Server Error).</li>
<li><strong>Validate input</strong>: Always validate request data (body, params, query) before processing.</li>
<li><strong>Keep controllers thin</strong>: Move business logic to service layers or models.</li>
<li><strong>Use asynchronous handling</strong>: Prefer async/await with try/catch or promises; avoid blocking the event loop.</li>
<li><strong>Secure sensitive data</strong>: Never hardcode secrets; use environment variables and secrets managers.</li>
<li><strong>Enable compression</strong>: Use compression middleware (e.g., compression) to reduce response sizes.</li>
<li><strong>Implement rate limiting</strong>: Protect against abuse with rate-limiter middleware.</li>
<li><strong>Log effectively</strong>: Use structured logging (e.g., winston, pino) for production monitoring.</li>
<li><strong>Document your API</strong>: Use OpenAPI/Swagger to generate interactive documentation.</li>
<li><strong>Write tests</strong>: Implement unit and integration tests with frameworks like Jest and Supertest.</li>
</ol>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-is-the-difference-between-express-4-x-and-5-x">What is the difference between Express 4.x and 5.x?</h3><p>Express 5.x introduces native support for async/await in route handlers and middleware, eliminates certain deprecated APIs (like <code>app.param(fn)</code>), and improves error handling for asynchronous operations. The 4.x line remains under long-term support for stability-critical applications.</p>
<h3 id="do-i-need-a-database-to-build-a-rest-api">Do I need a database to build a REST API?</h3><p>No — you can start with in-memory storage or flat files for learning or prototyping. For production, integrate a database (e.g., PostgreSQL, MongoDB) using an ORM (like Sequelize or Prisma) or a query builder.</p>
<h3 id="how-do-i-handle-authentication-in-an-express-api">How do I handle authentication in an Express API?</h3><p>Common strategies include JSON Web Tokens (JWT), session-based authentication, or OAuth 2.0. Libraries like jsonwebtoken, passport, or express-jwt simplify implementation. Always use HTTPS in production to protect tokens.</p>
<h3 id="can-i-use-typescript-with-express">Can I use TypeScript with Express?</h3><p>Yes. Express officially supports TypeScript via <code>@types/express</code>. Initialize a TypeScript project with <code>tsc --init</code>, install type definitions, and rename <code>.js</code> files to <code>.ts</code>. Ensure <code>esModuleInterop</code> is enabled in <code>tsconfig.json</code>.</p>
<h3 id="how-should-i-structure-a-larger-express-application">How should I structure a larger Express application?</h3><p>As your API grows, adopt a layered structure: routes (handle HTTP), controllers (logic), services (business rules), models (data access), and middleware (cross-cutting concerns). Keep each file focused on a single responsibility.</p>
</section>
<section><h2 id="summary"><a href="#summary" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Summary</h2><p>Building a REST API with Node.js and Express involves setting up the server, defining RESTful routes, applying essential middleware, managing configuration via environment variables, handling errors gracefully, and following production best practices. By leveraging Express&#39;s minimalist core and Node.js&#39;s efficient runtime, developers can create scalable, maintainable APIs suitable for everything from small projects to enterprise systems. Remember to validate inputs, secure your endpoints, and monitor performance as your API evolves.</p>
</section>
<section><h2 id="sources"><a href="#sources" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Sources</h2><ul>
<li>MDN Web Docs: Express/Node introduction (CC-BY-SA)</li>
<li>Express.js Documentation: Guide (MIT License)</li>
<li>Node.js Documentation: About Node.js (MIT License)</li>
<li>Wikipedia: Representational state transfer (CC-BY-SA)</li>
<li>GitHub: Express repository (MIT License)</li>
</ul>
</section>]]></content:encoded>
    </item>
    <item>
      <title>What is responsive web design? How it works (media queries, fluid grids, images)</title>
      <link>https://debod.id/en/posts/responsive-web-design/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/responsive-web-design/</guid>
      <pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate>
      <category>frontend</category>
      <category>web</category>
      <category>frontend</category>
      <category>html</category>
      <description>Responsive web design (RWD) makes pages render well on every screen size. Learn how media queries, fluid grids, fluid images, and the viewport meta tag work together.</description>
      <content:encoded><![CDATA[<section><p><strong>Responsive web design (RWD)</strong> is a web design approach that makes pages render well on all screen sizes and resolutions while keeping good usability. Instead of building one layout for desktop and another for mobile, you build a single fluid page that adapts to whatever device opens it — a phone, tablet, laptop, or widescreen monitor. The term <em>responsive design</em> was coined by Ethan Marcotte in 2010 to describe building with fluid grids, fluid images, and media queries.</p></section>
<section><h2 id="why-responsive-design-replaced-the-old-approach"><a href="#why-responsive-design-replaced-the-old-approach" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Why responsive design replaced the old approach</h2><p>Before RWD became standard, developers talked about <em>mobile web design</em>. Sites served different experiences based on which device the visitor used — often through server-side browser sniffing that detected the browser type and served a stripped-down page to phones. Mobile devices used to be low-powered and some browsers couldn&#39;t handle CSS or full HTML, so this made sense at the time.</p>
<p>That&#39;s no longer the case. Modern phones handle the same technologies as desktop computers, and there are now many device shapes — laptops, tablets, phones, and watches. Instead of catering to a few screen sizes, you design defensively for common sizes plus unknowns. The goal stays the same as the old &quot;mobile-friendly&quot; approach: good layout, readable content, and reasonable performance on every device.</p>
</section>
<section><h2 id="html-is-fluid-by-default"><a href="#html-is-fluid-by-default" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>HTML is fluid by default</h2><p>Plain HTML is inherently fluid. If you create a page with no CSS and resize the window, the browser automatically reflows the text to fit the viewport. The problem isn&#39;t wrapping — it&#39;s that a page&#39;s <em>design</em> breaks down without help: long lines of text on a wide monitor are hard to read, while a fixed-width design causes horizontal scrollbars on narrow screens and empty space on wide ones.</p>
<p>RWD solves this with three building blocks that Marcotte described in 2010: <strong>fluid grids</strong>, <strong>fluid images</strong>, and <strong>media queries</strong>.</p>
</section>
<section><h2 id="fluid-grids-and-relative-units"><a href="#fluid-grids-and-relative-units" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Fluid grids and relative units</h2><p>A fluid grid uses relative units (percentages, <code>em</code>, <code>rem</code>, viewport units like <code>vw</code>) instead of fixed pixel column widths, so the layout stretches and shrinks with the viewport. Modern CSS layout methods — Flexbox and CSS Grid — are inherently responsive and handle most of this for you, so you rarely hand-build percentage grids anymore.</p>
</section>
<section><h2 id="media-queries-the-condition-based-switch"><a href="#media-queries-the-condition-based-switch" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Media queries: the condition-based switch</h2><p>Media queries let you run tests about the device (for example, viewport width, orientation, or screen vs. print) and apply CSS selectively. They&#39;re initiated with the <code>@media</code> at-rule. Every stylesheet has an implicit media type of <code>all</code> unless you specify one, which means these two blocks are equivalent:</p>
<pre><code class="language-css">body { color: black; background-color: white; }
</code></pre>
<pre><code class="language-css">@media all { body { color: black; background-color: white; } }
</code></pre>
<p>You can target media <em>types</em> like <code>print</code> (useful to stop wasting ink on backgrounds) or <code>screen</code>, and you can add <em>conditions</em> on top — these are called media features. A common one is <code>orientation</code>:</p>
<pre><code class="language-css">@media (orientation: landscape) { /* landscape styles */ }
@media (orientation: portrait) { /* portrait styles */ }
</code></pre>
<p>For responsive design the most useful media feature is viewport width. <code>min-width</code> applies styles when the window is <em>at least</em> a certain width:</p>
<pre><code class="language-css">@media (min-width: 720px) { /* wide-screen styles */ }
</code></pre>
<p><code>max-width</code> applies styles below a width, and you can write the same condition with range syntax:</p>
<pre><code class="language-css">@media (max-width: 400px) { /* narrow styles */ }
@media (width &lt;= 400px) { /* equivalent */ }
</code></pre>
<p>You can combine conditions with <code>and</code>:</p>
<pre><code class="language-css">@media (min-width: 50em) and (max-width: 60em) { /* between 50em and 60em */ }
</code></pre>
<p>The point where a media feature condition becomes true is called a <strong>breakpoint</strong>. Best practice is to choose breakpoints based on your <em>content</em> — for instance, the width at which lines of text become uncomfortably long — rather than on specific device sizes, since popular device sizes change every release cycle. It&#39;s also recommended to use relative units rather than absolute sizes when defining breakpoints.</p>
</section>
<section><h2 id="mobile-first-vs-desktop-first"><a href="#mobile-first-vs-desktop-first" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Mobile-first vs. desktop-first</h2><p>A common approach is <strong>mobile-first</strong>: start with a single-column layout for narrow screens, then add media queries for wider screens only when you know there&#39;s enough room for a multi-column layout. This keeps the base experience simple and fast, and narrow devices never download unnecessary desktop-only layout rules.</p>
</section>
<section><h2 id="the-viewport-meta-tag"><a href="#the-viewport-meta-tag" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The viewport meta tag</h2><p>Mobile browsers once rendered desktop-width pages and squashed them to fit the phone screen, which made everything tiny. The viewport <code>&lt;meta&gt;</code> tag fixes this by telling the browser how wide the page should be. The standard declaration sets the page width to the device&#39;s CSS-pixel width:</p>
<pre><code class="language-html">&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot;&gt;
</code></pre>
<p>The <code>content</code> attribute is a comma-separated list of <code>key=value</code> pairs, including <code>width</code> (or the special value <code>device-width</code>), <code>height</code>, <code>initial-scale</code>, <code>maximum-scale</code>, <code>minimum-scale</code>, and <code>user-scalable</code>. <strong>Avoid disabling zoom</strong>: setting <code>user-scalable</code> to <code>no</code> prevents people with low vision from reading the page, and WCAG requires at least 2× scaling (5× is the recommended target).</p>
</section>
<section><h2 id="responsive-images"><a href="#responsive-images" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Responsive images</h2><p>Text wraps automatically at the edge of the screen, but images have an intrinsic size and can overflow a narrow viewport. Several techniques keep them responsive:</p>
<p><strong>Constrain with CSS.</strong> Use <code>max-inline-size: 100%</code> so an image never renders wider than its container, and <code>block-size: auto</code> so the browser preserves the aspect ratio while resizing:</p>
<pre><code class="language-css">img { max-inline-size: 100%; block-size: auto; }
</code></pre>
<p><strong>Fit within an aspect ratio.</strong> If a design forces a different aspect ratio, <code>object-fit: contain</code> preserves the image&#39;s ratio (leaving empty space) and <code>object-fit: cover</code> crops it (filling the box). <code>object-position</code> changes where the crop focuses:</p>
<pre><code class="language-css">img { width: 100%; aspect-ratio: 2/1; object-fit: cover; object-position: top center; }
</code></pre>
<p><strong>Hint the browser in HTML.</strong> Always include <code>width</code> and <code>height</code> attributes so the browser reserves the correct space and other content doesn&#39;t jump when the image loads (this prevents layout shift). Use <code>loading=&quot;lazy&quot;</code> for images below the fold, and for a hero/LCP image use <code>loading=&quot;eager&quot;</code> and optionally <code>fetchpriority=&quot;high&quot;</code> to fetch it immediately:</p>
<pre><code class="language-html">&lt;img src=&quot;hero.jpg&quot; alt=&quot;Description&quot; width=&quot;1200&quot; height=&quot;800&quot; loading=&quot;eager&quot; fetchpriority=&quot;high&quot;&gt;
</code></pre>
<p>Only set <code>fetchpriority=&quot;high&quot;</code> on a truly vital image, because prioritizing one resource means the browser de-prioritizes another (such as a script or font file).</p>
<p><strong>Deliver the right size.</strong> The <code>srcset</code> attribute complements (does not replace) <code>src</code>, giving the browser a set of candidate images and letting it choose the most appropriate one for the device and viewport.</p>
</section>
<section><h2 id="summary"><a href="#summary" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Summary</h2><p>Responsive web design is an approach, not a single technology: fluid grids and relative units let layouts adapt, media queries switch styles at content-driven breakpoints, the viewport meta tag makes mobile browsers treat pages at their real width, and fluid images with width/height hints prevent overflow and layout shift. The mindset is defensive — design for the known device sizes and be ready for the unknowns.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-is-the-main-difference-between-responsive-and-adaptive-design">What is the main difference between responsive and adaptive design?</h3><p>Responsive design builds one fluid layout that responds to the screen using fluid grids, fluid images, and media queries. The older &quot;mobile&quot; approach — and some adaptive setups — served different layouts per device, sometimes via server-side browser detection. RWD renders well on any screen size with a single page, which is why it became the standard.</p>
<h3 id="do-i-need-media-queries-for-every-breakpoint">Do I need media queries for every breakpoint?</h3><p>No. Start from a mobile-first single-column layout, then add media queries only where your content needs them — for example, where text lines get too long for comfortable reading. Breakpoints should be driven by content, not by the latest phone model, and mobile users should not download desktop-only layout rules.</p>
<h3 id="why-does-my-mobile-page-look-like-a-tiny-desktop-page">Why does my mobile page look like a tiny desktop page?</h3><p>That&#39;s the symptom before the viewport meta tag: mobile browsers rendered the desktop-width page and shrunk it, making text unreadably small. Adding <code>&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot;&gt;</code> makes the browser treat the page at the device&#39;s pixel width, so the layout reflows correctly on mobile.</p>
<h3 id="how-do-responsive-images-prevent-layout-shift">How do responsive images prevent layout shift?</h3><p>Images have an intrinsic size but text wraps, so a wide image can overflow or push content around. Setting the CSS rules above, giving every image <code>width</code> and <code>height</code> attributes so the browser reserves the space, and using <code>loading=&quot;lazy&quot;</code> below the fold means the page keeps its layout instead of jumping when images load.</p>
<h3 id="should-i-disable-pinch-to-zoom-on-mobile">Should I disable pinch-to-zoom on mobile?</h3><p>No. Setting <code>user-scalable=no</code> blocks zoom for people with low vision and is discouraged by accessibility guidance that requires at least 2× scaling. Keep the default zoom enabled and let the responsive layout do the work.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>How to implement rate limiting in Node.js: algorithms and practical examples</title>
      <link>https://debod.id/en/posts/rate-limiting-nodejs/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/rate-limiting-nodejs/</guid>
      <pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate>
      <category>nodejs</category>
      <category>nodejs</category>
      <category>security</category>
      <category>express</category>
      <category>api</category>
      <category>backend</category>
      <description>Rate limiting protects Node.js APIs from abuse and overload. This guide explains fixed window, sliding window, and token bucket algorithms, shows how to add rate limiting to an Express API, and how to test it.</description>
      <content:encoded><![CDATA[<section><p>Rate limiting controls how many requests a client can send to your API in a given amount of time. When a client exceeds its limit, the server responds with the HTTP status <code>429 Too Many Requests</code>, optionally including a <code>Retry-After</code> header that tells the client how long to wait before trying again. Rate limiting protects your API from abuse, prevents overload, and keeps performance predictable for every legitimate user.</p></section>
<section><h2 id="what-rate-limiting-is-and-why-you-need-it"><a href="#what-rate-limiting-is-and-why-you-need-it" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What rate limiting is and why you need it</h2><p>In networking, rate limiting means controlling how many operations can be performed in a given period of time, usually to avoid overloading a system and causing performance degradation. A server might limit the number of requests it accepts from a single client in a time period, which not only optimizes overall performance but also mitigates attacks such as denial-of-service (DoS). Without rate limiting, a single misbehaving or malicious client can consume all of your API&#39;s resources and make it slow or unavailable for everyone else.</p>
<p>Rate limiting is commonly applied to public APIs, login and password-reset endpoints, and any route that triggers expensive work. It is often confused with throttling, which gradually slows a client down, and with debouncing, which coalesces rapid events. Rate limiting rejects requests outright once a ceiling is reached, which is the strictest of the three.</p>
</section>
<section><h2 id="the-429-too-many-requests-response"><a href="#the-429-too-many-requests-response" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The <code>429 Too Many Requests</code> response</h2><p>When a client exceeds its allowed request rate, the server returns the HTTP status <code>429 Too Many Requests</code>. According to RFC 6585, a <code>Retry-After</code> header may be included to indicate how long the client should wait before making a new request. The <code>429</code> status tells both the client and any tooling (caches, proxies, monitoring) that the request was deliberately refused because of rate limiting, not because the server is broken.</p>
<pre><code class="language-http">HTTP/1.1 429 Too Many Requests
Retry-After: 60

{
  &quot;error&quot;: &quot;rate_limit_exceeded&quot;,
  &quot;message&quot;: &quot;You have exceeded the limit of 100 requests per minute&quot;
}
</code></pre>
<p>The client should read <code>Retry-After</code> and stop sending requests until that many seconds have passed, then retry. Well-behaved clients use this header to back off gracefully instead of hammering the API in a loop.</p>
</section>
<section><h2 id="rate-limiting-algorithms"><a href="#rate-limiting-algorithms" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Rate limiting algorithms</h2><p>There are several algorithms for deciding when to allow or block a request. Each trades simplicity, memory, and fairness differently.</p>
<h3 id="fixed-window-counter">Fixed window counter</h3><p>The fixed window counter divides time into fixed buckets (for example, 100 requests per 60 seconds). Every request increments a counter for the current window; when the window expires, the counter resets. It is the simplest to implement and needs only one counter per key, but it has a known weakness: two consecutive windows are independent, so a client can make twice the limit in the instant around a window boundary. A burst of 100 requests at the end of one minute and 100 at the start of the next passes a 100-per-minute limit.</p>
<h3 id="sliding-window-log">Sliding window log</h3><p>The sliding window log records a timestamp for every request. To decide whether to allow a new request, the server counts how many timestamps fall inside the current moving window and rejects if the count exceeds the limit. It is the most accurate, because the window truly slides, but it stores every request timestamp, so memory grows with traffic. For a high-traffic endpoint this can become expensive.</p>
<h3 id="sliding-window-counter">Sliding window counter</h3><p>The sliding window counter approximates a true rolling window with two integers: the count for the current window and the count for the previous one. It computes a weighted estimate of requests in the window by blending the two counters, giving smooth behavior that is close to a real sliding window at a small, constant memory cost. This makes it popular in production at scale, and it is what many library-based rate limiters use.</p>
<h3 id="token-bucket">Token bucket</h3><p>The token bucket models an explicit burst capacity plus a steady refill rate. A bucket holds up to <code>capacity</code> tokens; a token is added at a fixed rate, up to the capacity, and every request consumes one token. If the bucket is empty, the request is rejected. The token bucket allows short bursts (up to the full capacity) while enforcing a sustained average rate, which is ideal for APIs where clients legitimately burst, such as batch jobs or media streaming.</p>
<table>
<thead>
<tr>
<th>Algorithm</th>
<th>Accuracy</th>
<th>Memory</th>
<th>Burst handling</th>
<th>Best for</th>
</tr>
</thead>
<tbody><tr>
<td>Fixed window</td>
<td>Coarse — boundary bursts</td>
<td>Low</td>
<td>Weak (boundary double-burst)</td>
<td>Simple rate caps</td>
</tr>
<tr>
<td>Sliding window log</td>
<td>Exact</td>
<td>High (stores every request)</td>
<td>Strong</td>
<td>Low-traffic, strict accuracy</td>
</tr>
<tr>
<td>Sliding window counter</td>
<td>Near-exact</td>
<td>Low (two counters)</td>
<td>Strong</td>
<td>Production at scale</td>
</tr>
<tr>
<td>Token bucket</td>
<td>Average-rate</td>
<td>Low</td>
<td>Intended (bounded burst)</td>
<td>User-facing APIs, streaming</td>
</tr>
</tbody></table>
<p>A common rule of thumb: token bucket when clients need to burst, sliding window counter when you want smooth, fair behavior under steady load, and fixed window when you need the simplest possible implementation.</p>
</section>
<section><h2 id="building-a-fixed-window-rate-limiter-in-node-js"><a href="#building-a-fixed-window-rate-limiter-in-node-js" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Building a fixed window rate limiter in Node.js</h2><p>You do not need a library to understand the mechanics. Here is a minimal fixed window limiter implemented as plain Node.js middleware for an Express app. It tracks one counter per IP address.</p>
<pre><code class="language-js">const WINDOW_MS = 60 * 1000; // 60 seconds
const MAX_REQUESTS = 100;

const hits = new Map(); // ip -&gt; { count, resetAt }

function rateLimiter(req, res, next) {
  const ip = req.ip;
  const now = Date.now();
  let entry = hits.get(ip);

  if (!entry || now &gt;= entry.resetAt) {
    entry = { count: 0, resetAt: now + WINDOW_MS };
    hits.set(ip, entry);
  }

  entry.count += 1;

  res.setHeader(&quot;X-RateLimit-Limit&quot;, String(MAX_REQUESTS));
  res.setHeader(&quot;X-RateLimit-Remaining&quot;, String(Math.max(0, MAX_REQUESTS - entry.count)));

  if (entry.count &gt; MAX_REQUESTS) {
    res.setHeader(&quot;Retry-After&quot;, String(Math.ceil((entry.resetAt - now) / 1000)));
    return res.status(429).json({ error: &quot;Too Many Requests&quot; });
  }

  next();
}

app.use(rateLimiter);
</code></pre>
<p>This example stores state in a JavaScript <code>Map</code> in the process&#39;s memory. It works for a single Node.js instance, but it will not work correctly if you run multiple instances behind a load balancer, because each instance keeps its own counter. In a clustered or multi-instance deployment, use a shared store such as Redis so every instance reads the same counters.</p>
</section>
<section><h2 id="using-a-rate-limiting-library-with-express"><a href="#using-a-rate-limiting-library-with-express" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Using a rate limiting library with Express</h2><p>For production, a maintained library saves you from re-implementing edge cases such as IPv6 subnet handling, reliable header output, and pluggable stores. <code>express-rate-limit</code> is a widely used middleware for Express. Install it, configure a limiter, and attach it to your app or to specific routes.</p>
<pre><code class="language-js">import { rateLimit } from &quot;express-rate-limit&quot;;

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  limit: 100, // max 100 requests per window per IP
  standardHeaders: &quot;draft-8&quot;, // emit the Ratelimit header
  legacyHeaders: false, // disable the older X-RateLimit- headers
});

app.use(limiter); // apply to the whole app
// or: app.use(&quot;/api/auth&quot;, limiter); // protect only sensitive routes
</code></pre>
<p>A shared store makes the limiter consistent across multiple instances:</p>
<pre><code class="language-js">import { RedisStore } from &quot;rate-limit-redis&quot;;
// ... limiter = rateLimit({ store: new RedisStore(...), ... })
</code></pre>
<p><code>express-rate-limit</code> supports custom stores like <code>rate-limit-redis</code> so the hit counts live in Redis instead of in a single process&#39;s memory. It also lets you customize the key generator (for example, keying on an authenticated user ID instead of the raw IP, which matters behind a proxy or NAT where many users share one IP).</p>
</section>
<section><h2 id="limiting-a-token-bucket-with-a-library"><a href="#limiting-a-token-bucket-with-a-library" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Limiting a token bucket with a library</h2><p>Libraries such as <code>bottleneck</code> and <code>rate-limiter-flexible</code> provide token bucket and other rate limiter classes that you can drive from your own code rather than as Express middleware. The general shape is: create a limiter with a capacity and a refill rate, then consume a token before doing work.</p>
<pre><code class="language-js">import { RateLimiterRedis } from &quot;rate-limiter-flexible&quot;;

const limiter = new RateLimiterRedis({
  storeClient: redisClient,
  points: 100, // capacity (burst)
  duration: 60, // refill per 60 seconds
});

async function handle(req, res) {
  try {
    await limiter.consume(req.ip);
    // proceed with the request
  } catch {
    res.status(429).json({ error: &quot;Too Many Requests&quot;, retryAfter: 60 });
  }
}
</code></pre>
<p>The <code>consume</code> call either resolves (allow) or rejects (limit exceeded). You decide what to do on rejection, typically returning <code>429</code> with a <code>Retry-After</code> header.</p>
</section>
<section><h2 id="where-to-apply-rate-limiting"><a href="#where-to-apply-rate-limiting" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Where to apply rate limiting</h2><p>Rate limiting is not one-size-fits-all. Different routes need different limits, and keying on the right identifier matters.</p>
<ul>
<li><strong>Sensitive routes</strong> (login, password reset, signup) should have aggressive limits — for example, a handful of attempts per minute per IP or per account — because they are prime targets for credential stuffing and brute force.</li>
<li><strong>Read-only public routes</strong> can allow much higher limits.</li>
<li><strong>Key by user identity, not raw IP, when possible.</strong> Behind a proxy, NAT, or CDN many real users share a single visible IP, so limiting purely by IP can block legitimate users. If requests are authenticated, key on the user ID or API key.</li>
<li><strong>Servers behind a CDN</strong> such as Cloudflare can enforce rate limiting at the edge, before traffic reaches your origin. This offloads protection and lets you set custom limits on specific paths — a good complement to in-application limiting.</li>
</ul>
</section>
<section><h2 id="best-practices"><a href="#best-practices" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Best practices</h2><ul>
<li><strong>Always return <code>429</code> with a <code>Retry-After</code> header</strong> so well-behaved clients back off automatically and you do not silently drop traffic.</li>
<li><strong>Expose rate limit headers</strong> (<code>X-RateLimit-Limit</code>, <code>X-RateLimit-Remaining</code>, or the newer <code>Ratelimit</code> header) so clients and tooling can predict and respect the limit.</li>
<li><strong>Use a shared store for multi-instance deployments.</strong> In-memory counters are per-process and become inaccurate the moment you scale horizontally.</li>
<li><strong>Log and monitor <code>429</code> responses.</strong> A spike in <code>429</code>s can reveal a bug in your client code, a misbehaving crawler, or an attack in progress.</li>
<li><strong>Pick the key carefully.</strong> Limiting by IP is simple but coarse; limiting by authenticated user or API key is far more fair behind proxies.</li>
<li><strong>Do not rate limit only as an afterthought</strong> — decide your limits, store, and headers up front so clients are built to respect them from the start.</li>
</ul>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-is-rate-limiting-in-node-js">What is rate limiting in Node.js?</h3><p>Rate limiting in Node.js is the practice of controlling how many requests a single client can make to your server in a period of time. When a client exceeds its allowance, the server returns the HTTP status <code>429 Too Many Requests</code>. It is implemented either with a dedicated library or with a custom middleware that tracks request counts per client key (usually IP address or user ID).</p>
<h3 id="what-is-the-difference-between-rate-limiting-and-throttling">What is the difference between rate limiting and throttling?</h3><p>Rate limiting rejects requests once a client passes a maximum count in a time window, returning <code>429</code>. Throttling instead slows a client down by introducing delay, allowing the request eventually but at a reduced pace. Debouncing is a third related technique that coalesces rapid repeated events into fewer calls. Rate limiting is the strictest of the three.</p>
<h3 id="which-rate-limiting-algorithm-should-i-use">Which rate limiting algorithm should I use?</h3><p>Use a token bucket when clients legitimately need to burst, a sliding window counter when you want smooth, fair behavior under steady load, and a fixed window counter when you need the simplest possible implementation. Sliding window log is most accurate but stores every request timestamp, so it suits low-traffic endpoints only. For most production Node.js APIs, a sliding window counter or token bucket is a good default.</p>
<h3 id="does-rate-limiting-work-with-multiple-node-js-instances">Does rate limiting work with multiple Node.js instances?</h3><p>Only if you use a shared store. An in-memory counter is local to a single process, so if you run several instances behind a load balancer each one would track its own counts, and the effective limit would be multiplied by the number of instances. Libraries such as <code>express-rate-limit</code> and <code>rate-limiter-flexible</code> accept a shared store such as Redis to keep counts consistent across all instances.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>What are Protocol Buffers? How protobuf works and when to use it</title>
      <link>https://debod.id/en/posts/protobuf-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/protobuf-explained/</guid>
      <pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate>
      <category>api</category>
      <category>api</category>
      <category>backend</category>
      <category>web-development</category>
      <category>programming</category>
      <description>Protocol Buffers (protobuf) is Google&apos;s language-neutral, platform-neutral data serialization format. Define your data once in a .proto file, compile it, and get fast, compact binary output. Learn how protobuf works and when it outperforms JSON.</description>
      <content:encoded><![CDATA[<section><p>When two systems need to exchange structured data, they face a choice: use a human-readable format like JSON or invest in something faster and more compact. Protocol Buffers — commonly called <strong>protobuf</strong> — are Google&#39;s answer to that trade-off. They are a binary serialization format that trades human readability for speed, size, and type safety. This guide explains how protobuf works, where it shines, and when JSON is still the better choice.</p></section>
<section><h2 id="what-protobuf-is"><a href="#what-protobuf-is" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What protobuf is</h2><p>Protocol Buffers are a free and open-source cross-platform data format developed by Google for serializing structured data. Google released protobuf publicly in 2008, though the internal project dates back to 2001. The design goals emphasized simplicity and performance — protobuf was built to be smaller and faster than XML, and it largely succeeded.</p>
<p>A schema for protobuf associates data types with field names, using integer tags to identify each field. The schema lives in a <code>.proto</code> file written in protobuf&#39;s <strong>Interface Description Language (IDL)</strong>. You define your messages and services once, then run the <code>protoc</code> compiler to generate source code in your language of choice — C++, Java, Python, JavaScript, Go, PHP, Dart, Ruby, and many others are officially supported.</p>
<p>Once compiled, the generated code provides methods to serialize (marshall) structured data to a binary wire format and deserialize (unmarshall) it back into native language objects. The same <code>.proto</code> definition produces compatible code across all supported languages, so a Java program can serialize data that a Python program deserializes without either side knowing about the other.</p>
</section>
<section><h2 id="the-proto-file-format"><a href="#the-proto-file-format" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The .proto file format</h2><p>A protobuf message looks like this:</p>
<pre><code class="language-proto">syntax = &quot;proto3&quot;;

message Person {
  string name = 1;
  int32 age = 2;
  optional string email = 3;
}
</code></pre>
<p>Each field has a <strong>tag</strong> — a small integer written after the equals sign. The tag, not the field name, is what appears on the wire. When you serialize a <code>Person</code>, the output contains tag <code>1</code> for the name, tag <code>2</code> for the age, and so on. This is why protobuf messages are compact: field names never appear in the output, only their numeric identifiers.</p>
<p>Field tags are also the mechanism for <strong>forward and backward compatibility</strong>. If you add a new field with a new tag to a message, old code that does not know about that field simply ignores it during deserialization. Old data serialized by old code still decodes correctly in new code as long as field tags do not change. This tag-based evolution model lets you extend schemas over time without breaking existing clients or servers.</p>
<p>Protobuf supports several field types: <code>string</code>, <code>int32</code>, <code>int64</code>, <code>float</code>, <code>double</code>, <code>bool</code>, <code>bytes</code>, and nested <code>message</code> types. Collections use <code>repeated</code> (which maps to arrays or lists depending on the language). Maps use <code>map&lt;key_type, value_type&gt;</code>. Enums are supported with named values. The <code>oneof</code> construct lets a message hold at most one of a group of fields.</p>
</section>
<section><h2 id="binary-wire-format"><a href="#binary-wire-format" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Binary wire format</h2><p>The canonical protobuf serialization is binary. Unlike JSON or XML, a protobuf binary payload is not human-readable — but it is highly efficient. Field tags and wire types are encoded in a compact varint format; strings and bytes include their length prefix. A small integer tag takes 1 byte; a 32-bit integer takes 4 bytes; a 64-bit integer takes 8 bytes. No quotes, no field names, no whitespace.</p>
<p>Google also defines a text format for debugging and testing. This format is self-describing — it includes field names — but it loses the forward/backward compatibility guarantees of the binary format. The protobuf documentation explicitly recommends using the text format <strong>only for human editing and debugging</strong>, not for production data interchange.</p>
</section>
<section><h2 id="schema-evolution-and-compatibility"><a href="#schema-evolution-and-compatibility" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Schema evolution and compatibility</h2><p>Protobuf&#39;s compatibility story is one of its strongest features, but it comes with rules. Not all schema changes are safe:</p>
<p><strong>Wire-safe changes</strong> (safe across the binary wire) include adding new fields, renaming fields (the wire only sees the tag, not the name), changing a field between 32-bit and 64-bit integer variants, and converting between <code>optional</code> and a <code>oneof</code> group. Old code and new code can communicate across these changes without data loss.</p>
<p><strong>Wire-unsafe changes</strong> include changing a field&#39;s type (e.g., <code>string</code> to <code>bytes</code>), changing between a <code>map</code> and its equivalent <code>repeated</code> key-value message, or adding a required field. These break compatibility and should be avoided.</p>
<p>The ProtoJSON specification adds further nuance: when converting protobuf to JSON for API payloads, certain changes that are wire-safe in binary become unsafe in JSON because field names appear in the JSON output. Renaming a field that is also exposed in a JSON API will break clients that use the old name.</p>
</section>
<section><h2 id="protobuf-vs-json"><a href="#protobuf-vs-json" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Protobuf vs JSON</h2><p>JSON remains the dominant format for web APIs and wherever human-readability matters. Protobuf wins in specific scenarios:</p>
<ul>
<li><strong>Performance-critical internal services</strong>: gRPC at Google powers nearly all inter-machine communication. Protobuf&#39;s binary encoding and code generation make it faster than JSON parsing, especially for high-throughput services.</li>
<li><strong>Cross-language contracts</strong>: defining a <code>.proto</code> schema once and generating native code for 10 languages eliminates the manual boilerplate of writing encode/decode logic in each language.</li>
<li><strong>Long-term data storage</strong>: protobuf&#39;s schema and tag-based evolution make it a solid choice for binary storage formats where schema migration matters.</li>
</ul>
<p>JSON wins when:</p>
<ul>
<li><strong>Human debugging is frequent</strong>: reading <code>{&quot;name&quot;:&quot;Alice&quot;,&quot;age&quot;:30}</code> requires no tooling; reading a protobuf binary requires the <code>protoc</code> decode plugin.</li>
<li><strong>Browser compatibility is required</strong>: browsers have no native protobuf support. JSON works everywhere over HTTP without code generation.</li>
<li><strong>Ecosystem tooling matters</strong>: JSON integrates with logging systems, observability platforms, and developer tools without friction.</li>
</ul>
</section>
<section><h2 id="protojson-protobuf-on-json"><a href="#protojson-protobuf-on-json" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>ProtoJSON: protobuf on JSON</h2><p>For environments where binary is not practical, protobuf defines <strong>ProtoJSON</strong> — a canonical mapping between protobuf messages and JSON. Field names map to camelCase JSON keys, enum values map to their string names by default, <code>Timestamp</code> and <code>Duration</code> use RFC 3339 strings, and <code>BytesValue</code> uses base64-encoded strings.</p>
<p>ProtoJSON is not simply JSON that happens to contain protobuf data — it is a deliberate specification with its own rules. <code>int64</code> fields are quoted in JSON to avoid JavaScript&#39;s 53-bit integer precision limit. Well-known types like <code>google.protobuf.Any</code> use type URL strings. ProtoJSON is useful for REST APIs that want protobuf-style schema evolution but need JSON for client compatibility.</p>
</section>
<section><h2 id="limitations"><a href="#limitations" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Limitations</h2><p>Protobuf is not a universal solution. Key limitations:</p>
<ul>
<li><strong>Not self-describing</strong>: a raw protobuf payload cannot be interpreted without its <code>.proto</code> schema. This differs from JSON or XML, which are human-readable without a schema. In practice this means schema files must be distributed alongside the code.</li>
<li><strong>Not streamable</strong>: protobuf assumes an entire message fits in memory. For streaming large datasets, you need to split data into multiple messages or choose a different format.</li>
<li><strong>No built-in compression</strong>: protobuf payloads compress well with gzip or zstd, but there is no default compression as there might be in some protocols.</li>
<li><strong>Not suitable for large scientific data</strong>: formats like FITS have less overhead for multi-dimensional floating-point arrays.</li>
<li><strong>No single specification</strong>: unlike JSON or XML, protobuf has no IETF standard. The canonical implementation lives at <code>protobuf.dev</code>, and while the format is stable, it is controlled by Google.</li>
</ul>
</section>
<section><h2 id="protobuf-and-grpc"><a href="#protobuf-and-grpc" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Protobuf and gRPC</h2><p>Protocol Buffers are not only a data format — they are also the foundation of <strong>gRPC</strong>, Google&#39;s RPC framework. A gRPC service is defined in a <code>.proto</code> file with both message definitions and <code>service</code> declarations. The <code>protoc</code> compiler, with a gRPC plugin, generates both the data serialization code and the RPC client and server stubs.</p>
<p>This tight integration is why protobuf appears in this site&#39;s guide on REST vs gRPC and GraphQL vs gRPC. gRPC uses protobuf&#39;s binary efficiency and code generation to deliver low-latency, type-safe RPC calls over HTTP/2. If you are evaluating gRPC, you are implicitly adopting protobuf as your data format.</p>
</section>
<section><h2 id="verdict"><a href="#verdict" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Verdict</h2><p>Protocol Buffers are the right choice when you control both ends of the communication, need maximum performance and minimal payload size, or want a language-neutral data contract that evolves safely over time. The <code>.proto</code> schema as a single source of truth eliminates a class of encode/decode bugs that plague multi-language codebases.</p>
<p>Use JSON for public-facing APIs, browser clients, rapid prototyping, and anywhere human-readable payloads simplify debugging. Protobuf and JSON solve the same problem at opposite ends of the trade-off spectrum — choosing between them is a matter of whether your system values machine efficiency or human readability more.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-is-the-difference-between-protobuf-and-json">What is the difference between protobuf and JSON?</h3><p>Protobuf serializes data to a compact binary format; JSON is human-readable text. Protobuf messages are smaller than equivalent JSON and parse faster, but you cannot read them without a <code>.proto</code> schema and the <code>protoc</code> compiler. JSON requires no schema and works natively in browsers.</p>
<h3 id="can-protobuf-be-used-in-web-browsers">Can protobuf be used in web browsers?</h3><p>No. Browsers have no native protobuf support. You can use protobuf in a Node.js backend or in a web app that sends data to a server, but the browser itself cannot serialize or deserialize protobuf without a JavaScript library, and that library cannot use the <code>protoc</code>-generated code path that other languages use. For browser APIs, JSON is the practical choice.</p>
<h3 id="how-does-protobuf-achieve-forward-and-backward-compatibility">How does protobuf achieve forward and backward compatibility?</h3><p>Protobuf uses numeric field tags on the wire instead of field names. When you add a new field with a new tag, old code that does not recognize that tag simply skips it. Old data serialized by old code still decodes correctly because the old fields remain at their original tags. As long as you never reuse or remove field tags, the format is forward- and backward-compatible.</p>
<h3 id="is-protobuf-faster-than-json">Is protobuf faster than JSON?</h3><p>Yes, in most benchmarks. Binary encoding eliminates the overhead of parsing strings into numbers and handling Unicode. Code generation means serializers and deserializers are optimized at compile time, not interpreted at runtime. The performance advantage is most pronounced with large or frequent messages; for small, occasional payloads the difference is negligible.</p>
<h3 id="what-programming-languages-support-protobuf">What programming languages support protobuf?</h3><p>Google officially supports C++, Java, Python, Go, Objective-C, C#, JavaScript, Ruby, PHP, and Dart. Community implementations exist for over 20 additional languages including Rust, Swift, Kotlin, MATLAB, and Ada. The official implementation at <code>github.com/protocolbuffers/protobuf</code> is the canonical reference.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>How to monitor a Node.js application with Prometheus and Grafana</title>
      <link>https://debod.id/en/posts/prometheus-grafana-monitoring/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/prometheus-grafana-monitoring/</guid>
      <pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate>
      <category>programming</category>
      <category>nodejs</category>
      <category>monitoring</category>
      <category>docker</category>
      <description>Build a complete Node.js + Prometheus + Grafana monitoring stack: instrument Express with prom-client, scrape metrics, run Prometheus in Docker, build Grafana dashboards with PromQL, and configure alerts. Includes a working docker-compose example.</description>
      <content:encoded><![CDATA[<section><p>A production Node.js service is not &quot;done&quot; when it boots — it is done when you can see what is happening inside it. Prometheus is the open-source standard for collecting numeric time-series metrics from instrumented applications, and Grafana is the open-source standard for turning those metrics into dashboards and alerts. Combined, they form the backbone of modern observability for Node.js services. This guide builds the full stack from scratch: instrumenting an Express API with the official <code>prom-client</code> library, running Prometheus and Grafana in Docker, scraping the application, and creating dashboards with PromQL.</p></section>
<section><h2 id="what-prometheus-and-grafana-do"><a href="#what-prometheus-and-grafana-do" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What Prometheus and Grafana do</h2><p>Prometheus is a systems monitoring and alerting toolkit originally built at SoundCloud in 2012 and now a graduated Cloud Native Computing Foundation project. It collects metrics as time-series data, where each data point is a numeric value recorded at a specific timestamp and tagged with key-value pairs called labels. Prometheus uses a pull model: the server periodically scrapes an HTTP endpoint that your application exposes, instead of receiving pushes from your code.</p>
<p>Grafana is an open-source analytics and visualization platform. It connects to Prometheus (and dozens of other data sources), lets you write queries in PromQL (Prometheus&#39;s query language), and renders the results as graphs, gauges, tables, and alerts on dashboards. In short: Prometheus stores the numbers, Grafana shows them to humans.</p>
<p>Both are battle-tested at scale: Prometheus scrapes millions of time series per server, and Grafana dashboards sit behind on-call screens at companies of every size. For a Node.js service, the typical setup looks like this:</p>
<pre><code>Node.js app --(/metrics)--&gt; Prometheus --(PromQL)--&gt; Grafana
                                  |
                                  v
                              Alertmanager -&gt; Slack / Email / Pager
</code></pre>
</section>
<section><h2 id="instrumenting-the-application"><a href="#instrumenting-the-application" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Instrumenting the application</h2><p>The most widely used Node.js client is <code>siimon/prom-client</code>, an Apache-2.0 library that supports counters, gauges, histograms, and summaries. Install it next to Express:</p>
<pre><code class="language-bash">npm install prom-client express
</code></pre>
<p>Create a metrics module that registers default Node.js metrics (event-loop lag, memory, GC, active handles) and exposes a few custom application metrics:</p>
<pre><code class="language-javascript">// src/metrics.js
const client = require(&quot;prom-client&quot;);

const register = new client.Registry();
client.collectDefaultMetrics({ register });

// Custom: total HTTP requests, labelled by method/route/status
const httpRequestsTotal = new client.Counter({
  name: &quot;http_requests_total&quot;,
  help: &quot;Total HTTP requests&quot;,
  labelNames: [&quot;method&quot;, &quot;route&quot;, &quot;status&quot;],
  registers: [register],
});

// Custom: request latency histogram
const httpRequestDurationSeconds = new client.Histogram({
  name: &quot;http_request_duration_seconds&quot;,
  help: &quot;HTTP request duration in seconds&quot;,
  labelNames: [&quot;method&quot;, &quot;route&quot;, &quot;status&quot;],
  buckets: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10],
  registers: [register],
});

module.exports = {
  register,
  httpRequestsTotal,
  httpRequestDurationSeconds,
};
</code></pre>
<p>Wire the metrics into an Express app. The key principle: a middleware measures every request, then exposes <code>/metrics</code> as a plain HTTP endpoint that Prometheus can scrape:</p>
<pre><code class="language-javascript">// src/app.js
const express = require(&quot;express&quot;);
const {
  register,
  httpRequestsTotal,
  httpRequestDurationSeconds,
} = require(&quot;./metrics&quot;);

const app = express();

app.use((req, res, next) =&gt; {
  const end = httpRequestDurationSeconds.startTimer();
  res.on(&quot;finish&quot;, () =&gt; {
    // Use req.route?.path to avoid label cardinality explosion
    const route = req.route?.path || req.path;
    const labels = {
      method: req.method,
      route,
      status: String(res.statusCode),
    };
    end(labels);
    httpRequestsTotal.inc(labels);
  });
  next();
});

app.get(&quot;/&quot;, (_req, res) =&gt; res.json({ ok: true }));
app.get(&quot;/users/:id&quot;, (req, res) =&gt; res.json({ id: req.params.id }));

// Scrape endpoint — Prometheus polls this URL
app.get(&quot;/metrics&quot;, async (_req, res) =&gt; {
  res.set(&quot;Content-Type&quot;, register.contentType);
  res.end(await register.metrics());
});

app.listen(3000, () =&gt; console.log(&quot;listening on 3000&quot;));
</code></pre>
<p>Two important guardrails from the Prometheus best-practices guide. First, never use a raw URL path like <code>/users/42</code> as a label value — every user ID becomes a new time series and the database grows forever. Use <code>req.route.path</code> (the pattern <code>/users/:id</code>) instead. Second, register <code>collectDefaultMetrics</code> once at startup so event-loop lag, memory, GC, and Node.js version are recorded automatically.</p>
<p>You can verify the endpoint locally before going further:</p>
<pre><code class="language-console">$ curl -s http://localhost:3000/metrics | head
# HELP http_requests_total Total HTTP requests
# TYPE http_requests_total counter
http_requests_total{method=&quot;GET&quot;,route=&quot;/&quot;,status=&quot;200&quot;} 3
# HELP process_cpu_user_seconds_total Total user CPU time spent in seconds
# TYPE process_cpu_user_seconds_total counter
process_cpu_user_seconds_total 0.42
</code></pre>
</section>
<section><h2 id="running-prometheus-and-grafana-with-docker-compose"><a href="#running-prometheus-and-grafana-with-docker-compose" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Running Prometheus and Grafana with Docker Compose</h2><p>The simplest way to run the rest of the stack is a single Compose file. Create <code>monitoring/compose.yaml</code>:</p>
<pre><code class="language-yaml">services:
  prometheus:
    image: prom/prometheus:latest
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
      - prom-data:/prometheus
    ports:
      - &quot;9090:9090&quot;
    restart: unless-stopped

  grafana:
    image: grafana/grafana:latest
    volumes:
      - grafana-data:/var/lib/grafana
    ports:
      - &quot;3001:3000&quot;
    environment:
      - GF_SECURITY_ADMIN_USER=admin
      - GF_SECURITY_ADMIN_PASSWORD=admin
    depends_on:
      - prometheus
    restart: unless-stopped

volumes:
  prom-data:
  grafana-data:
</code></pre>
<p>The Prometheus configuration tells the server what to scrape and how often. Create <code>monitoring/prometheus.yml</code>:</p>
<pre><code class="language-yaml">global:
  scrape_interval: 15s
  evaluation_interval: 15s

scrape_configs:
  - job_name: &quot;nodejs-app&quot;
    metrics_path: /metrics
    static_configs:
      - targets: [&quot;host.docker.internal:3000&quot;]
        labels:
          service: &quot;api&quot;
</code></pre>
<p><code>host.docker.internal</code> is the special hostname Docker Desktop (and Docker on Linux 20.10+) provides so a container can reach the host machine. On plain Linux without that, replace it with the host&#39;s LAN IP. Start the stack:</p>
<pre><code class="language-console">$ docker compose up -d
</code></pre>
<p>Then point Prometheus at your application: if you are running the Express app on the host on port 3000, Prometheus will scrape it every 15 seconds. Verify on the Prometheus UI at <code>http://localhost:9090/targets</code> — the <code>nodejs-app</code> job should appear in green &quot;UP&quot; state.</p>
</section>
<section><h2 id="adding-the-prometheus-data-source-in-grafana"><a href="#adding-the-prometheus-data-source-in-grafana" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Adding the Prometheus data source in Grafana</h2><p>Grafana listens on <code>http://localhost:3001</code> (mapped from container port 3000). The first login uses <code>admin</code> / <code>admin</code> and asks you to set a new password. To add Prometheus as a data source:</p>
<ol>
<li>Click the cogwheel in the sidebar and choose <strong>Data Sources</strong>.</li>
<li>Click <strong>Add data source</strong> and select <strong>Prometheus</strong>.</li>
<li>Set <strong>URL</strong> to <code>http://prometheus:9090</code> (the Compose service name resolves inside the default network).</li>
<li>Click <strong>Save &amp; Test</strong> — Grafana will tell you the data source is working.</li>
</ol>
<p>The Prometheus data source has shipped with Grafana since 2.5.0 in 2015, so no plugin install is needed.</p>
</section>
<section><h2 id="building-your-first-dashboard"><a href="#building-your-first-dashboard" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Building your first dashboard</h2><p>Grafana dashboards are made of panels. Each panel runs a PromQL query against the data source and renders a visual. The metrics you registered earlier let you build the four panels every API dashboard needs.</p>
<p><strong>1. Request rate (per-second, by status code).</strong> Counts how many requests are landing on your service and groups them by HTTP status. PromQL uses <code>rate</code> to convert a counter into requests-per-second:</p>
<pre><code class="language-promql">sum by (status) (rate(http_requests_total[1m]))
</code></pre>
<p><strong>2. Error rate (5xx fraction).</strong> What fraction of responses are server errors? A rising line here wakes someone up:</p>
<pre><code class="language-promql">sum(rate(http_requests_total{status=~&quot;5..&quot;}[5m]))
  /
sum(rate(http_requests_total[5m]))
</code></pre>
<p><strong>3. Latency percentiles.</strong> A histogram is observed into buckets; <code>histogram_quantile</code> reconstructs a percentile across the buckets. The 95th and 99th percentiles tell you what most users and the slowest users actually experience:</p>
<pre><code class="language-promql">histogram_quantile(0.95,
  sum by (le) (rate(http_request_duration_seconds_bucket[5m]))
)
</code></pre>
<p><strong>4. Event-loop lag.</strong> Default Node.js metrics include <code>nodejs_eventloop_lag_seconds</code>. A long event loop is the most common reason a Node.js service &quot;feels slow&quot; even when CPU is low:</p>
<pre><code class="language-promql">nodejs_eventloop_lag_seconds
</code></pre>
<p>Add a graph panel for each query, choose the Prometheus data source, and lay the four panels out in a 2×2 grid. The result is a real-time picture of your service: traffic, errors, latency, and runtime health.</p>
</section>
<section><h2 id="alerting-with-prometheus"><a href="#alerting-with-prometheus" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Alerting with Prometheus</h2><p>Dashboards show what happened. Alerts tell you something needs attention right now. Prometheus evaluates alert rules — PromQL expressions with thresholds — on the same <code>evaluation_interval</code> (15s by default). Create <code>monitoring/alerts.yml</code>:</p>
<pre><code class="language-yaml">groups:
  - name: api
    rules:
      - alert: HighErrorRate
        expr: |
          sum(rate(http_requests_total{status=~&quot;5..&quot;}[5m]))
            /
          sum(rate(http_requests_total[5m]))
            &gt; 0.05
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: &quot;Error rate above 5% for 2m&quot;
          description: &quot;API is returning errors on more than 5% of requests.&quot;

      - alert: HighLatencyP95
        expr: |
          histogram_quantile(0.95,
            sum by (le) (rate(http_request_duration_seconds_bucket[5m]))
          ) &gt; 1
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: &quot;p95 latency over 1s&quot;
</code></pre>
<p>The <code>for: 2m</code> clause requires the condition to hold for two minutes before the alert fires — without it, a single bad request would page someone. Add <code>rule_files: [&quot;alerts.yml&quot;]</code> to <code>prometheus.yml</code> and reload Prometheus with <code>curl -X POST http://localhost:9090/-/reload</code>. Alerts in firing state appear at <code>http://localhost:9090/alerts</code>. To turn those alerts into Slack messages or pages, point Prometheus at an Alertmanager instance with the appropriate webhook configuration.</p>
</section>
<section><h2 id="production-hardening"><a href="#production-hardening" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Production hardening</h2><p>The setup above is enough to learn the stack. For a real deployment, three things matter most.</p>
<p><strong>Retention.</strong> Prometheus stores data on local disk. The default retention is 15 days. For longer history, use a remote-write adapter to ship data to Grafana Cloud, Thanos, or a managed service. Prometheus is designed for reliability during incidents, not for cheap long-term storage.</p>
<p><strong>High-cardinality labels.</strong> A label like <code>user_id</code> or full URL path creates a new time series for every value and is the single most common Prometheus outage. Always prefer coarse labels (<code>route</code>, <code>status</code>, <code>method</code>) over raw request data. The <code>route</code> from <code>req.route.path</code> is the safe choice.</p>
<p><strong>Security.</strong> Lock down <code>/metrics</code> behind an internal network, a reverse proxy with basic auth, or a service mesh. Do not expose it to the public internet — it leaks the shape of your traffic and can be abused for fingerprinting. Prometheus&#39;s own ports should also be inside a private network.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="why-use-prometheus-instead-of-a-saas-like-datadog">Why use Prometheus instead of a SaaS like Datadog?</h3><p>Prometheus is open source, vendor-independent, and free. It runs as a single static binary with no external dependencies, so you keep working during the kind of partial outages where hosted services sometimes degrade. The trade-off is that you operate the storage and dashboards yourself. For small teams that prefer managed, the same metrics flow into Grafana Cloud, Datadog, or New Relic with minimal change.</p>
<h3 id="do-i-need-grafana-can-i-just-use-prometheus-s-built-in-ui">Do I need Grafana? Can I just use Prometheus&#39;s built-in UI?</h3><p>Prometheus ships with a basic expression browser at <code>/graph</code> for ad-hoc queries. For ongoing work — dashboards, alerting visualizations, sharing with non-engineers — Grafana is the standard. You can also import hundreds of community dashboards from <code>grafana.com/dashboards</code> to skip writing panels by hand.</p>
<h3 id="what-is-the-difference-between-a-counter-gauge-and-histogram">What is the difference between a counter, gauge, and histogram?</h3><p>A counter is a value that only goes up (total requests, total errors). A gauge is a value that can go up or down (current memory use, active connections). A histogram samples observations into buckets so you can compute percentiles and averages later (request duration, response size). For request rate, use a counter; for &quot;how much memory is in use right now&quot;, use a gauge; for &quot;what was the 95th-percentile latency over the last 5 minutes&quot;, use a histogram.</p>
<h3 id="how-do-i-avoid-running-out-of-disk-on-prometheus">How do I avoid running out of disk on Prometheus?</h3><p>The two levers are the <code>--storage.tsdb.retention.time</code> flag (default 15 days) and the <code>--storage.tsdb.retention.size</code> flag. For long-term storage, the recommended pattern is Prometheus locally for fast recent queries plus a remote-write target (Thanos, Mimir, Grafana Cloud) for the historical archive.</p>
<h3 id="can-i-monitor-more-than-just-node-js-with-this-stack">Can I monitor more than just Node.js with this stack?</h3><p>Yes. Prometheus is language-agnostic — any service that exposes a <code>/metrics</code> endpoint in the Prometheus format can be scraped. For databases, queues, and operating systems, use exporters: <code>node_exporter</code> for Linux host metrics, <code>redis_exporter</code> for Redis, <code>postgres_exporter</code> for PostgreSQL. The Grafana dashboard stays the same; you just add more jobs to <code>prometheus.yml</code>.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>What is OAuth 2.0? How OAuth works and how to secure it</title>
      <link>https://debod.id/en/posts/oauth-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/oauth-explained/</guid>
      <pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate>
      <category>security</category>
      <category>authentication</category>
      <category>security</category>
      <category>api</category>
      <category>web-development</category>
      <description>OAuth 2.0 is an open standard for delegated authorization defined in RFC 6749. It lets a user (the resource owner) grant a third-party application (the client) limited access to resources on another server without sharing their password. This guide explains the four roles, the main grant types, the recommended authorization-code flow with PKCE, and the security best practices from RFC 9700.</description>
      <content:encoded><![CDATA[<section><p>OAuth 2.0 is an open standard for delegated authorization, defined by the Internet Engineering Task Force in RFC 6749. It lets a resource owner (typically a user) grant a third-party application (the client) limited, scoped access to resources hosted on another server — without ever giving that application their password. Instead of sharing credentials, the client receives an access token from an authorization server. Designed specifically to work over HTTP, OAuth 2.0 is the standard behind features like &quot;Sign in with Google&quot; and the way third-party apps read a limited set of your data on platforms such as GitHub or Google.</p></section>
<section><h2 id="oauth-is-authorization-not-authentication"><a href="#oauth-is-authorization-not-authentication" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>OAuth is authorization, not authentication</h2><p>The most common confusion is thinking OAuth is a login system. OAuth 2.0 is an <strong>authorization</strong> framework: it decides <em>what a client is allowed to do</em> with the user&#39;s resources. It does not, by itself, tell the client <em>who</em> the user is. To add identity, OpenID Connect (OIDC) builds an authentication layer on top of OAuth 2.0 and issues an ID token alongside the access token. When you log in with a third-party account, the workflow you experience is usually OAuth for authorization combined with OIDC for authentication.</p>
</section>
<section><h2 id="the-four-roles"><a href="#the-four-roles" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The four roles</h2><p>RFC 6749 defines four roles that every OAuth interaction involves:</p>
<ul>
<li><strong>Resource owner</strong> — the entity that can grant access to a protected resource. When it is a person, this is the end-user.</li>
<li><strong>Resource server</strong> — the server that hosts the protected resources and accepts authorized requests using access tokens.</li>
<li><strong>Client</strong> — the application making requests on behalf of the resource owner, with its authorization. The client can be a web app, a mobile app, or a server-to-server service.</li>
<li><strong>Authorization server</strong> — the server that authenticates the resource owner, obtains authorization, and issues access and refresh tokens.</li>
</ul>
<p>The resource owner&#39;s credentials are used only at the authorization server — never at the client. That separation is the core security win of OAuth.</p>
</section>
<section><h2 id="tokens-access-tokens-and-refresh-tokens"><a href="#tokens-access-tokens-and-refresh-tokens" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Tokens: access tokens and refresh tokens</h2><p>An <strong>access token</strong> is the credential a client presents to the resource server to access protected resources. It represents the authorization granted by the resource owner and is typically short-lived. A <strong>refresh token</strong> lets the client obtain a new access token after the current one expires, without forcing the user to log in again. Refresh tokens are sensitive: if they leak, an attacker can mint new access tokens indefinitely, so they must be stored securely and protected like secrets.</p>
<p>Bearer tokens, defined in RFC 6750, are the most common access token type. A bearer token is used by the client by putting it in the <code>Authorization: Bearer &lt;token&gt;</code> header of an HTTP request. Because anyone who holds the token can use it, bearer tokens require HTTPS to protect them in transit and must never be embedded in query strings, browser history, or logs.</p>
</section>
<section><h2 id="the-main-grant-types"><a href="#the-main-grant-types" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The main grant types</h2><p>RFC 6749 defines four grant types, each for a different client and use case:</p>
<ul>
<li><strong>Authorization code</strong> — the recommended flow for web and native apps that can keep a secret. The client redirects the user to the authorization server, the user logs in and consents, and the server returns an authorization code to the client. The client then exchanges that code for tokens at the token endpoint.</li>
<li><strong>Implicit (deprecated)</strong> — originally designed for browser-only apps. It returned the access token directly in the URL fragment. Because the token leaks through browser history and referrers, RFC 9700 formally deprecates this flow.</li>
<li><strong>Resource owner password credentials (ROPC)</strong> — the client collects the user&#39;s password directly. The user&#39;s credentials end up in the client&#39;s hands, which defeats the point of delegation and breaks with MFA and passkeys, so RFC 9700 strongly discourages it.</li>
<li><strong>Client credentials</strong> — used for server-to-server communication where there is no user. The client authenticates with its own credentials and gets a token for a consented scope.</li>
</ul>
<p>A fifth flow, the <strong>device authorization grant</strong> (RFC 8628), handles login on devices without browsers, like smart TVs and CLI tools.</p>
</section>
<section><h2 id="the-recommended-flow-authorization-code-with-pkce"><a href="#the-recommended-flow-authorization-code-with-pkce" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The recommended flow: authorization code with PKCE</h2><p>The current baseline, from RFC 9700, is the authorization code flow combined with Proof Key for Code Exchange (PKCE, RFC 7636):</p>
<ol>
<li>The client creates a random secret called a <strong>code verifier</strong> and derives a <strong>code challenge</strong> from it (usually a SHA-256 hash).</li>
<li>It redirects the user to the authorization server&#39;s <code>/authorize</code> endpoint, sending the challenge, the requested scope, and a <code>state</code> value.</li>
<li>The user logs in and consents. The server redirects back to the client with an <strong>authorization code</strong> (and echoes the <code>state</code>).</li>
<li>The client exchanges the code for tokens at the <code>/token</code> endpoint, sending the code <strong>verifier</strong> along with it.</li>
<li>The server verifies that the verifier matches the original challenge, then issues an access token (and optionally a refresh token).</li>
</ol>
<p>PKCE binds the authorization request to the token request. Even if an attacker intercepts the authorization code, they cannot exchange it because they do not hold the code verifier. This prevents authorization-code injection and some CSRF attacks, which is why PKCE is now recommended for all client types, not just mobile apps.</p>
</section>
<section><h2 id="how-to-secure-an-oauth-implementation"><a href="#how-to-secure-an-oauth-implementation" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How to secure an OAuth implementation</h2><p>Best current practice is codified in RFC 9700 (BCP 212, published January 2025). The key requirements are:</p>
<ul>
<li><strong>Use the authorization code flow with PKCE</strong> for everything, including single-page and mobile apps. Do not use the implicit grant.</li>
<li><strong>Do not use the resource owner password credentials grant.</strong> Prefer the authorization code flow.</li>
<li><strong>Match redirect URIs exactly.</strong> The server compares the <code>redirect_uri</code> in each request against the exact registered value — no pattern matching, no wildcards, no prefix rules — so an attacker cannot redirect the authorization code to their own site.</li>
<li><strong>Use the <code>state</code> parameter</strong> as an anti-CSRF token and validate it on return.</li>
<li><strong>Protect tokens in transit and at rest.</strong> Bearer tokens travel over HTTPS only and are stored server-side in secure storage, never in URLs or logs.</li>
<li><strong>Keep refresh tokens separate and protected</strong>, and revoke them when no longer needed.</li>
<li><strong>Where supported, use sender-constrained tokens</strong> such as mutual TLS (RFC 8705) or DPoP (RFC 9449) so a stolen token cannot be replayed.</li>
</ul>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-is-the-difference-between-oauth-and-openid-connect">What is the difference between OAuth and OpenID Connect?</h3><p>OAuth 2.0 is an authorization framework that lets a client access a user&#39;s resources with limited, scoped tokens. It does not identify the user on its own. OpenID Connect (OIDC) is an authentication layer built on top of OAuth 2.0 that adds an ID token carrying the user&#39;s identity, so a client can both verify who the user is and access their resources.</p>
<h3 id="what-are-the-four-roles-in-oauth-2-0">What are the four roles in OAuth 2.0?</h3><p>The resource owner (the user who grants access), the resource server (which hosts the protected resources), the client (the application requesting access on the user&#39;s behalf), and the authorization server (which authenticates the user and issues tokens). The user&#39;s credentials are only ever shared with the authorization server, never with the client.</p>
<h3 id="what-is-the-difference-between-an-access-token-and-a-refresh-token">What is the difference between an access token and a refresh token?</h3><p>An access token is the short-lived credential a client presents to get protected resources. A refresh token is a longer-lived credential the client exchanges for new access tokens after the current one expires, letting a session continue without asking the user to log in again.</p>
<h3 id="why-is-the-implicit-flow-deprecated">Why is the implicit flow deprecated?</h3><p>The implicit flow returned the access token directly in the URL fragment, where it leaked through browser history, referrers, and logs, and it could not use refresh tokens. RFC 9700 (BCP 212) deprecates it in favor of the authorization code flow with PKCE, which modern browsers and CORS fully support.</p>
<h3 id="does-oauth-allow-a-website-to-see-my-password">Does OAuth allow a website to see my password?</h3><p>No. With OAuth, you authenticate only at the authorization server. Your password never reaches the third-party client; the client receives a scoped token that lets it access only the resources you approved.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>How to set up a reverse proxy with Nginx: a practical guide</title>
      <link>https://debod.id/en/posts/nginx-reverse-proxy-guide/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/nginx-reverse-proxy-guide/</guid>
      <pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate>
      <category>networking</category>
      <category>nginx</category>
      <category>proxy</category>
      <category>networking</category>
      <category>devops</category>
      <category>web</category>
      <category>load-balancing</category>
      <category>tls</category>
      <description>A reverse proxy sits in front of your web servers and forwards client requests to them. This guide shows you exactly how to set one up with Nginx — proxy_pass, headers, load balancing, TLS and common pitfalls.</description>
      <content:encoded><![CDATA[<section><p>A reverse proxy is a server that sits in front of one or more web servers and forwards client requests to them, so clients never talk to your origin servers directly. Nginx is the most widely deployed web server and one of the most common tools for this job. This guide walks you through installing Nginx, writing a reverse-proxy config, and handling headers, load balancing and TLS.</p></section>
<section><h2 id="what-a-reverse-proxy-does"><a href="#what-a-reverse-proxy-does" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What a reverse proxy does</h2><p>On the Internet, a <strong>forward proxy</strong> sits in front of a group of clients and forwards their requests to servers on their behalf. A <strong>reverse proxy</strong> does the opposite: it sits in front of one or more origin servers, intercepts requests from clients at the edge, and forwards them to the right back-end. To clients, the reverse proxy is the only server they ever contact.</p>
<p>This setup is useful for several reasons:</p>
<table>
<thead>
<tr>
<th>Job</th>
<th>What the reverse proxy does</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Hide your back-end</strong></td>
<td>Clients never see the origin server&#39;s address, open ports or identity</td>
</tr>
<tr>
<td><strong>Load balancing</strong></td>
<td>Distributes requests across many back-end instances</td>
</tr>
<tr>
<td><strong>TLS termination</strong></td>
<td>Handles HTTPS certificates in one place, at the edge</td>
</tr>
<tr>
<td><strong>Caching</strong></td>
<td>Serves repeated responses without hitting the back end</td>
</tr>
<tr>
<td><strong>Compression</strong></td>
<td>Gzip/Brotli-compresses responses to reduce transfer size</td>
</tr>
<tr>
<td><strong>Security layer</strong></td>
<td>Filters some attacks (DDoS, slow-loris, malformed requests) before they reach your app</td>
</tr>
</tbody></table>
<p>Nginx is an open-source web server released under the 2-clause BSD license, and it handles all of the above. Because it uses an event-driven architecture, it can handle more than 10,000 simultaneous connections with a low memory footprint, which is why it is so widely used as a reverse proxy and load balancer.</p>
</section>
<section><h2 id="prerequisites"><a href="#prerequisites" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Prerequisites</h2><p>Before you start you need:</p>
<ul>
<li>A Linux server (Ubuntu/Debian commands are shown below) running Nginx.</li>
<li>At least one back-end application listening on a port (for example a Node.js or Python app on <code>http://127.0.0.1:3000</code>). The back end does <strong>not</strong> need to be publicly reachable — the reverse proxy talks to it on the local network.</li>
<li>A domain name and, for TLS, a way to get a certificate (Let&#39;s Encrypt free certificates work well with Nginx).</li>
</ul>
</section>
<section><h2 id="installing-nginx"><a href="#installing-nginx" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Installing Nginx</h2><p>On Debian or Ubuntu, install with:</p>
<pre><code>sudo apt update
sudo apt install nginx
</code></pre>
<p>When it finishes, Nginx starts automatically and serves a default page on port 80. Check it is running with:</p>
<pre><code>sudo systemctl status nginx
</code></pre>
<p>On most distributions the config lives in <code>/etc/nginx/nginx.conf</code>, with site configs in <code>/etc/nginx/sites-available/</code> and symlinked into <code>/etc/nginx/sites-enabled/</code>. You can also put small config snippets in <code>/etc/nginx/conf.d/</code>.</p>
</section>
<section><h2 id="a-minimal-reverse-proxy-config"><a href="#a-minimal-reverse-proxy-config" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>A minimal reverse proxy config</h2><p>The core directive that makes Nginx a reverse proxy is <code>proxy_pass</code>. It tells Nginx which back-end server to forward a request to. A minimal server block looks like this:</p>
<pre><code>server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://127.0.0.1:3000;
    }
}
</code></pre>
<p>With this config, a request to <code>http://example.com/</code> is forwarded to <code>http://127.0.0.1:3000/</code>, where your Node.js or Python app runs. Clients only ever see <code>example.com</code>; they never learn that the app lives on port 3000.</p>
<p><code>proxy_pass</code> can target any upstream: a local port, another server on your network, or a Unix socket for a service like the PHP-FPM application server.</p>
</section>
<section><h2 id="forwarding-the-right-http-headers"><a href="#forwarding-the-right-http-headers" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Forwarding the right HTTP headers</h2><p>By default Nginx forwards the request but the back-end app does not know the original client&#39;s IP or protocol, because to the back end every request looks like it comes from the proxy (usually <code>127.0.0.1</code>). You fix this by forwarding the standard proxy headers:</p>
<pre><code>location / {
    proxy_pass http://127.0.0.1:3000;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}
</code></pre>
<p>What each header does:</p>
<ul>
<li><strong><code>Host $host</code></strong> — tells the back end which hostname the client asked for. Without this, apps that route by domain name (virtual hosts) break.</li>
<li><strong><code>X-Real-IP $remote_addr</code></strong> — the client&#39;s real IP address.</li>
<li><strong><code>X-Forwarded-For</code></strong> — the full chain of client IPs the request passed through. Many apps read this to get the real client IP.</li>
<li><strong><code>X-Forwarded-Proto $scheme</code></strong> — whether the original request was HTTP or HTTPS. Apps use this to build correct absolute links and to know if the request arrived over TLS.</li>
</ul>
<p>Note that these headers are not inherently trustworthy: a client can set a fake <code>X-Forwarded-For</code>. Only trust the values your own proxy adds. <code>$remote_addr</code> in Nginx is the IP Nginx actually saw, so <code>X-Real-IP</code> is usually the one to log and trust.</p>
</section>
<section><h2 id="load-balancing-with-the-upstream-directive"><a href="#load-balancing-with-the-upstream-directive" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Load balancing with the upstream directive</h2><p>To spread requests across several back-end instances, define an <code>upstream</code> block and point <code>proxy_pass</code> at it by name:</p>
<pre><code>upstream app_servers {
    server 127.0.0.1:3000;
    server 127.0.0.1:3001;
}

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://app_servers;
    }
}
</code></pre>
<p>By default Nginx uses <strong>round-robin</strong> — each new request goes to the next server in the list. You can change the strategy:</p>
<ul>
<li><strong><code>least_conn</code></strong> — sends each request to the server with the fewest active connections; good for uneven request durations.</li>
<li><strong><code>ip_hash</code></strong> — always sends the same client IP to the same back-end; useful when an app keeps per-user state in memory.</li>
</ul>
<pre><code>upstream app_servers {
    least_conn;
    server 127.0.0.1:3000;
    server 127.0.0.1:3001;
}
</code></pre>
<p>If one back-end goes down, Nginx notices and stops routing new requests to it (inactive servers are marked <code>down</code>), so a single failing instance does not take the whole site with it.</p>
</section>
<section><h2 id="websocket-and-long-lived-connections"><a href="#websocket-and-long-lived-connections" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>WebSocket and long-lived connections</h2><p>Most modern apps use <code>upgrade</code> headers for WebSockets or streaming. You forward them like this:</p>
<pre><code>location / {
    proxy_pass http://127.0.0.1:3000;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection &quot;upgrade&quot;;
}
</code></pre>
<p><code>proxy_http_version 1.1</code> is required because HTTP/1.1 is needed for the <code>Upgrade</code> and <code>Connection</code> headers to work correctly with a proxy. Many guides add this block to application routes that stream data (SSE, WebSockets, gRPC-Web).</p>
</section>
<section><h2 id="adding-tls-with-a-free-certificate"><a href="#adding-tls-with-a-free-certificate" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Adding TLS with a free certificate</h2><p>HTTPS is not optional for a production site. The easiest path is Let&#39;s Encrypt via <code>certbot</code>:</p>
<pre><code>sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com
</code></pre>
<p>Certbot edits your Nginx config to add the certificate and redirects HTTP to HTTPS. The result is a server block that terminates TLS and forwards plain HTTP to your back end:</p>
<pre><code>server {
    listen 443 ssl;
    server_name example.com;

    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
</code></pre>
<p>One important detail for apps behind TLS: because the connection between Nginx and your back end is now plain HTTP, your app will see <code>X-Forwarded-Proto: https</code>. If your app needs to know the original protocol (to generate secure links or decide whether to set a <code>Secure</code> cookie), it must read <code>X-Forwarded-Proto</code> — and you must make sure the app trusts that header only from your own proxy.</p>
</section>
<section><h2 id="checking-and-reloading-the-config"><a href="#checking-and-reloading-the-config" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Checking and reloading the config</h2><p>Nginx config is strict: one syntax error and the service fails to start. Always test before reloading:</p>
<pre><code>sudo nginx -t
</code></pre>
<p>If the test passes, reload Nginx without dropping connections:</p>
<pre><code>sudo systemctl reload nginx
</code></pre>
<p><code>nginx -t</code> validates the config; <code>reload</code> applies it gracefully. This is the safe loop for every config change.</p>
</section>
<section><h2 id="common-pitfalls"><a href="#common-pitfalls" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Common pitfalls</h2><ul>
<li><strong>The proxy is a security surface.</strong> Since every public request hits Nginx, apply rate limits, keep it patched, and don&#39;t expose debug endpoints through it.</li>
<li><strong>Forgotten proxy headers.</strong> An app that sees <code>127.0.0.1</code> as every client IP and generates HTTP links behind TLS is almost always missing the <code>X-Forwarded-*</code> headers above.</li>
<li><strong>Trusting client-supplied headers.</strong> Letting arbitrary clients set <code>X-Forwarded-For</code> unchecked lets attackers spoof their IP in your logs. Only append headers from the proxy; don&#39;t blindly trust the whole chain.</li>
<li><strong>One server block per site.</strong> Put each site in its own <code>server</code> block with its own <code>server_name</code>; the <code>listen 80 default_server</code> block catches requests for domains you did not configure.</li>
<li><strong>Reload, not restart.</strong> <code>systemctl restart nginx</code> drops active connections; <code>reload</code> does not.</li>
</ul>
</section>
<section><h2 id="when-you-do-not-need-a-reverse-proxy"><a href="#when-you-do-not-need-a-reverse-proxy" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>When you do not need a reverse proxy</h2><p>If you have a single static site and no rolling deployments, a plain web server without proxying is enough. Add a reverse proxy when you: run a dynamic app on a local port, run several apps on one machine (each gets its own <code>server_name</code> or path), need load balancing across instances, want to centralize TLS and caching, or want to hide your origin server. For many cloud setups, managed providers (Cloudflare Pages, load balancers, API gateways) already give you reverse-proxy behavior, so you only operate Nginx yourself when you are self-hosting.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="is-nginx-a-web-server-or-a-reverse-proxy">Is Nginx a web server or a reverse proxy?</h3><p>Both. Nginx originally served static files, but it is equally capable as a reverse proxy, load balancer, HTTP cache and mail proxy. In practice the same <code>proxy_pass</code> configuration turns Nginx into a reverse proxy in front of one or more back-end servers.</p>
<h3 id="what-is-the-difference-between-a-reverse-proxy-and-a-load-balancer">What is the difference between a reverse proxy and a load balancer?</h3><p>A load balancer distributes traffic across several back-end instances; a reverse proxy forwards traffic from clients to back-end servers and can add TLS, caching, compression and security. Because Nginx does both, the two roles often blur — in Nginx you define <code>upstream</code> blocks for load balancing, which is one feature inside the broader reverse-proxy setup.</p>
<h3 id="do-i-need-a-certificate-on-both-nginx-and-my-back-end-app">Do I need a certificate on both Nginx and my back-end app?</h3><p>No. With Nginx terminating TLS, the public connection is HTTPS while the connection from Nginx to your back-end is often plain HTTP on a local port. That is normal and the standard setup for a single reverse proxy. Your app must then read <code>X-Forwarded-Proto</code> to know the original request was HTTPS.</p>
<h3 id="how-do-i-make-my-app-see-the-real-client-ip">How do I make my app see the real client IP?</h3><p>Forward the proxy headers from Nginx (<code>X-Real-IP</code>, <code>X-Forwarded-For</code>, <code>X-Forwarded-Proto</code>) as shown above, and configure your app to trust a single upstream — your Nginx instance. Never trust an unbounded list of <code>X-Forwarded-For</code> values from the Internet.</p>
<h3 id="does-a-reverse-proxy-slow-my-site-down">Does a reverse proxy slow my site down?</h3><p>A well-configured reverse proxy adds only a few milliseconds per request and is usually a net win because it offloads TLS, compression and caching from your application servers. The proxy should be stateless and never store session data, so any instance can handle any request and a failed instance can be replaced without downtime.</p>
</section>
<section><h2 id="verdict"><a href="#verdict" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Verdict</h2><p>A reverse proxy is the standard way to expose dynamic applications safely and reliably. Nginx makes it straightforward: one <code>server</code> block, a <code>proxy_pass</code> to your back-end port, the four forwarding headers, and optionally an <code>upstream</code> block for load balancing. Start with the minimal config, always run <code>nginx -t</code> before <code>reload</code>, forward the headers your app needs, and terminate TLS once at the edge. Once it is in place, your origin servers stay private, certificates live in one place, and you can scale by adding more back-end instances behind the same proxy.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>Monolith vs Microservices: which architecture should you choose?</title>
      <link>https://debod.id/en/posts/monolith-vs-microservices/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/monolith-vs-microservices/</guid>
      <pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate>
      <category>devops</category>
      <category>microservices</category>
      <category>devops</category>
      <category>scalability</category>
      <description>Monoliths and microservices are the two main ways to structure an application. A monolith ships as one deployable unit with a single codebase, while microservices split an app into small independent services that each own their data and scale alone. Compare the trade-offs and learn how to choose.</description>
      <content:encoded><![CDATA[<section><p>A monolithic application is built and deployed as one unit, with a single codebase and usually one shared database. A microservices architecture splits an application into a collection of small, loosely coupled services that communicate over lightweight protocols, are deployed independently, and each own their own data. Neither is objectively superior — they trade different sets of advantages and costs — so the right choice depends on your team, your scale, and your product. This guide breaks down what each architecture actually is, where each shines, and how to decide.</p></section>
<section><h2 id="what-a-monolithic-application-is"><a href="#what-a-monolithic-application-is" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What a monolithic application is</h2><p>A monolithic application is software in which all the components that make up a system are combined into a single, unified program. The user interface, business logic, and data-access layer live together in one codebase and are built and deployed as a single artifact.</p>
<p>The traditional model, a monolith is often the simplest way to build software:</p>
<ul>
<li><strong>Simple development</strong> — one codebase, one build, one place to look. Getting started is fast because there is no distribution overhead.</li>
<li><strong>Simple deployment</strong> — a single artifact ships to a server; there is nothing to coordinate across many moving parts.</li>
<li><strong>Simple testing</strong> — an end-to-end test can exercise the whole application at once because everything lives in one process.</li>
<li><strong>Shared data is easy</strong> — a single database means foreign keys, joins, and transactions all work the way you expect; consistency is immediate.</li>
</ul>
<p>The trade-off appears as the application and the team grow. Because everything is tightly coupled in one deployable unit, any change — even a small one — can mean rebuilding and redeploying the whole application. Scaling means scaling everything together, and a failure in one part can take down the entire application. As the codebase grows, a monolith can become harder to understand, test, and maintain.</p>
</section>
<section><h2 id="what-a-microservices-architecture-is"><a href="#what-a-microservices-architecture-is" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What a microservices architecture is</h2><p>A microservices architecture, or just microservices, is an architectural pattern that organizes an application into a collection of loosely coupled, fine-grained services that communicate through lightweight protocols such as HTTP or message queues.</p>
<p>Each service:</p>
<ul>
<li>Is <strong>deployed independently</strong> — you can update, release, and roll back a single service without touching the others.</li>
<li><strong>Owns its own data</strong> — each service manages its own database or data store, so teams do not share and fight over a single schema.</li>
<li><strong>Scales independently</strong> — a service under heavy load scales alone instead of forcing the entire application to scale.</li>
<li><strong>Maps to a business capability</strong> — services are often organized around functions such as &quot;orders&quot;, &quot;payments&quot;, or &quot;search&quot;, and small teams can own each one end to end.</li>
</ul>
<p>Because services are independent processes, they can be developed by separate teams using the tools that suit each problem. Fault isolation is better: if one service fails, the others keep running. And independent deployment lets teams ship changes continuously without coordinating a single big release.</p>
<p>The cost is complexity. Microservices are a distributed system, and distributed systems bring new problems:</p>
<ul>
<li><strong>Network calls are slow and can fail</strong> — in-process function calls become remote calls with latency and failure modes.</li>
<li><strong>Consistency becomes eventual</strong> — cross-service transactions are hard; data consistency across services often has to tolerate delay (a pattern called eventual consistency).</li>
<li><strong>Operational burden rises</strong> — you need service discovery, load balancing, monitoring, and deployment tooling for many moving parts.</li>
<li><strong>Testing is harder</strong> — a test that spans multiple services must deal with a distributed environment.</li>
</ul>
</section>
<section><h2 id="monolith-vs-microservices-the-key-differences"><a href="#monolith-vs-microservices-the-key-differences" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Monolith vs microservices: the key differences</h2><table>
<thead>
<tr>
<th>Aspect</th>
<th>Monolithic</th>
<th>Microservices</th>
</tr>
</thead>
<tbody><tr>
<td>Structure</td>
<td>One codebase, one deployable unit</td>
<td>Many small, independent services</td>
</tr>
<tr>
<td>Deployment</td>
<td>Everything redeployed together</td>
<td>Each service deployed and released independently</td>
</tr>
<tr>
<td>Data</td>
<td>One shared database</td>
<td>Each service owns its own data store</td>
</tr>
<tr>
<td>Scaling</td>
<td>Scale the whole application together</td>
<td>Scale individual services independently</td>
</tr>
<tr>
<td>Development</td>
<td>Simple to start, one codebase</td>
<td>More complex, many codebases and contracts</td>
</tr>
<tr>
<td>Failures</td>
<td>A failure can take down the whole app</td>
<td>Faults isolated to one service</td>
</tr>
<tr>
<td>Team fit</td>
<td>Small to mid teams, one shared codebase</td>
<td>Many teams owning their own services</td>
</tr>
<tr>
<td>Best for MVPs</td>
<td>Very well suited</td>
<td>Usually overkill at first</td>
</tr>
</tbody></table>
</section>
<section><h2 id="when-to-choose-a-monolith"><a href="#when-to-choose-a-monolith" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>When to choose a monolith</h2><p>Choose a monolith — or the middle-ground <strong>modular monolith</strong> (a single deployable unit with clearly separated internal modules) — when:</p>
<ul>
<li>You are building a <strong>new product or MVP</strong>, and the domain boundaries are not yet certain. A monolith lets you change structure cheaply as you learn.</li>
<li>Your <strong>team is small</strong>, and a single codebase with one deployment is far more productive than paying for distributed-systems infrastructure.</li>
<li>You do <strong>not yet need independent scaling</strong> — one service is not so much heavier than the others that the whole app has to scale, or you can scale the monolith vertically on a bigger machine first.</li>
</ul>
<p>The honest guideline: the large majority of projects should start as a monolith (ideally a modular one). You get speed, simplicity, and cheap changes, and you avoid paying for distributed-systems complexity you do not yet need. Many teams later migrate to microservices only when a real, specific need — uneven scaling, many autonomous teams, or independent release cadence — makes the complexity worth it.</p>
</section>
<section><h2 id="when-microservices-become-worth-it"><a href="#when-microservices-become-worth-it" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>When microservices become worth it</h2><p>Reach for microservices when several of these are genuinely true:</p>
<ul>
<li><strong>Uneven scaling</strong> — one part of the application (say search or payments) needs to scale far beyond the rest, and scaling the whole monolith wastes resources.</li>
<li><strong>Many autonomous teams</strong> — you have enough teams that independent ownership, deployment, and release of a service is worth the coordination cost. This is Conway&#39;s Law in practice: systems mirror the teams that build them.</li>
<li><strong>Independent release cadence</strong> — different parts of the product ship on their own schedule and should not be blocked by a single whole-app release.</li>
<li><strong>Fault isolation matters</strong> — a failure in one capability should not take down the entire product.</li>
<li><strong>Technology freedom</strong> — you want to pick the best tool per service rather than one stack for everything.</li>
</ul>
<p>Migrating a working monolith to microservices is a significant project with real risk — mostly around distributed data: joins become API calls, transactions become eventual, and multi-step operations need patterns like sagas to keep things consistent. A common low-risk path is the <strong>strangler pattern</strong>: gradually carve out one service at a time from the monolith while the rest keeps working, rather than doing a big-bang rewrite.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="are-microservices-always-better-than-a-monolith">Are microservices always better than a monolith?</h3><p>No. Microservices and a monolith trade different costs. Microservices give you independent scaling, deployment, and team ownership, but add the complexity of running a distributed system: network failures, eventual consistency, and heavier operational tooling. A monolith is simpler, cheaper to build and operate at first, and easier to test end to end. The better choice depends on team size, scale, and product needs, not on a blanket rule.</p>
<h3 id="should-i-start-with-a-monolith-or-microservices">Should I start with a monolith or microservices?</h3><p>Start with a monolith, ideally a modular one — a single deployable unit with clearly separated modules. It is fastest to build, easiest to change while your domain boundaries are still uncertain, and avoids paying for distributed-systems complexity before you need it. Move toward microservices only when a real need (uneven scaling, many autonomous teams, or independent release cadence) makes the added complexity worth it.</p>
<h3 id="what-is-the-difference-between-a-monolith-and-microservices-in-data-handling">What is the difference between a monolith and microservices in data handling?</h3><p>A monolith typically uses one shared database, so foreign keys, joins, and transactions work directly and consistency is immediate. In a microservices architecture each service owns its own database, which gives clean data ownership but means joins become API calls or data duplication and cross-service consistency becomes eventual rather than immediate. Distributed data is often the hardest part of microservices and the part teams most underestimate.</p>
<h3 id="what-is-a-modular-monolith">What is a modular monolith?</h3><p>A modular monolith is a single deployable unit whose internal modules are clearly separated along business boundaries, but which still ships and scales as one application. It gives you many of the maintainability benefits of good boundaries without the complexity of running a distributed system. It is a common recommended middle ground: start with a modular monolith and carve out real microservices later if a particular module needs to scale or release independently.</p>
</section>
<section><h2 id="verdict"><a href="#verdict" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Verdict</h2><p>Do not default to microservices because they sound modern. Default to a monolith — preferably a modular one — for new products, MVPs, and small-to-mid teams, and adopt microservices only when a concrete need appears: one part of the application scales unevenly, you have multiple teams that need independent ownership and release, or fault isolation is critical. When you do migrate, do it incrementally with the strangler pattern, and budget for the hardest problem of all: distributed data. In almost every case, the simpler architecture that meets today&#39;s real needs is the right one — you can always split later.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>What is load balancing? How load balancers work and key algorithms</title>
      <link>https://debod.id/en/posts/load-balancing-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/load-balancing-explained/</guid>
      <pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate>
      <category>networking</category>
      <category>load-balancing</category>
      <category>networking</category>
      <category>devops</category>
      <category>cloud</category>
      <category>nginx</category>
      <category>performance</category>
      <category>scalability</category>
      <description>Load balancing is the process of distributing incoming traffic across multiple servers so that no single server is overloaded. A load balancer sits in front of a pool of servers and decides where to send each request, trading total capacity for reliability and speed. This guide explains how load balancers work, the difference between Layer 4 and Layer 7, the main routing algorithms, and how to choose one.</description>
      <content:encoded><![CDATA[<section><p>Load balancing is the process of distributing a set of tasks — usually incoming requests or traffic — over a set of servers so that overall processing becomes more efficient and no single machine is overloaded. A <strong>load balancer</strong> is the component that sits in front of a pool of servers and decides, for every request, which server should handle it. Done well, it improves response time, makes the service resilient to individual server failures, and lets you scale horizontally by adding more machines. This guide walks through how load balancers work, the Layer 4 vs Layer 7 distinction, the main routing algorithms, and how to pick the right one.</p></section>
<section><h2 id="what-a-load-balancer-does"><a href="#what-a-load-balancer-does" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What a load balancer does</h2><p>At its core, a load balancer is a reverse-proxy style component that intercepts traffic destined for a service and forwards each request to one member of a backend pool. In doing so it solves several problems at once:</p>
<ul>
<li><strong>Availability.</strong> If one server goes down or stops responding, the load balancer stops sending it traffic and routes new requests to the healthy servers, keeping the service up. This is called failover.</li>
<li><strong>Scalability.</strong> Because traffic is spread across many servers, you can handle more requests by adding servers to the pool instead of making one machine more powerful.</li>
<li><strong>Performance.</strong> Distributing load avoids unevenly overloading some compute nodes while other nodes sit idle, which improves response time and utilization.</li>
<li><strong>Single entry point.</strong> Clients and DNS see one address; the complexity of many servers is hidden behind it.</li>
</ul>
<p>The load balancer itself is typically aware of the health of the pool: it monitors servers, for example by fetching a known page or checking a health endpoint, and only routes traffic to members that respond. When a server comes back online, the balancer starts routing to it again.</p>
</section>
<section><h2 id="layer-4-vs-layer-7-load-balancing"><a href="#layer-4-vs-layer-7-load-balancing" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Layer 4 vs Layer 7 load balancing</h2><p>Load balancers are commonly described by the OSI layer they operate on. The two most common types are Layer 4 and Layer 7.</p>
<p><strong>Layer 4 (transport) load balancing</strong> works at the transport layer and makes routing decisions using only header information such as IP address, port, and protocol. It does not look inside the payload. Because it does less inspection, Layer 4 balancing is very fast and has low overhead, which makes it a good fit for TCP and UDP traffic, database connections, VoIP, and other long-lived or high-throughput workloads.</p>
<p><strong>Layer 7 (application) load balancing</strong> works at the application layer and can inspect the HTTP request itself — the URL path, headers, cookies, and body. This lets it route requests intelligently, for example sending <code>/api</code> traffic to one backend pool and <code>/images</code> to another, or using cookies for session affinity. It is more feature-rich but carries more processing overhead per request, so it is best suited to HTTP workloads where that intelligence pays off.</p>
<p>In practice many real-world deployments use both: a Layer 4 balancer in front for speed and a Layer 7 balancer for application-aware routing.</p>
</section>
<section><h2 id="load-balancing-algorithms"><a href="#load-balancing-algorithms" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Load balancing algorithms</h2><p>A load balancer needs a rule for choosing which server gets each request. These rules are called algorithms or scheduling methods, and they fall into two broad families: <strong>static</strong> algorithms, which ignore the current state of the servers, and <strong>dynamic</strong> algorithms, which take the servers&#39; live load into account.</p>
<h3 id="round-robin">Round robin</h3><p>Round robin is the simplest static method. The first request goes to the first server, the second to the second server, and so on down to the last, then it cycles back to the first. It is easy to set up and works well when all servers have roughly equal capacity and requests take similar time to process. Its weakness is that it ignores how loaded each server actually is.</p>
<h3 id="weighted-round-robin">Weighted round robin</h3><p>Weighted round robin is round robin with a weight assigned to each server, so that more powerful servers receive proportionally more requests — and, depending on the implementation, receive them first. It is a good choice when servers in the pool differ in capacity.</p>
<h3 id="least-connections">Least connections</h3><p>Least connections is a dynamic method that routes each new request to the server with the fewest active connections at that moment. It suits workloads where requests vary a lot in processing time or are long-lived, such as WebSocket connections, because a server with a few heavy connections is not confused for a lightly loaded one based on connection count alone. It can also be weighted.</p>
<h3 id="hashing">Hashing</h3><p>Hash-based methods map each request to a server using a hash of some value, most commonly the client IP address (IP hash). Because the same client always hashes to the same server, this provides <strong>session persistence</strong> (affinity) without reading cookies — useful for stateful applications. This is why IP-hash balancing is a popular choice for certain types of workloads.</p>
<h3 id="randomized-and-power-of-two-choices">Randomized and power-of-two choices</h3><p>Randomized static load balancing simply assigns tasks to servers at random. <strong>Power of two choices</strong> picks two servers at random and sends the request to whichever of the two is less loaded. The latter is a surprisingly effective dynamic method: with only two random samples it balances load far better than pure random assignment and is used in distributed systems where a central scheduler is undesirable.</p>
</section>
<section><h2 id="health-checks-and-failover"><a href="#health-checks-and-failover" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Health checks and failover</h2><p>Load balancing is often used to implement failover — the continuation of service after the failure of one or more components. The servers are monitored continually; when one becomes unresponsive, the balancer is informed and stops sending it traffic, and when it comes back online the balancer restarts routing to it. For failover to keep capacity when a server drops, the pool needs at least one server in excess of the service&#39;s capacity (often called N+1 redundancy). Note that this technique can make the load balancer itself a single point of failure, so production setups usually balance at more than one level.</p>
</section>
<section><h2 id="choosing-an-algorithm"><a href="#choosing-an-algorithm" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Choosing an algorithm</h2><p>There is no universal best algorithm — the right choice depends on your traffic and your servers:</p>
<ul>
<li><strong>Equal servers, similar requests:</strong> start with round robin. It is simple and predictable.</li>
<li><strong>Servers of different capacity:</strong> weighted round robin (or weighted least connections).</li>
<li><strong>Long-lived or variable-length connections</strong> (WebSockets, media): least connections.</li>
<li><strong>Stateful sessions without cookies:</strong> IP hashing for session affinity.</li>
<li><strong>HTTP with distinct resource types</strong> (paths, headers): a Layer 7 balancer so routing can be content-aware.</li>
</ul>
<p>The practical pattern is to start with the simplest method that meets your needs, add health checks, and move to a more dynamic algorithm only when your workload shows it needs one.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-is-the-difference-between-layer-4-and-layer-7-load-balancing">What is the difference between Layer 4 and Layer 7 load balancing?</h3><p>Layer 4 load balancing makes routing decisions from transport-layer headers only (IP address, port, protocol) and is fast with low overhead, making it good for TCP/UDP and many long-lived workloads. Layer 7 load balancing inspects the HTTP request — path, headers, cookies, body — which allows content-aware routing and session affinity but costs more processing per request. Many systems use a Layer 4 balancer in front of a Layer 7 balancer to get both speed and intelligence.</p>
<h3 id="what-is-the-best-load-balancing-algorithm">What is the best load balancing algorithm?</h3><p>There is no single best algorithm. Round robin is simplest and works for equal-capacity servers with similar requests; weighted round robin suits servers of different capacity; least connections suits long-lived or variable-length connections; and IP hashing gives session affinity for stateful apps. Pick the simplest method that meets your needs, then move to a more dynamic algorithm only if your workload requires it.</p>
<h3 id="what-is-session-affinity-in-load-balancing">What is session affinity in load balancing?</h3><p>Session affinity (also called sticky sessions or persistence) ensures that requests from a given client are consistently sent to the same server, which matters for stateful applications that keep session data in memory on one server. It can be achieved with cookies at Layer 7 or with IP hashing so that the same client always routes to the same server.</p>
<h3 id="does-load-balancing-make-the-load-balancer-a-single-point-of-failure">Does load balancing make the load balancer a single point of failure?</h3><p>It can. If the load balancer itself fails, all the traffic through it stops. Production setups mitigate this by running load balancers in redundant pairs, balancing at multiple levels, or using DNS-level failover, so the balancing layer itself does not become the bottleneck.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>How to add JWT authentication to a Node.js API: a practical guide</title>
      <link>https://debod.id/en/posts/jwt-authentication-nodejs/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/jwt-authentication-nodejs/</guid>
      <pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate>
      <category>security</category>
      <category>jwt</category>
      <category>authentication</category>
      <category>security</category>
      <category>nodejs</category>
      <category>express</category>
      <category>api</category>
      <description>Step-by-step implementation of JWT authentication on a Node.js/Express API: password hashing, login endpoint, token verification middleware, refresh tokens, and the common security pitfalls.</description>
      <content:encoded><![CDATA[<section><p>JSON Web Tokens (JWTs) are the standard way to make a stateless authenticated API in Node.js. The client sends credentials once to a <code>login</code> endpoint, the server returns a signed token, and from then on the client proves who it is by sending that token in an <code>Authorization</code> header. This guide walks through the whole flow on an Express API: hashing passwords, signing tokens, verifying them in middleware, refreshing them, and avoiding the security mistakes that take most JWT setups down.</p></section>
<section><h2 id="what-jwt-gives-you-and-what-it-does-not"><a href="#what-jwt-gives-you-and-what-it-does-not" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What JWT gives you (and what it does not)</h2><p>A JWT is a compact, URL-safe string with three base64url-encoded parts — <code>header.payload.signature</code> — separated by dots. The server signs the <code>header.payload</code> with a secret (or private key) and the receiver verifies the signature. Because the signature covers the payload, a client cannot tamper with the claims without the server noticing.</p>
<p>JWT gives you:</p>
<ul>
<li><strong>Stateless authentication.</strong> The server does not keep a session table; the token is the proof. A cluster of API nodes can verify any token as long as they share the signing key.</li>
<li><strong>Self-contained claims.</strong> The payload can carry the user id, role and expiry, so the API does not need a database round-trip on every request to know who the caller is.</li>
<li><strong>Cross-service trust.</strong> The same token can be verified by multiple services that share the public key (with RS256/ES256).</li>
</ul>
<p>JWT does <strong>not</strong> give you:</p>
<ul>
<li><strong>Revocation for free.</strong> A signed token is valid until it expires, even if the user logs out or is banned. You need a denylist, short expirations, or refresh-token rotation to mitigate that.</li>
<li><strong>Encrypted payload.</strong> Base64url is encoding, not encryption. Anyone who can read the token can read the payload. Never put passwords, tokens or PII in JWT claims unless you use a JWE.</li>
<li><strong>Password storage.</strong> JWT is for sessions. Storing user passwords is a separate job that needs a slow, salted hash (bcrypt, scrypt or Argon2).</li>
</ul>
<p>For a deeper look at the structure and the claims, see the <a href="/en/posts/jwt-explained/">JWT explained</a> guide; for the surrounding HTTP layer, see <a href="/en/posts/rest-api-nodejs-express/">REST API with Node.js and Express</a>.</p>
</section>
<section><h2 id="prerequisites"><a href="#prerequisites" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Prerequisites</h2><ul>
<li>Node.js 20+ installed.</li>
<li>A working Express app. If you do not have one yet, the <a href="/en/posts/rest-api-nodejs-express/">REST API with Node.js and Express</a> guide walks you through the basics.</li>
<li>A database or in-memory store for users (a <code>users</code> array is enough for the demo; the same code works with Postgres, MongoDB, etc.).</li>
<li>The packages <code>jsonwebtoken</code>, <code>bcrypt</code> and <code>express</code>.</li>
</ul>
<pre><code>npm install express jsonwebtoken bcrypt
</code></pre>
<p>All three are open source. <code>jsonwebtoken</code> is the most widely used JWT library for Node.js, <code>bcrypt</code> is the reference password-hashing function (designed by Provos and Mazières in 1999 and still considered safe with a sufficient cost factor).</p>
</section>
<section><h2 id="the-data-model"><a href="#the-data-model" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The data model</h2><p>A user record needs at least an id, an email and a password hash. The plain password is never stored. A minimal shape looks like this:</p>
<pre><code class="language-js">// users store — in production this is a database table
const users = [];
let nextId = 1;
</code></pre>
<p>In a real app you would also store a unique index on email, a <code>createdAt</code> timestamp, and either a <code>passwordUpdatedAt</code> field (to invalidate tokens after a password change) or a <code>tokenVersion</code> counter (to bump every time the user logs out everywhere).</p>
</section>
<section><h2 id="hashing-passwords-with-bcrypt"><a href="#hashing-passwords-with-bcrypt" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Hashing passwords with bcrypt</h2><p>Never store passwords in plain text or with a fast hash like SHA-256. bcrypt is the standard choice: it is slow on purpose, salts automatically, and the cost factor can be raised over time to keep pace with hardware.</p>
<pre><code class="language-js">import bcrypt from &quot;bcrypt&quot;;

const COST = 12; // 2^12 = 4096 rounds — balance between security and login latency

export async function hashPassword(plain) {
  return bcrypt.hash(plain, COST);
}

export async function verifyPassword(plain, hash) {
  return bcrypt.compare(plain, hash);
}
</code></pre>
<p>The <code>COST</code> value is the log2 of the number of internal rounds. 12 is the common production default: on a modern server it takes roughly 200–300 ms, which slows a brute-force attack on a stolen database to a crawl while still being acceptable for a real login. Drop to 10 for test environments, raise to 13 if your hardware can take it.</p>
<p>Two important properties of bcrypt, straight from the spec:</p>
<ul>
<li><strong>Salting is automatic.</strong> Every call to <code>bcrypt.hash</code> generates a fresh 16-byte salt and embeds it in the output. You do not store the salt separately.</li>
<li><strong>The hash carries its own parameters.</strong> The output looks like <code>$2a$12$R9h/cIPz0gi.URNNX3kh2O.PST9/PgBkqquzi.Ss7KIUgO2t0jWMUW</code> — the <code>$2a$</code> part identifies the algorithm, <code>12</code> the cost, and the next 22 characters the salt. Verification extracts all three from the stored hash.</li>
</ul>
<p><code>bcrypt.compare</code> is constant-time for a given hash, which avoids timing attacks on the comparison.</p>
</section>
<section><h2 id="signing-a-jwt"><a href="#signing-a-jwt" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Signing a JWT</h2><p>The <code>jsonwebtoken</code> package exposes <code>jwt.sign(payload, secret, options)</code>:</p>
<pre><code class="language-js">import jwt from &quot;jsonwebtoken&quot;;

const ACCESS_SECRET = process.env.JWT_ACCESS_SECRET;
const REFRESH_SECRET = process.env.JWT_REFRESH_SECRET;

export function signAccessToken(user) {
  return jwt.sign(
    { sub: user.id, email: user.email },
    ACCESS_SECRET,
    { algorithm: &quot;HS256&quot;, expiresIn: &quot;15m&quot; }
  );
}

export function signRefreshToken(user) {
  return jwt.sign(
    { sub: user.id, typ: &quot;refresh&quot; },
    REFRESH_SECRET,
    { algorithm: &quot;HS256&quot;, expiresIn: &quot;7d&quot; }
  );
}
</code></pre>
<p>A few details that matter:</p>
<ul>
<li><strong><code>algorithm: &quot;HS256&quot;</code> is pinned explicitly.</strong> Without it, some libraries honor the <code>alg</code> field of an incoming token, which is the well-known “algorithm confusion” attack. Pin it.</li>
<li><strong><code>sub</code> (subject) is the user id.</strong> The JWT spec reserves a small set of claim names — <code>iss</code>, <code>sub</code>, <code>aud</code>, <code>exp</code>, <code>nbf</code>, <code>iat</code>, <code>jti</code> — and using them keeps your tokens interoperable.</li>
<li><strong><code>expiresIn</code> controls lifetime.</strong> Short access tokens (15 minutes is a common default) limit the damage if one leaks. Refresh tokens (7 days) are the way the client gets a new access token without re-entering the password.</li>
<li><strong>Two different secrets.</strong> Access and refresh tokens are signed with different keys so a leaked refresh token cannot be used to forge access tokens, and vice versa.</li>
<li><strong>Secrets come from the environment.</strong> <code>process.env.JWT_ACCESS_SECRET</code> is loaded from your deployment environment, never hardcoded. A 32-byte random string is enough; <code>openssl rand -hex 32</code> gives you one.</li>
</ul>
</section>
<section><h2 id="the-login-endpoint"><a href="#the-login-endpoint" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The login endpoint</h2><p>The <code>POST /auth/login</code> route takes an email and password, verifies the hash, and returns both tokens:</p>
<pre><code class="language-js">app.post(&quot;/auth/login&quot;, async (req, res) =&gt; {
  const { email, password } = req.body ?? {};
  if (typeof email !== &quot;string&quot; || typeof password !== &quot;string&quot;) {
    return res.status(400).json({ error: &quot;email and password required&quot; });
  }

  const user = users.find((u) =&gt; u.email === email);
  if (!user || !(await verifyPassword(password, user.passwordHash))) {
    // same response for unknown email and wrong password
    return res.status(401).json({ error: &quot;invalid credentials&quot; });
  }

  return res.json({
    accessToken: signAccessToken(user),
    refreshToken: signRefreshToken(user),
  });
});
</code></pre>
<p>Two security details worth pointing out:</p>
<ul>
<li><strong>Same error for unknown email and wrong password.</strong> Telling the client “user not found” leaks which emails are registered; “invalid credentials” is the only safe response. This is the standard mitigation against user enumeration.</li>
<li><strong>Rate limiting belongs here.</strong> Even with a slow hash, an attacker can try millions of passwords per second if you let them. A simple per-IP and per-email rate limiter (e.g. <code>express-rate-limit</code>) on the login and refresh routes is not optional in production.</li>
</ul>
<p>The matching <code>POST /auth/register</code> endpoint is symmetrical: validate input, hash the password, save the user, return tokens. Same error-handling rules.</p>
</section>
<section><h2 id="verifying-tokens-in-middleware"><a href="#verifying-tokens-in-middleware" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Verifying tokens in middleware</h2><p>Every protected route runs through a middleware that reads the <code>Authorization</code> header, verifies the token, and attaches the user to the request:</p>
<pre><code class="language-js">function requireAuth(req, res, next) {
  const header = req.get(&quot;authorization&quot;) || &quot;&quot;;
  const [scheme, token] = header.split(&quot; &quot;);
  if (scheme !== &quot;Bearer&quot; || !token) {
    return res.status(401).json({ error: &quot;missing bearer token&quot; });
  }
  try {
    const payload = jwt.verify(token, ACCESS_SECRET, { algorithms: [&quot;HS256&quot;] });
    req.user = { id: payload.sub, email: payload.email };
    return next();
  } catch {
    return res.status(401).json({ error: &quot;invalid or expired token&quot; });
  }
}
</code></pre>
<p>Then mount it on the routes that need it:</p>
<pre><code class="language-js">app.get(&quot;/me&quot;, requireAuth, (req, res) =&gt; {
  res.json({ id: req.user.id, email: req.user.email });
});
</code></pre>
<p><code>jwt.verify</code> does three things for you, all of which you want:</p>
<ul>
<li><strong>Checks the signature.</strong> A tampered or unsigned token fails.</li>
<li><strong>Checks <code>exp</code> and <code>nbf</code>.</strong> An expired token or one whose <code>nbf</code> is in the future is rejected. No clock skew by default.</li>
<li><strong>Pins the algorithm.</strong> The <code>algorithms: [&quot;HS256&quot;]</code> option disables the “alg=none” and “RS256 verified with the public key as an HMAC secret” attacks. Pin it the same way on the signing side.</li>
</ul>
<p>The middleware never re-throws the underlying JWT error to the client — a generic <code>invalid or expired token</code> is enough; details like “signature mismatch” vs “expired at …” help an attacker tune their approach and are best left out.</p>
</section>
<section><h2 id="refresh-tokens"><a href="#refresh-tokens" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Refresh tokens</h2><p>When the access token expires, the client sends the refresh token to <code>POST /auth/refresh</code> and gets a new pair. The refresh token is rotated: the old one is invalidated and a new one is returned.</p>
<pre><code class="language-js">app.post(&quot;/auth/refresh&quot;, (req, res) =&gt; {
  const { refreshToken } = req.body ?? {};
  if (typeof refreshToken !== &quot;string&quot;) {
    return res.status(400).json({ error: &quot;refreshToken required&quot; });
  }
  try {
    const payload = jwt.verify(refreshToken, REFRESH_SECRET, { algorithms: [&quot;HS256&quot;] });
    if (payload.typ !== &quot;refresh&quot;) {
      return res.status(401).json({ error: &quot;wrong token type&quot; });
    }
    const user = users.find((u) =&gt; u.id === payload.sub);
    if (!user) return res.status(401).json({ error: &quot;user not found&quot; });
    return res.json({
      accessToken: signAccessToken(user),
      refreshToken: signRefreshToken(user),
    });
  } catch {
    return res.status(401).json({ error: &quot;invalid or expired refresh token&quot; });
  }
});
</code></pre>
<p>Why rotation matters: if a refresh token leaks, the legitimate client will use it next and the server will see the same <code>jti</code> twice. That is the signal to invalidate the user&#39;s whole token family. In a real system, store the <code>jti</code> of the last issued refresh token per user; if the same <code>jti</code> shows up again, revoke everything and force a re-login.</p>
</section>
<section><h2 id="logging-out"><a href="#logging-out" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Logging out</h2><p>Stateless JWT makes logout tricky. The token is still mathematically valid after the user leaves — there is nothing on the server to delete. Three options, in order of complexity:</p>
<ol>
<li><strong>Client-side discard.</strong> The client deletes the tokens. Simple, but does not protect a leaked token.</li>
<li><strong>Short access tokens.</strong> With a 15-minute access token, a stolen token is useful for at most 15 minutes even without server-side action.</li>
<li><strong>Server-side denylist.</strong> Keep a Redis set of revoked <code>jti</code>s and check it in <code>requireAuth</code>. For most apps, option 2 alone is enough; add the denylist if you have a real reason to invalidate tokens immediately (password change, ban, suspected leak).</li>
</ol>
<pre><code class="language-js">app.post(&quot;/auth/logout&quot;, requireAuth, (req, res) =&gt; {
  // client should also delete the tokens
  res.status(204).end();
});
</code></pre>
</section>
<section><h2 id="common-pitfalls"><a href="#common-pitfalls" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Common pitfalls</h2><ul>
<li><strong>Storing the secret in source control.</strong> If <code>JWT_ACCESS_SECRET</code> lives in the repo, rotate it the moment the repo leaks. Use environment variables and a secret manager.</li>
<li><strong>Putting the token in <code>localStorage</code>.</strong> <code>localStorage</code> is readable by any script on the page; a single XSS bug drains it. Store tokens in an <code>HttpOnly; Secure; SameSite=Strict</code> cookie when the client is a browser. For native apps or server-to-server callers, the <code>Authorization</code> header is fine.</li>
<li><strong>Trusting the <code>alg</code> field.</strong> The classic JWT pitfall: a server that does not pin the algorithm can be tricked into accepting a token with <code>alg: none</code> or <code>alg: HS256</code> where the public RSA key is the “secret”. Always pass <code>algorithms: [&quot;HS256&quot;]</code> to <code>jwt.verify</code>.</li>
<li><strong>Long-lived access tokens.</strong> A token that never expires is a permanent backdoor. 15 minutes is a sensible default for human-driven APIs; service-to-service tokens can be longer but should still expire.</li>
<li><strong>No rate limiting on auth routes.</strong> Without a limiter, an attacker can grind the password field. <code>express-rate-limit</code> keyed on <code>(ip, email)</code> is the standard fix.</li>
<li><strong>Sensitive data in the payload.</strong> JWT payload is readable by anyone who has the token. Put only the user id, role and similar non-sensitive claims in there.</li>
<li><strong>Forgetting HTTPS.</strong> Tokens travel in the <code>Authorization</code> header on every request. Without TLS, anyone on the network can read them. Terminate TLS at the edge (Nginx, a managed load balancer, a CDN) and never run an authenticated API over plain HTTP.</li>
</ul>
</section>
<section><h2 id="end-to-end-flow"><a href="#end-to-end-flow" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>End-to-end flow</h2><p>Putting it all together, a client does this:</p>
<ol>
<li><code>POST /auth/login</code> with <code>{ email, password }</code>. Receives <code>{ accessToken, refreshToken }</code>.</li>
<li>Calls any protected route with <code>Authorization: Bearer &lt;accessToken&gt;</code>. The server returns the user data.</li>
<li>After 15 minutes, <code>accessToken</code> is expired. The client calls <code>POST /auth/refresh</code> with <code>{ refreshToken }</code> and gets a new pair.</li>
<li>On logout, the client deletes both tokens and calls <code>POST /auth/logout</code> to tell the server.</li>
</ol>
<p>That is the full loop. The server is stateless except for the user store and (optionally) a refresh-token version counter or denylist.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="how-long-should-an-access-token-live">How long should an access token live?</h3><p>For human-driven APIs, 5 to 30 minutes is the common range. Shorter is safer (a leaked token is useful for less time) but worse for UX (the client has to refresh more often). 15 minutes is a balanced default. For service-to-service tokens, an hour to a day is common; rotate them with the same refresh flow.</p>
<h3 id="where-should-i-store-jwts-on-the-client">Where should I store JWTs on the client?</h3><p>For a browser app, the safest option is an <code>HttpOnly; Secure; SameSite=Strict</code> cookie set by the server. JavaScript on the page cannot read it, so an XSS bug cannot exfiltrate it. For a mobile or server-to-server client, store the token in the OS keychain or a secret manager, and send it as <code>Authorization: Bearer …</code> on every request. <code>localStorage</code> is convenient but exposes the token to any script that runs on the page.</p>
<h3 id="should-i-use-hs256-or-rs256">Should I use HS256 or RS256?</h3><p>HS256 uses a single shared secret: the same key signs and verifies. It is fast and simple, and it is the right choice when the same service issues and verifies the token. RS256 uses a private key to sign and a public key to verify; the public key can be shared with other services so they can verify tokens without being able to mint new ones. Pick RS256 when you have multiple services that need to verify tokens but only one service that issues them.</p>
<h3 id="what-is-the-difference-between-an-access-token-and-a-refresh-token">What is the difference between an access token and a refresh token?</h3><p>The access token is the short-lived credential the client sends on every API call. The refresh token is a longer-lived credential whose only job is to mint new access tokens at the refresh endpoint. Keeping them separate means a leaked access token expires in minutes, and a leaked refresh token can be rotated and revoked. The signing secrets are different so a compromise of one does not give the attacker the other.</p>
<h3 id="do-i-need-a-database-for-users">Do I need a database for users?</h3><p>Yes — at least a place to store <code>{ id, email, passwordHash }</code>. The demo in this guide uses an in-memory array so the example is self-contained, but in production the user store is a real database table. The same JWT code works with any store because the auth layer only needs the user record after the token is verified.</p>
<h3 id="is-jwt-the-right-choice-for-sessions">Is JWT the right choice for sessions?</h3><p>JWT is a great fit for stateless APIs and for cross-service auth. It is the wrong choice when you need instant server-side revocation (no denylist), or when your sessions carry large amounts of state (the token is sent on every request, so the smaller the better). For traditional server-rendered web apps with <code>Set-Cookie</code> sessions, a server-side session table is often simpler.</p>
</section>
<section><h2 id="verdict"><a href="#verdict" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Verdict</h2><p>JWT authentication on a Node.js API is a 100-line job once the parts are clear: bcrypt for passwords, <code>jsonwebtoken</code> for signing, two routes (<code>/auth/login</code> and <code>/auth/refresh</code>) and one middleware (<code>requireAuth</code>). The hard part is not the code, it is the discipline — short access tokens, pinned algorithms, rate limiting, no secrets in the repo, and HTTPS in front of everything. Once those are in place, the API is stateless, horizontally scalable, and the same token works across every node behind the load balancer.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>What is JSON? The complete guide to JavaScript Object Notation</title>
      <link>https://debod.id/en/posts/json-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/json-explained/</guid>
      <pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate>
      <category>programming</category>
      <category>api</category>
      <category>javascript</category>
      <category>rest</category>
      <category>web-development</category>
      <description>JSON (JavaScript Object Notation) is an open, language-independent, text-based data interchange format derived from JavaScript object syntax. It represents data as a small set of types — objects, arrays, strings, numbers, booleans, and null — and is the de facto format for web APIs, configuration files, and data exchange between servers and clients. This guide explains JSON&apos;s syntax, its data types, how it compares to XML and YAML, and how to parse and generate it.</description>
      <content:encoded><![CDATA[<section><p>JSON (JavaScript Object Notation) is an open, text-based, language-independent data interchange format that represents structured data using a small set of types: objects, arrays, strings, numbers, booleans, and null. It was derived from JavaScript object syntax, and it is now standardized by both the IETF (RFC 8259, Internet Standard STD 90) and Ecma International (ECMA-404). In practice, JSON is the de facto format for web APIs, for configuration files, and for exchanging data between a server and a client. This guide covers its syntax and data types, how it relates to JavaScript, how it compares to XML and YAML, and how to parse and generate it.</p></section>
<section><h2 id="what-makes-json-so-widely-used"><a href="#what-makes-json-so-widely-used" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What makes JSON so widely used?</h2><p>JSON&#39;s design goals, set out in RFC 8259, are that it be minimal, portable, textual, and a subset of JavaScript. Those four properties explain its dominance:</p>
<ul>
<li><strong>It is tiny and readable.</strong> There is no tag markup, no namespace syntax, no schema language required to read a small document — a human can scan a JSON object and understand it at a glance.</li>
<li><strong>It is language-independent.</strong> Although it looks like JavaScript, standard libraries in almost every programming language can parse and generate JSON. You send JSON from a JavaScript client, a Python server, or a Go service, and any of them can read it.</li>
<li><strong>It transmits cleanly over the network.</strong> Because JSON is just text encoded in UTF-8, it fits naturally into HTTP request and response bodies, and it is trivial to log, cache, and test.</li>
<li><strong>It is strict enough to be unambiguous.</strong> The small, well-defined grammar means two different implementations agree on the same document — the property that made it possible to build interoperability across the web.</li>
</ul>
</section>
<section><h2 id="the-json-data-types"><a href="#the-json-data-types" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The JSON data types</h2><p>A JSON document can be any one of the following values:</p>
<ul>
<li><strong>Object</strong> — an unordered collection of zero or more name/value pairs, written as <code>{ &quot;name&quot;: value, ... }</code>. The names are strings, and the values can be any JSON type. Curly braces delimit the object and a colon separates each name from its value.</li>
<li><strong>Array</strong> — an ordered sequence of zero or more values, written as <code>[ value, value, ... ]</code> with square brackets. The values can be any JSON type, and an array&#39;s elements do not have to be the same type.</li>
<li><strong>String</strong> — a sequence of zero or more Unicode characters enclosed in double quotes, with a backslash escaping syntax for special characters.</li>
<li><strong>Number</strong> — a signed decimal number that may contain a fractional part and may use exponential (E) notation. JSON makes no distinction between integer and floating point, and it cannot represent <code>NaN</code> or <code>Infinity</code>.</li>
<li><strong>Boolean</strong> — the literal value <code>true</code> or <code>false</code>.</li>
<li><strong>null</strong> — the literal word <code>null</code>, representing an empty value.</li>
</ul>
<p>A simple example of a complete JSON object:</p>
<pre><code class="language-json">{
  &quot;name&quot;: &quot;Debod&quot;,
  &quot;isActive&quot;: true,
  &quot;posts&quot;: 54,
  &quot;tags&quot;: [&quot;json&quot;, &quot;api&quot;],
  &quot;meta&quot;: { &quot;editor&quot;: &quot;Hermes&quot;, &quot;reviewed&quot;: null }
}
</code></pre>
</section>
<section><h2 id="json-s-strict-syntax-rules"><a href="#json-s-strict-syntax-rules" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>JSON&#39;s strict syntax rules</h2><p>JSON is a subset of JavaScript object literal syntax, but it is stricter in ways that matter:</p>
<ul>
<li><strong>Strings must use double quotes</strong> (<code>&quot;name&quot;</code>), never single quotes.</li>
<li><strong>Every key must be quoted.</strong> In JavaScript you can write <code>{name: &quot;x&quot;}</code>; in JSON you must write <code>{&quot;name&quot;: &quot;x&quot;}</code>.</li>
<li><strong>No trailing commas.</strong> A comma after the last element in an object or array is invalid in JSON, though it is common in JavaScript.</li>
<li><strong>No comments.</strong> JSON deliberately has no comment syntax. Douglas Crockford removed comments because people used them to hold parsing directives, which would have destroyed interoperability.</li>
<li><strong>Only serializable types are allowed.</strong> JSON cannot represent functions, <code>Date</code>, <code>Set</code>, <code>Map</code>, <code>undefined</code>, <code>NaN</code>, or <code>Infinity</code>.</li>
<li><strong>Numbers use decimal notation.</strong> There is no support for hex literals or leading zeros.</li>
</ul>
<p>Because the grammar is so confined, a single misplaced comma or missing quote makes a JSON document invalid. That strictness is a feature: it keeps every conforming parser in agreement.</p>
</section>
<section><h2 id="json-and-javascript-parse-and-stringify"><a href="#json-and-javascript-parse-and-stringify" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>JSON and JavaScript: parse and stringify</h2><p>Even though JSON is not JavaScript, the <code>JSON</code> object in JavaScript provides the two operations you need almost every day:</p>
<ul>
<li><code>JSON.stringify(value)</code> — converts a JavaScript object or array into a JSON string so it can be stored or transmitted.</li>
<li><code>JSON.parse(string)</code> — converts a JSON string back into a JavaScript object so you can read its values.</li>
</ul>
<p>These map to the general concepts of <strong>serialization</strong> (object → text) and <strong>deserialization</strong> (text → object). On the web, you also meet JSON through the Fetch API: <code>response.json()</code> reads a response body and returns a JavaScript object directly.</p>
</section>
<section><h2 id="json-vs-xml-vs-yaml"><a href="#json-vs-xml-vs-yaml" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>JSON vs XML vs YAML</h2><p>JSON is not the only text data format, but it holds a particular niche:</p>
<ul>
<li><strong>JSON vs XML.</strong> JSON needs far less markup — no closing tags, no namespaces — so the same data is shorter and easier to read. XML predates JSON and is still used where complex document structure, namespaces, or strict schema validation (XSD) are required. For typical API payloads and config, JSON is the lighter, more common choice.</li>
<li><strong>JSON vs YAML.</strong> YAML 1.2 is a superset of JSON, meaning any valid JSON is valid YAML. YAML adds comments, multi-line strings, and a more readable indentation-based syntax, which makes it popular for configuration files where humans edit by hand. But YAML&#39;s syntax is richer and its parsers more varied, so JSON remains the safer choice for program-to-program data exchange.</li>
</ul>
<p>In short, use JSON when data crosses a network or is read by programs; use YAML when humans hand-edit configuration; use XML when you need namespaces, attributes, or formal document schemas.</p>
</section>
<section><h2 id="when-and-where-json-appears"><a href="#when-and-where-json-appears" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>When and where JSON appears</h2><p>JSON&#39;s stamp is everywhere in modern development:</p>
<ul>
<li><strong>Web APIs.</strong> REST and GraphQL APIs return JSON bodies; <code>Content-Type: application/json</code> is the norm for request and response payloads. Tools like Postman, Insomnia, and Swagger are built around it.</li>
<li><strong>Configuration and metadata.</strong> <code>package.json</code>, TypeScript config, VS Code settings, and countless tool manifests are JSON (or JSONC, which adds comments and trailing commas). JSON-LD, used for structured data on the web, is JSON that carries linked-data semantics.</li>
<li><strong>Databases.</strong> Document databases like MongoDB store records as BSON (a binary superset of JSON). PostgreSQL, MySQL, and SQL Server all support a native JSON type and query operators.</li>
<li><strong>Client/server communication.</strong> Sending structured data from a browser to a server, or between microservices, is almost always done with JSON today.</li>
</ul>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-does-json-stand-for">What does JSON stand for?</h3><p>JSON stands for JavaScript Object Notation. It is a data interchange format derived from JavaScript object syntax, standardized by RFC 8259 (an IETF Internet Standard, STD 90) and by ECMA-404.</p>
<h3 id="is-json-a-programming-language">Is JSON a programming language?</h3><p>No. JSON is a data interchange format, not a language. It contains only data — no functions, variables, or logic — and it can be parsed and generated by standard libraries in almost any programming language.</p>
<h3 id="what-are-the-valid-data-types-in-json">What are the valid data types in JSON?</h3><p>JSON supports six types: objects (name/value pairs in curly braces), arrays (ordered lists in square brackets), strings (double-quoted Unicode text), numbers (decimal values, optionally with fractions or exponents), booleans (<code>true</code>/<code>false</code>), and <code>null</code>. It cannot represent functions, dates, sets, maps, <code>undefined</code>, <code>NaN</code>, or <code>Infinity</code>.</p>
<h3 id="what-is-the-difference-between-json-and-xml">What is the difference between JSON and XML?</h3><p>JSON is a compact, minimal format derived from JavaScript, using objects, arrays, and quoted keys with no closing tags or namespaces. XML is an older markup format with opening and closing tags, attributes, and namespaces. For API payloads and configuration, JSON is shorter and more common; XML is still used where complex document structure, namespaces, or XSD schema validation are required.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>Infrastructure as Code: What it is and why it changes everything</title>
      <link>https://debod.id/en/posts/infrastructure-as-code-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/infrastructure-as-code-explained/</guid>
      <pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate>
      <category>devops</category>
      <category>devops</category>
      <category>cloud</category>
      <category>automation</category>
      <description>Infrastructure as Code replaces manual server setup with definition files you version, test, and deploy like software. Learn the declarative vs imperative split, the key benefits, and which tools are available.</description>
      <content:encoded><![CDATA[<section><p>Before Infrastructure as Code, setting up a server meant physical hardware in a data center, manual configuration through a console, and a long runbook that only one person understood. That model worked when a company had a handful of servers. It collapses when you need fifty servers across three regions, a staging environment that mirrors production, and the ability to rebuild everything from scratch in twenty minutes after an incident.</p>
<p>IaC solves this by turning infrastructure into <em>files you can read, version, and test</em>. Instead of clicking through a cloud console, you write a definition that describes what your infrastructure should look like — how many servers, what type, what network rules, what storage — and an IaC tool reads that definition and provisions the real infrastructure to match it. <a href="https://en.wikipedia.org/wiki/Infrastructure_as_code">Wikipedia</a> defines it as &quot;the process of managing and provisioning computer data center resources through machine-readable definition files, rather than via physical hardware configuration or interactive configuration tools.&quot;</p>
<p>The file becomes the single source of truth. If your infrastructure drifts — someone tweaked a setting by hand and forgot to tell anyone — you can re-apply the definition and the tool corrects the drift. If a server dies, you run the definition again and it rebuilds. If you need to replicate the environment for a new team, you check out the repository and run the tool.</p></section>
<section><h2 id="declarative-vs-imperative-the-two-ways-to-describe-infrastructure"><a href="#declarative-vs-imperative-the-two-ways-to-describe-infrastructure" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Declarative vs Imperative: the two ways to describe infrastructure</h2><p>There are two broad approaches to writing IaC definitions, and the <a href="https://en.wikipedia.org/wiki/Infrastructure_as_code">Wikipedia IaC article</a> frames the distinction clearly: &quot;The declarative approach focuses on <em>what</em> the eventual target configuration should be; the imperative focuses on <em>how</em> the infrastructure is to be changed to meet this.&quot;</p>
<p><strong>Declarative</strong> is the dominant approach in modern IaC. You write a definition that describes the desired end state — &quot;I want three servers, each with 4 GB of RAM, running Ubuntu 22.04, behind a load balancer&quot; — and the IaC tool figures out the exact sequence of API calls to get there. <a href="https://developers.cloudflare.com/terraform/index.md">Cloudflare&#39;s Terraform documentation</a> describes Terraform&#39;s model: &quot;You define the final state of the configuration rather than the step-by-step procedure.&quot; If you run the same declarative definition twice, you get the same result — a property called <em>idempotency</em> that declarative IaC delivers by design.</p>
<p><strong>Imperative</strong> defines the exact sequence of steps to reach a target state. You write a script that says &quot;create server A, then install Nginx, then configure the firewall, then start the service.&quot; This gives you more control over the individual steps but requires you to manage the procedure yourself. Tools like Ansible can work in both modes.</p>
<table>
<thead>
<tr>
<th>Aspect</th>
<th>Declarative</th>
<th>Imperative</th>
</tr>
</thead>
<tbody><tr>
<td>What you write</td>
<td>The desired end state</td>
<td>The sequence of commands to reach the state</td>
</tr>
<tr>
<td>Mental model</td>
<td>&quot;What should this look like?&quot;</td>
<td>&quot;What steps do I run?&quot;</td>
</tr>
<tr>
<td>Idempotency</td>
<td>Automatic — re-running produces the same result</td>
<td>You must handle it yourself; re-running may cause errors</td>
</tr>
<tr>
<td>Handles drift</td>
<td>Yes — tool corrects deviations automatically</td>
<td>Partially — may require manual cleanup</td>
</tr>
<tr>
<td>Learning curve</td>
<td>Lower for simple cases, higher for debugging</td>
<td>Lower for developers used to scripting</td>
</tr>
<tr>
<td>Examples</td>
<td>Terraform, CloudFormation, Pulumi</td>
<td>Ansible (imperative mode), Chef (imperative mode)</td>
</tr>
</tbody></table>
</section>
<section><h2 id="why-teams-adopt-iac"><a href="#why-teams-adopt-iac" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Why teams adopt IaC</h2><p>The <a href="https://en.wikipedia.org/wiki/Infrastructure_as_code">Wikipedia IaC history section</a> traces the practice back to 1993 and CFEngine, but cloud computing is what made it mainstream. As organisations moved from physical servers to virtual machines they could provision through an API, the manual console workflow became a bottleneck. <a href="https://developers.cloudflare.com/workers/platform/infrastructure-as-code">Cloudflare&#39;s Workers IaC documentation</a> frames the value proposition: &quot;Define and store configuration in source code repositories like GitHub, track and version changes over time, and roll back when needed.&quot;</p>
<p>The concrete benefits teams report:</p>
<p><strong>Reproducibility.</strong> Every environment — development, staging, production — is described by the same files, instantiated by the same tool. The staging environment is not &quot;mostly like production&quot;; it is a precise replica built from the same definitions.</p>
<p><strong>Version control.</strong> Infrastructure changes go through the same review process as application code. A pull request shows exactly what will change, who approved it, and the diff is auditable. If a change causes an outage, you revert the commit.</p>
<p><strong>Speed.</strong> Provisioning a new environment that once took a week takes minutes. The definition file describes the target; the tool does the work. This makes experiments cheap — if an idea does not work, you destroy the environment and try another.</p>
<p><strong>Consistency at scale.</strong> Running ten servers and running ten thousand is the same command. Manual configuration accumulates small differences — the one server where someone set <code>max_connections</code> to a different value — that become production incidents. IaC applies the definition uniformly.</p>
<p><strong>Security as code.</strong> Security policies — encryption at rest, least-privilege IAM roles, firewall rules — are expressed in the definition file. They are not a checklist someone runs manually; they are enforced every time the infrastructure is built. <a href="https://developers.cloudflare.com/terraform/index.md">Cloudflare&#39;s Terraform provider documentation</a> shows this pattern: you describe the desired state of your firewall rules and the tool enforces them, with every change tracked in the repository.</p>
</section>
<section><h2 id="popular-iac-tools"><a href="#popular-iac-tools" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Popular IaC tools</h2><p>The <a href="https://en.wikipedia.org/wiki/Infrastructure_as_code">Wikipedia tool table</a> lists a wide range. The most widely used today:</p>
<p><strong>Terraform</strong> (HashiCorp, 2014) is the most popular IaC tool in the industry. It uses its own HashiCorp Configuration Language (HCL), a declarative language designed to be readable. Terraform is provider-agnostic — it can manage resources across AWS, Azure, Google Cloud, Cloudflare, and hundreds of other providers from a single definition file. <a href="https://developers.cloudflare.com/terraform/index.md">Cloudflare&#39;s Terraform documentation</a> shows exactly this: the same Terraform configuration that manages Cloudflare DNS also manages your cloud VMs and your CI/CD pipeline.</p>
<p><strong>Ansible</strong> (Red Hat, 2012) started as a configuration management tool and grew into a general-purpose automation platform. It communicates over SSH, requires no agent on target machines, and can run in both declarative and imperative modes. Ansible playbooks describe the target state for servers, networks, and applications.</p>
<p><strong>AWS CloudFormation</strong> is AWS&#39;s native IaC service. It is declarative and JSON- or YAML-based. If you live entirely inside AWS, CloudFormation integrates tightly with every AWS service. If you use multiple clouds, Terraform&#39;s provider model is more flexible.</p>
<p><strong>Pulumi</strong> (2018) takes a different approach: you write infrastructure definitions in a general-purpose programming language — Python, TypeScript, Go, C# — instead of a domain-specific language like HCL. This makes IaC more accessible to developers who already know those languages and allows you to use loops, conditionals, and libraries in your infrastructure code.</p>
<p><strong>OpenTofu</strong> (Linux Foundation, 2023) is an open-source fork of Terraform, created after HashiCorp switched to a non-open-source license. It is functionally compatible with Terraform and aims to be the community-driven continuation of the project.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="is-iac-the-same-as-configuration-management">Is IaC the same as configuration management?</h3><p>No. IaC focuses on provisioning — creating and deleting the underlying infrastructure (servers, networks, load balancers). Configuration management focuses on the software and settings on systems that already exist. In practice, teams use both: Terraform to provision the infrastructure, Ansible or a similar tool to configure the operating system and deploy applications on top of it.</p>
<h3 id="what-is-configuration-drift-and-why-does-it-matter">What is configuration drift and why does it matter?</h3><p>Configuration drift is the gradual divergence between your infrastructure&#39;s actual state and the state described in your code. It happens when someone makes a manual change — adjusting a setting through the cloud console, tweaking a configuration file on a running server — that never gets written back to the definition files. Over time, environments that started identical become different in small, invisible ways. Those differences are where incidents hide. IaC tools detect and correct drift by re-applying the definition.</p>
<h3 id="does-iac-slow-things-down-initially">Does IaC slow things down initially?</h3><p>Setting up the first IaC definitions takes longer than clicking through a console. That is the real cost. The return on that investment comes when you need to make a second environment, recover from an incident, or audit what changed last quarter. For a one-time project with a server you will never touch again, IaC may be overkill. For anything you will maintain, update, or scale, it pays back quickly.</p>
<h3 id="which-iac-tool-should-i-start-with">Which IaC tool should I start with?</h3><p>Terraform is the most widely applicable starting point. It works across every major cloud provider, has extensive documentation, a large community, and the HCL language is purpose-built for infrastructure definitions — readable even by people who do not write code every day. If you are an AWS-only shop and want tight integration with every AWS service, CloudFormation is a natural fit. If your team is more comfortable with Python or TypeScript, Pulumi lowers the learning curve by letting you use languages you already know.</p>
</section>
<section><h2 id="verdict"><a href="#verdict" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Verdict</h2><p>Infrastructure as Code is not optional once you have more than one environment or more than one person touching infrastructure. The manual console workflow that works for a single server becomes a liability at scale — inconsistent, unauditable, unrecoverable. IaC turns infrastructure into software: testable before you deploy it, reviewable through pull requests, versionable through git, and reproducible on demand.</p>
<p>The <a href="https://en.wikipedia.org/wiki/Infrastructure_as_code">Wikipedia definition</a> captures the essence: &quot;the process of managing and provisioning computer data center resources through machine-readable definition files.&quot; That process — defining infrastructure as files, not clicks — is what makes modern cloud infrastructure manageable, auditable, and safe to change. Pick Terraform as a starting point, version your definitions from day one, and treat infrastructure changes the same way you treat code changes: with a review, a test run, and a rollback plan.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>What is HTML? How HyperText Markup Language works and why it matters</title>
      <link>https://debod.id/en/posts/html-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/html-explained/</guid>
      <pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate>
      <category>web</category>
      <category>web</category>
      <category>html</category>
      <category>frontend</category>
      <description>HTML is the foundation of every web page. Learn what HyperText Markup Language is, how HTML elements and tags work, why semantic HTML matters for SEO and accessibility, and how HTML fits with CSS and JavaScript.</description>
      <content:encoded><![CDATA[<section><p>Every time you open a web page, your browser reads a document written in HTML — HyperText Markup Language. HTML is the most basic building block of the Web. It defines the meaning and structure of web content. Other technologies describe how a page looks (CSS) and how it behaves (JavaScript). Without HTML, a browser has nothing to render.</p>
<p>The word &quot;HyperText&quot; refers to the links that connect web pages to one another. Links are the fundamental feature that turns a collection of documents into a web — you upload content and link it to pages created by other people, becoming an active participant in the World Wide Web. The <a href="https://developer.mozilla.org/en-US/docs/Web/HTML">MDN HTML article</a> describes it simply: HTML &quot;uses markup to annotate text, images, and other content for display in a Web browser.&quot;</p></section>
<section><h2 id="elements-and-tags-the-anatomy-of-html"><a href="#elements-and-tags-the-anatomy-of-html" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Elements and tags: the anatomy of HTML</h2><p>HTML documents are built from <strong>elements</strong>. An element is made of three parts: an opening tag, the content, and a closing tag. The tags — written inside angle brackets <code>&lt;</code> and <code>&gt;</code> — tell the browser how to interpret the content between them.</p>
<pre><code>&lt;p&gt;This is a paragraph element.&lt;/p&gt;
</code></pre>
<p><code>&lt;p&gt;</code> is the opening tag. <code>This is a paragraph element.</code> is the content. <code>&lt;/p&gt;</code> is the closing tag. The forward slash before the element name signals the closing tag.</p>
<p>Some elements have <strong>attributes</strong> — additional values that configure the element or adjust its behavior. Attributes are written inside the opening tag:</p>
<pre><code>&lt;a href=&quot;https://example.com&quot;&gt;Visit Example&lt;/a&gt;
&lt;img src=&quot;photo.jpg&quot; alt=&quot;A landscape photograph&quot; /&gt;
</code></pre>
<p>Here, <code>href</code> is an attribute of the <code>&lt;a&gt;</code> (anchor) element, and <code>src</code> and <code>alt</code> are attributes of the <code>&lt;img&gt;</code> element.</p>
<p>Element tag names are case-insensitive — <code>&lt;TITLE&gt;</code>, <code>&lt;Title&gt;</code>, and <code>&lt;title&gt;</code> all work — but the <a href="https://developer.mozilla.org/en-US/docs/Web/HTML">MDN recommendation</a> is to write tags in lowercase. That is the universal convention.</p>
<p>Not all elements have content and closing tags. <strong>Void elements</strong> (also called empty elements) have no content and no closing tag. The <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/img"><code>&lt;img&gt;</code></a> element is a common example: it embeds an image but wraps no text. These elements may optionally use a trailing slash in the opening tag (<code>&lt;img src=&quot;photo.jpg&quot; alt=&quot;...&quot; /&gt;</code>), which is permitted but not required.</p>
</section>
<section><h2 id="a-complete-html-document"><a href="#a-complete-html-document" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>A complete HTML document</h2><p>The <a href="https://developer.mozilla.org/en-US/docs/Learn_web_development/Getting_started/Your_first_website/Creating_the_content">MDN first-HTML tutorial</a> shows a minimal complete HTML page:</p>
<pre><code>&lt;!doctype html&gt;
&lt;html lang=&quot;en&quot;&gt;
  &lt;head&gt;
    &lt;meta charset=&quot;utf-8&quot; /&gt;
    &lt;title&gt;My test page&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;img src=&quot;&quot; alt=&quot;My test image&quot; /&gt;
  &lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>Here is what each part does:</p>
<ul>
<li><code>&lt;!doctype html&gt;</code> is the <strong>doctype</strong> — a required preamble that tells the browser this is an HTML5 document. Modern browsers use it to switch to standards mode.</li>
<li><code>&lt;html lang=&quot;en&quot;&gt;</code> is the <strong>root element</strong>. Everything on the page lives inside it. The <code>lang</code> attribute sets the document&#39;s primary language.</li>
<li><code>&lt;head&gt;</code> is the <strong>head</strong> — a container for content that <em>is not displayed</em> on the page. It holds the page title, character encoding, links to CSS stylesheets, and metadata.</li>
<li><code>&lt;meta charset=&quot;utf-8&quot;&gt;</code> sets the character set to UTF-8, which covers characters from most written languages. There is no reason to omit this.</li>
<li><code>&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width&quot;&gt;</code> tells the browser to match the page width to the device screen, preventing mobile browsers from shrinking the page.</li>
<li><code>&lt;title&gt;</code> sets the text shown in the browser tab and used when bookmarking the page.</li>
<li><code>&lt;body&gt;</code> contains <em>all</em> visible content — text, images, forms, links, and more.</li>
</ul>
<p>Browsers do not display the HTML tags themselves. They use the tags to interpret the content and render the page. A paragraph <code>&lt;p&gt;...&lt;/p&gt;</code> becomes a block of text; an <code>&lt;a href=&quot;...&quot;&gt;</code> becomes a clickable link; an <code>&lt;img src=&quot;...&quot;&gt;</code> becomes a displayed image.</p>
</section>
<section><h2 id="semantic-html-meaning-over-appearance"><a href="#semantic-html-meaning-over-appearance" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Semantic HTML: meaning over appearance</h2><p>One of the most important concepts in HTML is <strong>semantics</strong> — writing markup that describes the <em>meaning</em> of content, not just its appearance.</p>
<p>The <a href="https://developer.mozilla.org/en-US/docs/Glossary/Semantics">MDN Semantics glossary</a> explains: &quot;In HTML, the <code>&lt;h1&gt;</code> element is a semantic element that gives the text it wraps around the role of &#39;a top level heading on your page.&#39;&quot; You can make any element <em>look</em> like an <code>&lt;h1&gt;</code> heading with CSS, but it will not carry the meaning of a heading for search engines, screen readers, or browser features.</p>
<p>The benefits of semantic HTML, according to MDN:</p>
<ul>
<li><strong>Search engines</strong> treat semantic content as important keywords that influence page rankings.</li>
<li><strong>Screen readers</strong> use semantic elements as signposts to help visually impaired users navigate a page.</li>
<li><strong>Developers</strong> find meaningful markup easier to read and maintain than a wall of <code>&lt;div&gt;</code> tags.</li>
<li>Semantic naming <strong>mirrors proper component naming</strong> in modern frontend frameworks.</li>
</ul>
<p>There are roughly one hundred semantic elements in HTML5. The most important:</p>
<table>
<thead>
<tr>
<th>Element</th>
<th>Meaning</th>
</tr>
</thead>
<tbody><tr>
<td><code>&lt;header&gt;</code></td>
<td>Introductory content or a set of navigational aids</td>
</tr>
<tr>
<td><code>&lt;nav&gt;</code></td>
<td>A section with navigation links</td>
</tr>
<tr>
<td><code>&lt;main&gt;</code></td>
<td>The dominant content of the document (one per page)</td>
</tr>
<tr>
<td><code>&lt;article&gt;</code></td>
<td>Self-contained content that could stand alone (a blog post, a news story)</td>
</tr>
<tr>
<td><code>&lt;section&gt;</code></td>
<td>A thematic grouping of content, typically with a heading</td>
</tr>
<tr>
<td><code>&lt;aside&gt;</code></td>
<td>Content tangentially related to the surrounding content (a sidebar)</td>
</tr>
<tr>
<td><code>&lt;footer&gt;</code></td>
<td>Footer for its nearest sectioning root or the page</td>
</tr>
<tr>
<td><code>&lt;h1&gt;</code>–<code>&lt;h6&gt;</code></td>
<td>Six levels of headings, where <code>&lt;h1&gt;</code> is the page title</td>
</tr>
<tr>
<td><code>&lt;p&gt;</code></td>
<td>A paragraph of text</td>
</tr>
<tr>
<td><code>&lt;ul&gt;</code>, <code>&lt;ol&gt;</code>, <code>&lt;li&gt;</code></td>
<td>Unordered list, ordered list, list item</td>
</tr>
<tr>
<td><code>&lt;figure&gt;</code>, <code>&lt;figcaption&gt;</code></td>
<td>An image, diagram, or code with a caption</td>
</tr>
</tbody></table>
<p>Compare a semantic page to one built only from <code>&lt;div&gt;</code> elements with classes like <code>class=&quot;sidebar&quot;</code> or <code>class=&quot;red-text&quot;</code>. The semantic version carries machine-readable meaning; the <code>&lt;div&gt;</code> version is just a styled box.</p>
<p>The <a href="https://en.wikipedia.org/wiki/HTML">Wikipedia HTML article</a> notes that the World Wide Web Consortium (W3C) &quot;encouraged the use of CSS over explicit presentational HTML since 1997&quot; — keeping HTML for structure and semantics, and CSS for appearance.</p>
</section>
<section><h2 id="html-css-and-javascript-the-three-layers"><a href="#html-css-and-javascript-the-three-layers" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>HTML, CSS, and JavaScript: the three layers</h2><p>HTML handles content and structure. Two other technologies work alongside it:</p>
<p><strong>CSS</strong> (Cascading Style Sheets) describes how HTML elements look — colors, fonts, spacing, layout. The <a href="https://en.wikipedia.org/wiki/HTML">Wikipedia HTML article</a> describes the relationship: &quot;It is often assisted by technologies such as Cascading Style Sheets (CSS) and scripting languages such as JavaScript.&quot; Separating CSS from HTML keeps both files easier to maintain.</p>
<p><strong>JavaScript</strong> adds behavior and interactivity — handling clicks, validating forms, updating content without a page reload. JavaScript can read and modify the HTML document through the DOM (Document Object Model).</p>
<p>These three layers stack: HTML provides the structure, CSS makes it look right, and JavaScript makes it work. A well-built web page keeps them separate: HTML for content, CSS for presentation, JavaScript for behavior.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-does-html-do">What does HTML do?</h3><p>HTML structures and annotates web content so browsers can render it. It marks up text as headings, paragraphs, lists, or links; embeds images, videos, and forms; and provides the semantic meaning that search engines and assistive technologies rely on. HTML does not style content (that is CSS) or define behavior (that is JavaScript).</p>
<h3 id="is-html-a-programming-language">Is HTML a programming language?</h3><p>HTML is a <strong>markup language</strong>, not a programming language. A programming language can execute logic, loops, and conditions. A markup language annotates content to describe its structure and meaning. HTML describes what content <em>is</em> — a heading, a paragraph, a list — not what the computer should <em>do</em>. HTML, CSS, and SQL are the most common markup/data-query languages in web development, but they are distinct from Turing-complete programming languages.</p>
<h3 id="why-does-semantic-html-matter">Why does semantic HTML matter?</h3><p>Semantic elements tell browsers, search engines, and screen readers what each part of a page means — not just how it looks. A <code>&lt;nav&gt;</code> element marks a navigation region; a <code>&lt;article&gt;</code> marks self-contained content; an <code>&lt;h1&gt;</code> marks the page title. This structure lets assistive technologies skip to the main content, lets search engines understand the page hierarchy, and makes the code readable by developers. <a href="https://developer.mozilla.org/en-US/docs/Glossary/Semantics">MDN&#39;s Semantics article</a> lists these as the primary benefits: better SEO, accessible navigation, maintainable code, and proper browser features.</p>
<h3 id="how-does-a-browser-read-html">How does a browser read HTML?</h3><p>When you request a URL, the web server sends an HTML document. The browser reads the document from top to bottom, parsing each tag and building a tree structure called the DOM (Document Object Model). The DOM represents every element — headings, paragraphs, images, links — as objects the browser can manipulate. The browser then renders the DOM visually on screen. Tags are not displayed; they are instructions the browser follows to interpret the content.</p>
<h3 id="what-is-the-difference-between-html-and-html5">What is the difference between HTML and HTML5?</h3><p>HTML (the original) was developed by Tim Berners-Lee in 1993. The <a href="https://en.wikipedia.org/wiki/HTML">Wikipedia HTML article</a> tracks its history: &quot;HTML 4 was published in 1997; HTML5 was a major evolution, adding native support for video, audio, canvas graphics, and web applications.&quot; HTML5 also introduced semantic elements (<code>&lt;header&gt;</code>, <code>&lt;nav&gt;</code>, <code>&lt;article&gt;</code>, <code>&lt;section&gt;</code>, <code>&lt;footer&gt;</code>) that were absent in earlier versions. Today, &quot;HTML&quot; and &quot;HTML5&quot; are often used interchangeably — the WHATWG maintains a &quot;living standard&quot; that evolves continuously, and all modern browsers implement HTML5 features.</p>
</section>
<section><h2 id="verdict"><a href="#verdict" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Verdict</h2><p>HTML is the foundation everything else on the Web is built on. Understanding it means understanding the difference between structure (HTML), presentation (CSS), and behavior (JavaScript) — and why keeping those three layers separate makes sites faster, more accessible, and easier to maintain.</p>
<p>The key practical takeaways: write semantic HTML (use <code>&lt;article&gt;</code> when you mean an article, <code>&lt;nav&gt;</code> when you mean navigation), keep content separate from styling, and write <code>&lt;!doctype html&gt;</code> and <code>&lt;meta charset=&quot;utf-8&quot;&gt;</code> in every document. A page built with meaningful HTML elements is automatically more accessible to screen readers, more readable to search engines, and easier for developers to work with — all without writing a single line of CSS or JavaScript.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>What is HSTS (HTTP Strict Transport Security)? How it works and why it matters</title>
      <link>https://debod.id/en/posts/hsts-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/hsts-explained/</guid>
      <pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate>
      <category>security</category>
      <category>security</category>
      <category>https</category>
      <category>web</category>
      <category>networking</category>
      <category>tls</category>
      <category>encryption</category>
      <description>HTTP Strict Transport Security (HSTS) is a web security policy that forces browsers to only use HTTPS. Learn how the Strict-Transport-Security header works, what it protects against, and how HSTS preload lists eliminate the SSL-stripping window.</description>
      <content:encoded><![CDATA[<section><p>Every time you visit <code>http://example.com</code> instead of <code>https://example.com</code>, your browser makes the first request over an unencrypted connection. Even if the server immediately redirects to HTTPS, an attacker sitting between you and the server can intercept that initial unencrypted request — and all the data it carries. This is called an <strong>SSL stripping attack</strong>, and it was the problem HTTP Strict Transport Security was designed to solve.</p>
<p>HSTS (HTTP Strict Transport Security) is a web security policy mechanism defined in <a href="https://www.rfc-editor.org/rfc/rfc6797">RFC 6797</a> that tells browsers to only ever connect to a domain over HTTPS. Once a browser receives the HSTS header from a server over a secure connection, it automatically upgrades any future request — including those typed as <code>http://</code> — to HTTPS, before sending a single byte over the wire.</p></section>
<section><h2 id="the-problem-hsts-solves"><a href="#the-problem-hsts-solves" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The problem HSTS solves</h2><p>The web uses HTTP over TLS (HTTPS) to protect communication between browsers and servers. HTTPS encrypts data in transit, authenticates the server, and prevents tampering. But HTTP — the unencrypted predecessor — still works on most servers, and users still type <code>http://</code> into their address bars.</p>
<p>This creates an attack window. A man-in-the-middle (MITM) attacker on a shared Wi-Fi network can intercept the first unencrypted HTTP request a user makes to a website. The attacker forwards nothing, or forwards a tampered version. The user sees the page, but the attacker has read or modified everything. This class of attack — where the attacker downgrades a secure connection to an insecure one — is called a <strong>protocol downgrade attack</strong> or <strong>SSL stripping</strong>.</p>
<p>The <a href="https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security">Wikipedia article on HSTS</a> describes the mechanism: when a server sends the <code>Strict-Transport-Security</code> header over HTTPS, it instructs user agents to &quot;automatically interact with it using only HTTPS connections.&quot; Browsers that obey this policy refuse to make plain HTTP requests to that domain.</p>
</section>
<section><h2 id="how-the-strict-transport-security-header-works"><a href="#how-the-strict-transport-security-header-works" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How the Strict-Transport-Security header works</h2><p>The <a href="https://developer.mozilla.org/en-US/docs/Web/Security/Transport_Layer_Security">MDN TLS documentation</a> explains that browsers only respect the HSTS header when it arrives over a secure (HTTPS) connection. An HTTP response carrying this header is simply ignored — a server cannot force browsers to use HTTPS over an insecure channel.</p>
<p>A minimal HSTS header looks like this:</p>
<pre><code>Strict-Transport-Security: max-age=31536000
</code></pre>
<p>This tells the browser to only use HTTPS when connecting to this domain for the next 315,360 seconds — one year. After the browser has received this header once, it will refuse HTTP requests to the domain until the <code>max-age</code> period expires.</p>
<h3 id="the-three-hsts-directives">The three HSTS directives</h3><p>The HSTS specification (<a href="https://www.rfc-editor.org/rfc/rfc6797">RFC 6797</a>) defines three directives:</p>
<table>
<thead>
<tr>
<th>Directive</th>
<th>What it does</th>
<th>Example</th>
</tr>
</thead>
<tbody><tr>
<td><code>max-age=&lt;seconds&gt;</code></td>
<td>How long the browser should enforce HTTPS-only (in seconds)</td>
<td><code>max-age=31536000</code> (1 year)</td>
</tr>
<tr>
<td><code>includeSubDomains</code></td>
<td>Applies the policy to all subdomains too</td>
<td><code>max-age=31536000; includeSubDomains</code></td>
</tr>
<tr>
<td><code>preload</code></td>
<td>Requests inclusion in the browser preload list</td>
<td><code>max-age=31536000; includeSubDomains; preload</code></td>
</tr>
</tbody></table>
<p><code>max-age</code> is the only mandatory directive. A value of <code>0</code> disables HSTS for that domain. Security-sensitive sites typically use <code>max-age=63072000</code> (two years) or higher with <code>includeSubDomains</code> and <code>preload</code>.</p>
<h3 id="what-hsts-protects-against">What HSTS protects against</h3><p>The <a href="https://en.wikipedia.org/wiki/Transport_Layer_Security">Wikipedia TLS article</a> describes TLS as providing three properties: <strong>confidentiality</strong> (encryption, so eavesdroppers cannot read data), <strong>integrity</strong> (tampering is detected), and <strong>authenticity</strong> (client and server prove their identities). HSTS extends these guarantees by preventing the protocol downgrade that would break all three.</p>
<p>Specifically, HSTS prevents:</p>
<ul>
<li><strong>SSL stripping attacks</strong> — where an attacker intercepts the first HTTP request and leaves the user on an unencrypted connection.</li>
<li><strong>Mixed content warnings</strong> — browsers automatically upgrade subresource requests from <code>http://</code> to <code>https://</code> on HSTS-enabled sites.</li>
<li><strong>Cookie hijacking</strong> — session cookies sent over HTTP can be stolen; HSTS ensures all traffic (including cookie transmission) goes over HTTPS.</li>
</ul>
<p>As the <a href="https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security">Wikipedia HSTS article</a> notes, HSTS also addresses &quot;cookie hijacking,&quot; where an attacker steals a session cookie over an unencrypted connection.</p>
</section>
<section><h2 id="the-trust-on-first-use-limitation"><a href="#the-trust-on-first-use-limitation" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The trust-on-first-use limitation</h2><p>The main weakness of HSTS is described in the <a href="https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security">Wikipedia HSTS article</a>: the protection &quot;normally only applies after a user has visited the site at least once.&quot; The first visit to a domain — whether typed manually, clicked from an email, or followed from a bookmark — is still made over HTTP unless the browser already has a cached HSTS policy.</p>
<p>This is called <strong>trust on first use</strong> (TOFU). An attacker who can intercept that first connection can still perform an SSL stripping attack, because the browser has not yet received the HSTS header.</p>
<h3 id="the-hsts-preload-list">The HSTS preload list</h3><p>To eliminate this first-visit window, major browsers maintain <strong>HSTS preload lists</strong> — hard-coded lists of domains that must always be accessed over HTTPS, baked into the browser binary. The <a href="https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security">Wikipedia HSTS article</a> explains: &quot;If a domain is on this list, the browser skips the initial request and encrypts all communication immediately.&quot;</p>
<p>The Chrome HSTS preload list (also used by Safari, Firefox, and other Chromium-based browsers) is managed at <a href="https://hstspreload.org">hstspreload.org</a>. Any domain can submit itself, provided it meets these requirements:</p>
<ul>
<li>A valid certificate for the domain and all subdomains.</li>
<li>Serving a redirect from HTTP to HTTPS, or refusing HTTP connections entirely.</li>
<li>Serving the HSTS header on the HTTPS response with at least <code>max-age=31536000; includeSubDomains; preload</code>.</li>
</ul>
<p>Once on the list, removing a domain is difficult — it requires browser updates, which can take months. This makes the preload commitment serious. Sites should only request preload after they are certain HTTPS will be permanent.</p>
</section>
<section><h2 id="mixed-content-and-hsts"><a href="#mixed-content-and-hsts" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Mixed content and HSTS</h2><p>The <a href="https://developer.mozilla.org/en-US/docs/Web/Security/Transport_Layer_Security">MDN TLS documentation</a> explains that an HTTPS page loading subresources over HTTP is called <strong>mixed content</strong> — the main page is encrypted, but the scripts, images, or stylesheets loaded over HTTP are not. An attacker who intercepts the page can replace those subresources with malicious code.</p>
<p>HSTS helps here too. When a browser has a cached HSTS policy for a domain, it automatically upgrades any <code>http://</code> subresource requests to <code>https://</code> before sending them. The <a href="https://developer.mozilla.org/en-US/docs/Web/Security/Transport_Layer_Security">MDN TLS documentation</a> describes how browsers &quot;either upgrade the load request to use HTTPS, or block the request entirely&quot; for mixed content on secure pages.</p>
</section>
<section><h2 id="deploying-hsts-practical-steps"><a href="#deploying-hsts-practical-steps" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Deploying HSTS: practical steps</h2><ol>
<li><strong>Enable HTTPS first</strong> — HSTS only works over HTTPS. A site must fully migrate to HTTPS before setting the header.</li>
<li><strong>Start with a short max-age</strong> — begin with <code>max-age=300</code> (5 minutes) and verify nothing breaks.</li>
<li><strong>Increase gradually</strong> — once verified, increase to <code>max-age=86400</code> (1 day), then a week, then a month.</li>
<li><strong>Add includeSubDomains</strong> — once stable, add the directive to cover all subdomains.</li>
<li><strong>Request preload</strong> — after running at <code>max-age=31536000; includeSubDomains</code> for a few weeks with no issues, submit to hstspreload.org.</li>
</ol>
<p>The <a href="https://developer.mozilla.org/en-US/docs/Web/Security/Transport_Layer_Security">MDN TLS documentation</a> also recommends sending the <code>Strict-Transport-Security</code> header on every HTTPS response, not just the first one, to refresh the browser&#39;s policy and extend its expiry.</p>
</section>
<section><h2 id="hsts-and-cdn-edge-deployments"><a href="#hsts-and-cdn-edge-deployments" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>HSTS and CDN edge deployments</h2><p>When a site uses a CDN, the HSTS header must be set at the CDN edge, not just the origin server. Cloudflare, for example, adds HSTS headers through its dashboard (Speed → Optimization → HSTS) or via a <code>Strict-Transport-Security</code> Page Rule. If the CDN terminates TLS, the HSTS header should be set on the CDN&#39;s response — it is the response the browser receives.</p>
<p>Sites using Cloudflare Pages can set HSTS via the <code>public/_headers</code> file:</p>
<pre><code>Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
</code></pre>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-does-hsts-protect-against">What does HSTS protect against?</h3><p>HSTS protects against protocol downgrade attacks (SSL stripping) and cookie hijacking. When a browser has a cached HSTS policy for a domain, it refuses to make unencrypted HTTP requests to that domain — even if the user types <code>http://</code> directly. This prevents attackers on shared networks from intercepting the first request or stealing session cookies.</p>
<h3 id="does-hsts-make-a-site-completely-secure">Does HSTS make a site completely secure?</h3><p>No. HSTS protects the <em>transport</em> layer — it ensures all connections use HTTPS. It does not protect against vulnerabilities in the application itself, such as XSS (cross-site scripting), CSRF, SQL injection, or weak passwords. It also cannot protect the very first visit to a domain, unless the domain is on the browser&#39;s HSTS preload list. HSTS is one layer of a defense-in-depth strategy.</p>
<h3 id="why-is-a-preload-list-needed-if-max-age-is-set">Why is a preload list needed if max-age is set?</h3><p>The first visit to a domain is made before the browser has received the HSTS header. An attacker who intercepts this first unencrypted request can strip HTTPS and leave the user on HTTP. The preload list bakes the HTTPS requirement into the browser itself, so the upgrade happens on the very first request — before any network traffic leaves the browser. The <a href="https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security">Wikipedia HSTS article</a> describes this as addressing &quot;the problem of the first request.&quot;</p>
<h3 id="what-is-the-difference-between-hsts-and-https-redirect">What is the difference between HSTS and HTTPS redirect?</h3><p>An HTTPS redirect (a server responding to <code>http://</code> requests with a <code>301 Moved Permanently</code> or <code>302 Found</code> redirect to the HTTPS URL) tells the browser where to go. HSTS tells the browser never to ask for the HTTP URL in the first place. Redirects can be intercepted and stripped before they take effect; HSTS eliminates the unencrypted request. The two work together: redirects handle existing links and bookmarks, while HSTS prevents the initial unencrypted connection.</p>
<h3 id="how-does-hsts-interact-with-subdomains">How does HSTS interact with subdomains?</h3><p>The <code>includeSubDomains</code> directive in the HSTS header extends the HTTPS-only policy to every subdomain of the domain. If <code>example.com</code> sets <code>Strict-Transport-Security: max-age=31536000; includeSubDomains</code>, then <code>api.example.com</code>, <code>cdn.example.com</code>, and any other subdomain are also covered. Without <code>includeSubDomains</code>, only the exact domain receives HSTS protection — a subdomain served over HTTP is unaffected.</p>
</section>
<section><h2 id="verdict"><a href="#verdict" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Verdict</h2><p>HSTS is a simple, high-impact security header. A single <code>Strict-Transport-Security</code> response header eliminates the SSL stripping attack class on every subsequent visit — for free, with no performance cost, and minimal deployment risk when introduced with a short <code>max-age</code>. The combination of a long <code>max-age</code>, <code>includeSubDomains</code>, and preload registration makes it a near-complete defense against protocol downgrade for the vast majority of users.</p>
<p>The practical takeaway: set <code>max-age=31536000; includeSubDomains</code> on your HTTPS responses today, submit to hstspreload.org once stable, and never serve any part of your site over HTTP again.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>What is GraphQL? How it works and when you should use it</title>
      <link>https://debod.id/en/posts/graphql-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/graphql-explained/</guid>
      <pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate>
      <category>development</category>
      <category>graphql</category>
      <category>api</category>
      <category>rest</category>
      <category>backend</category>
      <description>GraphQL is an open-source query language for APIs that lets clients request exactly the data they need. This guide explains how GraphQL works, its type system, queries and mutations, and how it compares to REST.</description>
      <content:encoded><![CDATA[<section><p>GraphQL is an open-source data query and manipulation language for APIs. Instead of calling many endpoints and receiving fixed response shapes, a client sends a query that describes exactly what data it wants, and the server returns precisely that data in the same shape. GraphQL is not tied to any particular database or storage engine, and several open-source runtimes implement it for different languages. This guide explains how GraphQL works, its core concepts of schema, queries, mutations and subscriptions, and how it compares to REST so you can decide when it is the right fit.</p></section>
<section><h2 id="what-problem-does-graphql-solve"><a href="#what-problem-does-graphql-solve" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What problem does GraphQL solve?</h2><p>Traditional REST and endpoint-based APIs have two common problems: over-fetching and under-fetching. Over-fetching happens when an endpoint returns a large fixed response and a client needs only a few fields — it downloads data it will never use. Under-fetching happens when a client needs data spread across several resources, so it has to make multiple round trips, one per endpoint, and then assemble the pieces.</p>
<p>GraphQL addresses both with a single endpoint and a declarative query. The client states the exact fields it needs, and the server resolves them from however many underlying data sources are required. The client gets one predictable response, shaped exactly like the query, with no more and no less than what it asked for.</p>
</section>
<section><h2 id="a-brief-history"><a href="#a-brief-history" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>A brief history</h2><p>Facebook started developing GraphQL in 2012 and released a draft specification with a reference implementation as open source in 2015. In 2018 the project moved to the newly established GraphQL Foundation, hosted by the non-profit Linux Foundation. Since then many public APIs have adopted GraphQL, including those of GitHub, Shopify and Yelp. The specification is open, and the reference implementation GraphQL.js is MIT-licensed.</p>
</section>
<section><h2 id="the-schema-and-type-system"><a href="#the-schema-and-type-system" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The schema and type system</h2><p>A GraphQL service is built by defining a schema: the collection of types and fields that completely describes what the API can do. The schema uses a small type system that includes scalars (such as <code>String</code>, <code>Int</code>, <code>Float</code>, <code>Boolean</code>, and <code>ID</code>), object types, and a root <code>Query</code> type that lists everything that can be queried.</p>
<pre><code class="language-graphql">type Query {
  currentUser: User
}

type User {
  id: ID!
  name: String!
  email: String
  posts: [Post]
}

type Post {
  id: ID!
  title: String!
  published: Boolean!
}
</code></pre>
<p>A field is non-nullable when it ends with an exclamation mark, and a field can be defined as a list by wrapping its type in square brackets. The schema is the contract between client and server: the client can explore it and build queries that are guaranteed to match, and the server validates every request against it before execution.</p>
</section>
<section><h2 id="queries"><a href="#queries" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Queries</h2><p>A query defines the exact shape of the data the client needs. The server validates the query against the schema, executes it using functions called resolvers (one per field), and returns a result that mirrors the shape of the original query, typically as JSON.</p>
<pre><code class="language-graphql">query {
  currentUser {
    name
    email
  }
}
</code></pre>
<pre><code class="language-json">{
  &quot;data&quot;: {
    &quot;currentUser&quot;: {
      &quot;name&quot;: &quot;John Doe&quot;,
      &quot;email&quot;: &quot;john@example.com&quot;
    }
  }
}
</code></pre>
<p>Because the client selects the fields, adding a field to the query changes only the response, not the endpoint. There is no need to version the API or spin up a new endpoint when a client needs more data — it simply asks for it.</p>
</section>
<section><h2 id="mutations-and-subscriptions"><a href="#mutations-and-subscriptions" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Mutations and subscriptions</h2><p>GraphQL has three kinds of operations. Queries read data. Mutations modify data — creating, updating or deleting it — and they accept input variables and can return data matching the mutation&#39;s shape so the client gets the result of the change in one round trip.</p>
<pre><code class="language-graphql">mutation CreatePost($title: String!) {
  createPost(title: $title) {
    id
    title
    published
  }
}
</code></pre>
<p>Subscriptions push live updates from the server to the client. A client subscribes once and receives updates whenever the relevant data changes, commonly transported over WebSockets. Subscriptions are how GraphQL supports real-time features such as notifications and live dashboards.</p>
</section>
<section><h2 id="how-graphql-works-over-http"><a href="#how-graphql-works-over-http" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How GraphQL works over HTTP</h2><p>GraphQL is typically served over HTTP: a client sends a query, typically as <code>POST</code> to a single endpoint such as <code>/graphql</code>, and receives the result. The GraphQL specification is agnostic about transport, so the same query language can work over other transports such as WebSockets for subscriptions.</p>
<p>One key difference from REST is the single endpoint. A REST API usually exposes many URL endpoints, one per resource, and uses HTTP methods such as <code>GET</code> and <code>POST</code>. A GraphQL API exposes one endpoint and lets the body of the request describe the operation and the fields wanted. HTTP remains the underlying protocol, so the caching, status and header semantics defined in RFC 9110 still apply to the transport.</p>
</section>
<section><h2 id="graphql-vs-rest"><a href="#graphql-vs-rest" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>GraphQL vs REST</h2><p>The two are not a strict either/or. GraphQL is often considered an alternative to REST, but it does not replace it — a GraphQL server can even sit in front of REST APIs, aggregating them behind a single schema, which is a common pattern for a microservice architecture acting as an API gateway.</p>
<table>
<thead>
<tr>
<th>Aspect</th>
<th>REST</th>
<th>GraphQL</th>
</tr>
</thead>
<tbody><tr>
<td>Endpoints</td>
<td>One per resource, many URLs</td>
<td>Single endpoint</td>
</tr>
<tr>
<td>Response shape</td>
<td>Fixed per endpoint</td>
<td>Client-defined per query</td>
</tr>
<tr>
<td>Over/under-fetching</td>
<td>Common</td>
<td>Avoided by design</td>
</tr>
<tr>
<td>Schema</td>
<td>Often implicit</td>
<td>Explicit, strongly typed, self-describing</td>
</tr>
<tr>
<td>Versioning</td>
<td>Often via URL versioning</td>
<td>Schema evolution with <code>@deprecated</code></td>
</tr>
<tr>
<td>Real time</td>
<td>Usually via polling or WebSockets</td>
<td>Subscriptions built-in</td>
</tr>
</tbody></table>
<p>REST shines when APIs are simple, stable and well-understood, and it benefits from mature caching across the web. GraphQL shines when clients have varied and evolving data needs — especially mobile clients and front-end applications with many views — and when you want one strongly typed contract that clients can explore.</p>
</section>
<section><h2 id="when-to-use-graphql"><a href="#when-to-use-graphql" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>When to use GraphQL</h2><p>Use GraphQL when different clients need different fields of the same data, when a client-side view composes data from several sources in one round trip, or when you want a self-documenting, strongly typed API. It is also a strong fit as an API gateway in front of multiple services.</p>
<ul>
<li><strong>Mobile + web parity</strong>: a small screen and a large screen rarely want the same fields; GraphQL lets each ask for exactly what it renders.</li>
<li><strong>Aggregation</strong>: one schema that combines data from several back-end services without the client making many calls.</li>
<li><strong>Rapid front-end evolution</strong>: adding fields usually means editing the query, not the server.</li>
</ul>
<p>Skip GraphQL when your API is a small set of stable endpoints, when you rely heavily on HTTP caching of raw responses, or when a straightforward REST surface is enough — GraphQL adds conceptual and operational overhead (resolvers, caching strategy, tooling) that is only worth paying when the flexibility pays off.</p>
</section>
<section><h2 id="security-considerations"><a href="#security-considerations" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Security considerations</h2><p>GraphQL introduces a few concerns specific to its design. Because a single request can ask for nested data, you should protect the server against expensive or abusive queries:</p>
<ul>
<li><strong>Query depth limits</strong>: cap how deeply a query can nest to stop recursive queries from exhausting resources.</li>
<li><strong>Query cost / complexity limits</strong>: estimate and cap the total work a query represents.</li>
<li><strong>Timeouts</strong>: bound how long the server spends executing a single operation.</li>
<li><strong>Introspection control</strong>: introspection, which lets clients explore the schema, can be disabled in production if you do not want to expose the full schema.</li>
<li><strong>Variables and batching</strong>: as with any API, validate input and protect against attacks such as injection and denial of service, and consider batching attacks where many queries are sent in one request.</li>
</ul>
<p>Many of the security concerns with GraphQL are the same ones that apply to any API — authentication, authorization, input validation — handled in your business logic layer with common patterns such as JWTs or OAuth.</p>
</section>
<section><h2 id="conclusion"><a href="#conclusion" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Conclusion</h2><p>GraphQL is an open, transport-agnostic query language that gives clients precise control over the data they receive. Its explicit schema, strong typing and three operation types (queries, mutations, subscriptions) solve over-fetching and under-fetching, and its single-endpoint design exposes a self-documenting contract. Choose it when your clients have varied and evolving data needs, and keep REST when a simple, stable set of endpoints and mature HTTP caching are the better fit. For the request/response side of API design, see the <a href="/en/posts/rest-api-nodejs-express/">REST API with Node.js and Express</a> guide, and for how these endpoints connect across services, read <a href="/en/posts/api-gateway-explained/">what an API gateway is</a>.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-is-graphql-vs-rest">What is GraphQL vs REST?</h3><p>GraphQL is a query language where the client asks for exactly the fields it needs from a single endpoint. REST is an architectural style where each resource has its own URL endpoint and a fixed response shape. GraphQL avoids over-fetching and under-fetching by letting the client shape the response, while REST benefits from simpler URLs and mature HTTP caching. They can coexist — a GraphQL server can aggregate multiple REST APIs behind one schema.</p>
<h3 id="is-graphql-a-database-language-like-sql">Is GraphQL a database language like SQL?</h3><p>No. GraphQL is a specification for remote client-server communication and is agnostic to the data source. Unlike SQL, it does not query a database directly. A GraphQL server coordinates resolver functions, which delegate to a business-logic layer that talks to whatever data sources you use — databases, other APIs or in-memory data.</p>
<h3 id="do-i-need-a-graphql-client">Do I need a GraphQL client?</h3><p>No, you can start by issuing GraphQL requests with a regular HTTP client. GraphQL clients such as Apollo and Relay add batching, caching, build-time schema validation and UI updates, and become valuable as your application grows, but they are optional.</p>
<h3 id="how-do-i-secure-a-graphql-api">How do I secure a GraphQL API?</h3><p>Apply the same protections as any API — authentication, authorization, input validation — and add GraphQL-specific guards such as query depth limits, query cost limits, timeouts, and controlling introspection. Resolve authorization in your business-logic layer, and authenticate with standard patterns such as JWTs or OAuth.</p>
<h3 id="is-graphql-only-for-javascript-or-react">Is GraphQL only for JavaScript or React?</h3><p>No. GraphQL is a specification that can be implemented in any language, and many runtimes exist across the ecosystem. It was introduced at a React conference and the reference implementation is in JavaScript, which is why it is associated with that stack, but it is transport- and language-agnostic.</p>
</section>
<section><h2 id="verdict"><a href="#verdict" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Verdict</h2><p>GraphQL is a powerful, open query language that earns its place when clients have diverse and evolving data requirements and you want a single, strongly typed, self-documenting API contract. It solves real problems — over-fetching, under-fetching and multiple round trips — that grow more painful as front-ends and clients multiply. The trade-off is added complexity in resolvers and caching, so match the tool to the surface: reach for GraphQL when flexibility pays, and reach for REST when a simple, stable, cache-friendly API is all you need.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>How to set up CI/CD with GitHub Actions</title>
      <link>https://debod.id/en/posts/github-actions-ci-cd/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/github-actions-ci-cd/</guid>
      <pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate>
      <category>programming</category>
      <category>github</category>
      <category>devops</category>
      <category>automation</category>
      <category>yaml</category>
      <description>Set up a CI/CD pipeline with GitHub Actions: create your first workflow, configure triggers, build and test your code on runners, use actions and secrets, and deploy automatically — open-license sources.</description>
      <content:encoded><![CDATA[<section><p>CI/CD (continuous integration and continuous delivery) automates building, testing, and deploying your code so every change is verified before it ships. GitHub Actions is a CI/CD platform built into GitHub: you define workflows in YAML files, and GitHub runs them in the cloud whenever you push code, open a pull request, or hit a schedule. This guide walks through creating your first workflow, wiring triggers, building and testing a Node.js project on hosted runners, using actions and secrets, and deploying the result automatically — with real workflow YAML you can copy and adapt.</p></section>
<section><h2 id="what-ci-cd-and-github-actions-mean"><a href="#what-ci-cd-and-github-actions-mean" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What CI/CD and GitHub Actions mean</h2><p>CI/CD is the combined practice of continuous integration (CI) and continuous delivery (CD). <strong>Continuous integration</strong> means merging small changes into your main branch frequently and verifying each change with automated builds and tests. <strong>Continuous delivery</strong> means releasing software in short cycles so reliable software can be deployed at any time with a simple, repeatable process. When CI and CD run together they form a pipeline.</p>
<p>GitHub Actions provides the platform to build that pipeline. Its four core components are:</p>
<ul>
<li><strong>Workflow</strong> — a configurable automated process defined in a YAML file in the <code>.github/workflows</code> directory.</li>
<li><strong>Event</strong> — what triggers the workflow: a push, a pull request, a schedule, or a manual dispatch.</li>
<li><strong>Job</strong> — a set of steps executed on the same runner.</li>
<li><strong>Runner</strong> — a machine that runs a job; GitHub provides Linux, Windows, and macOS virtual machines (GitHub-hosted runners), or you can host your own.</li>
</ul>
<p>A repository can have multiple workflows, each doing a different job: build and test every pull request, deploy on release, or add a label when an issue opens.</p>
</section>
<section><h2 id="prerequisites"><a href="#prerequisites" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Prerequisites</h2><ul>
<li>A GitHub repository (public or private) with your code committed.</li>
<li>A project with defined build/test commands (<code>npm test</code> for Node.js, <code>python -m pytest</code> for Python, etc.).</li>
<li>Basic familiarity with YAML.</li>
</ul>
<p>GitHub Actions is <strong>free for public repositories</strong>, and free minutes are included for private repositories on the free plan. You do not need a separate CI server.</p>
</section>
<section><h2 id="step-1-create-your-first-workflow-file"><a href="#step-1-create-your-first-workflow-file" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Step 1 — Create your first workflow file</h2><p>Workflows live in a <code>.github/workflows</code> directory at the root of your repository. Create the file <code>.github/workflows/ci.yml</code> with the minimum valid workflow:</p>
<pre><code class="language-yaml">name: CI

on: push

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - run: echo &quot;Hello from GitHub Actions&quot;
</code></pre>
<p>Commit and push this file. Every push to the repository now triggers a run: GitHub looks in <code>.github/workflows</code>, finds a workflow whose <code>on</code> value matches the event, and starts the job. You can watch the run under the <strong>Actions</strong> tab of your repository.</p>
<blockquote>
<p><strong>Why <code>actions/checkout</code>?</strong> Each job starts on a fresh virtual machine with an empty working directory. The <code>actions/checkout</code> action clones your repository into the runner so later steps can access your code.</p>
</blockquote>
</section>
<section><h2 id="step-2-configure-triggers-with-on"><a href="#step-2-configure-triggers-with-on" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Step 2 — Configure triggers with <code>on</code></h2><p>The <code>on</code> key defines which events start the workflow. Common triggers:</p>
<pre><code class="language-yaml">on: push            # any push to any branch
</code></pre>
<pre><code class="language-yaml">on:
  push:
    branches: [ main ]      # only the main branch
    paths: [ &quot;src/**&quot; ]     # only when files under src change
</code></pre>
<pre><code class="language-yaml">on:
  pull_request:             # run on pull requests
    branches: [ main ]

on:                         # run on a schedule, cron syntax
  schedule:
    - cron: &quot;0 8 * * *&quot;
</code></pre>
<pre><code class="language-yaml">on: workflow_dispatch       # manual trigger from the Actions tab
</code></pre>
<p>You can combine events. For example, <code>on: [push, pull_request]</code>. Branch and path filters use glob patterns; quoted <code>&#39;*&#39;</code> and <code>&#39;**&#39;</code> match any number of characters.</p>
</section>
<section><h2 id="step-3-build-and-test-your-code"><a href="#step-3-build-and-test-your-code" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Step 3 — Build and test your code</h2><p>The most common CI goal is to verify your code builds and passes tests on every change. Here is a practical workflow for a Node.js project:</p>
<pre><code class="language-yaml">name: CI

on:
  push:
    branches: [ main ]
  pull_request:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-node@v4
        with:
          node-version: &quot;20&quot;
          cache: &quot;npm&quot;
      - run: npm ci
      - run: npm run build --if-present
      - run: npm test
</code></pre>
<p>Key points:</p>
<ul>
<li><code>actions/setup-node</code> installs a specific Node.js version and adds it to <code>PATH</code>. It also caches dependencies (the <code>cache: &quot;npm&quot;</code> input) to make future runs faster.</li>
<li><code>npm ci</code> installs dependencies from the lock file — it is generally faster and more reliable than <code>npm install</code>.</li>
<li>The <code>--if-present</code> flag makes the build step optional, so the same workflow works for projects without a build script.</li>
<li>The matrix keyword lets you test multiple versions or operating systems in parallel without duplicating the job:</li>
</ul>
<pre><code class="language-yaml">jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [&quot;18&quot;, &quot;20&quot;]
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
      - run: npm ci
      - run: npm test
</code></pre>
<p>Each value in the <code>node-version</code> array creates a separate job running the same steps, using the <code>matrix</code> context to pick its version.</p>
</section>
<section><h2 id="step-4-store-secrets-and-use-them-in-workflows"><a href="#step-4-store-secrets-and-use-them-in-workflows" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Step 4 — Store secrets and use them in workflows</h2><p>Never hard-code credentials in a workflow file. Instead, store them as <strong>secrets</strong> on the repository (<code>Settings → Secrets and variables → Actions</code>) and reference them with the <code>secrets</code> context:</p>
<pre><code class="language-yaml">jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - run: npm test
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
</code></pre>
<p>GitHub redacts secrets in logs. Use the <strong>principle of least privilege</strong>: give each secret the minimum scope it needs, and rotate them if a workflow or repository is compromised.</p>
</section>
<section><h2 id="step-5-deploy-automatically-continuous-delivery"><a href="#step-5-deploy-automatically-continuous-delivery" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Step 5 — Deploy automatically (continuous delivery)</h2><p>The final step of a pipeline ships your verified code. A common pattern is to run tests first, then deploy only when tests pass. One workflow can do both by making the deploy job depend on the test job with <code>needs</code>:</p>
<pre><code class="language-yaml">name: CI/CD

on:
  push:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-node@v4
        with:
          node-version: &quot;20&quot;
          cache: &quot;npm&quot;
      - run: npm ci
      - run: npm test

  deploy:
    needs: test          # only runs if &#39;test&#39; succeeds
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - name: Deploy
        run: ./deploy.sh
        env:
          DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
</code></pre>
<p>The <code>needs: test</code> line makes the <code>deploy</code> job wait for the <code>test</code> job and run only if it succeeds — so broken code never deploys. In place of <code>./deploy.sh</code> you would call the deploy action for your platform (for example, a Cloudflare Pages or static hosting deployment action) or run the CLI with your secret token.</p>
</section>
<section><h2 id="step-6-check-debug-and-extend-your-workflow"><a href="#step-6-check-debug-and-extend-your-workflow" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Step 6 — Check, debug, and extend your workflow</h2><ul>
<li>Open the <strong>Actions</strong> tab to see run status, logs, and failures.</li>
<li>Each step&#39;s output is shown in the run log; failed steps are highlighted.</li>
<li>Use <code>continue-on-error: true</code> on a step when a known failure should not block the job.</li>
<li>Limit how long a job may run with <code>timeout-minutes</code> so a hanging job does not consume minutes forever.</li>
<li>Store build/test artifacts (test reports, screenshots, logs) with the <code>actions/upload-artifact</code> action so you can inspect them after a run.</li>
</ul>
</section>
<section><h2 id="common-mistakes-to-avoid"><a href="#common-mistakes-to-avoid" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Common mistakes to avoid</h2><ul>
<li><strong>Missing <code>.github/workflows</code> path</strong> — workflows must live in that exact directory at the repository root.</li>
<li><strong>Hard-coded credentials</strong> — always use secrets, never commit tokens or keys in YAML.</li>
<li><strong>Forgetting <code>actions/checkout</code></strong> — later steps cannot see your code without it.</li>
<li><strong>Unsafe shell interpolation</strong> — treat any value coming from a PR title or issue body as untrusted input when passing it to <code>run:</code>.</li>
<li><strong>Wrong action versions</strong> — pin the major version (e.g. <code>actions/checkout@v6</code>) and review new majors before upgrading.</li>
<li><strong>Ignoring the event context</strong> — a <code>pull_request</code> event runs in the context of the PR, so make sure your test commands are valid there too.</li>
</ul>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="is-github-actions-free">Is GitHub Actions free?</h3><p>Yes for public repositories, where workflows run at no cost. Private repositories include a monthly allowance of free action minutes on GitHub&#39;s hosted runners (the exact amount depends on your plan), after which you pay for additional minutes. Self-hosted runners use your own machines and have no per-minute fee.</p>
<h3 id="what-is-the-difference-between-ci-and-cd">What is the difference between CI and CD?</h3><p>Continuous integration (CI) automatically builds and tests every change you merge, catching integration problems early. Continuous delivery (CD) automates deploying the verified build so reliable software can be released at any time with a repeatable process. CI/CD refers to the two working together in one pipeline.</p>
<h3 id="how-do-i-run-a-workflow-only-on-the-main-branch">How do I run a workflow only on the main branch?</h3><p>Use the <code>on.push.branches</code> filter:</p>
<pre><code class="language-yaml">on:
  push:
    branches: [ main ]
</code></pre>
<p>The workflow then runs only when the push targets <code>main</code>. You can also add <code>paths</code> filters or use <code>pull_request</code> events with branch filters.</p>
<h3 id="what-is-a-github-actions-runner">What is a GitHub Actions runner?</h3><p>A runner is a machine that executes the steps of a job. GitHub provides GitHub-hosted runners (fresh Linux, Windows, or macOS virtual machines) with a set of preinstalled tools, or you can set up self-hosted runners on your own infrastructure.</p>
<h3 id="how-do-i-use-secrets-without-exposing-them-in-logs">How do I use secrets without exposing them in logs?</h3><p>Reference secrets through the <code>secrets</code> context (<code>${{ secrets.NAME }}</code>) and set them as environment variables or action inputs rather than printing them. GitHub automatically redacts secret values in logs. Keep secrets scoped to the minimum permission needed and rotate them regularly.</p>
</section>
<section><h2 id="summary"><a href="#summary" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Summary</h2><p>GitHub Actions turns a GitHub repository into a CI/CD platform. You define workflows as YAML files in <code>.github/workflows</code>, trigger them with events like <code>push</code> and <code>pull_request</code>, build and test code on hosted runners, secure credentials with secrets, and deploy automatically once tests pass. Start with one small workflow that builds and tests your project, then extend it with a deploy job — a working pipeline for free on public repositories.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>Git branching strategies: GitFlow, GitHub Flow, and trunk-based development</title>
      <link>https://debod.id/en/posts/git-branching-workflows/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/git-branching-workflows/</guid>
      <pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate>
      <category>programming</category>
      <category>git</category>
      <category>devops</category>
      <description>Compare the most common Git branching strategies — GitFlow, GitHub Flow, and trunk-based development — with concrete commands, branch structures, and a guide to choosing the right workflow for your team&apos;s release cadence and size.</description>
      <content:encoded><![CDATA[<section><p>A Git branching strategy (also called a branching model or workflow) defines how a team names branches, how work moves between them, and how releases are produced. The strategy you choose directly affects how often you integrate, how much merge conflict you deal with, and how fast you can ship. This guide explains the three most widely used models — GitFlow, GitHub Flow, and trunk-based development — with concrete branch structures and commands, and helps you decide which fits your release cadence and team size.</p></section>
<section><h2 id="why-a-branching-strategy-matters"><a href="#why-a-branching-strategy-matters" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Why a branching strategy matters</h2><p>A branching strategy gives a team a shared, predictable way to work with Git. Instead of each developer inventing their own branch names and merge rules, a workflow standardizes three things: where new work starts, where finished work goes, and how a release is produced. The result is fewer accidental conflicts, clearer code review, and a main branch whose state you can trust.</p>
<p>Branches themselves are a mechanism of version control: a branch is an independent line of development that forks off the mainline. Git makes branching cheap because its commits are content-addressed snapshots, so creating and merging branches is fast even in large repositories. Because branches are cheap, teams can afford to isolate work — the strategic question is <em>how much</em> isolation you want and for <em>how long</em>.</p>
<p>There are two broad philosophies. <strong>Feature-branch workflows</strong> isolate each piece of work on its own branch and merge it back when complete. <strong>Trunk-based development</strong> keeps a single mainline and integrates changes almost continuously. Most teams use something between these two extremes.</p>
</section>
<section><h2 id="trunk-based-development"><a href="#trunk-based-development" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Trunk-based development</h2><p>Trunk-based development (TBD) is a version control management practice where developers integrate small, frequent changes into a single main branch — the trunk — rather than working on long-lived feature branches. It is closely tied to continuous integration and continuous delivery, because the trunk is expected to remain in a deployable state at all times.</p>
<h3 id="how-it-works">How it works</h3><p>In a pure trunk-based workflow, developers commit directly to the main branch, or use very short-lived branches that are merged within hours. The key constraints are:</p>
<ul>
<li>One long-lived branch (main, sometimes called <code>master</code> or <code>trunk</code>).</li>
<li>Changes are small and integrated frequently — often several times per day.</li>
<li>Strong automated testing and builds guard the trunk, so integration is safe.</li>
</ul>
<pre><code class="language-bash"># Get the latest main and start work
git checkout main
git pull origin main

# Small change, committed directly to main
git add .
git commit -m &quot;fix: handle empty response in API client&quot;

# Or use a very short-lived branch that is merged the same day
git checkout -b fix/empty-response
git add .
git commit -m &quot;fix: handle empty response in API client&quot;
git checkout main
git merge fix/empty-response
</code></pre>
<h3 id="requirements-and-trade-offs">Requirements and trade-offs</h3><p>Trunk-based development works well for teams with fast release cycles, strong CI/CD, and a culture of small frequent commits. Because everything lands on one branch, it minimizes merge conflicts and keeps every developer working with the latest code. The trade-offs are real: it requires disciplined automated testing, and long-running or partially finished features need other mechanisms to stay safe — most commonly <strong>feature flags</strong>, which let you merge incomplete work behind a runtime switch that is off by default.</p>
<p>Trunk-based development is often associated with high-performing DevOps teams and is considered a foundation of continuous integration: if work sits on isolated branches for weeks before integration, you cannot call the process continuous integration, no matter how automated your builds are.</p>
</section>
<section><h2 id="github-flow"><a href="#github-flow" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>GitHub Flow</h2><p>GitHub Flow is a simplified branching model built around a single <code>main</code> branch and short-lived feature branches, with pull requests (PRs) as the review and merge gate. It was designed for teams that deploy continuously and want a workflow that is easy to learn.</p>
<h3 id="branch-structure">Branch structure</h3><p>GitHub Flow uses just two kinds of branches:</p>
<ul>
<li><strong>main</strong> — always deployable; everything is merged here and deployed from here.</li>
<li><strong>feature branches</strong> — created from <code>main</code>, live for hours or days, and deleted after merge.</li>
</ul>
<p>There is no separate <code>develop</code> branch and no release branches; every feature branch is treated the same way.</p>
<h3 id="the-workflow">The workflow</h3><pre><code class="language-bash"># 1. Create a feature branch from the latest main
git checkout main
git pull origin main
git checkout -b feature/add-user-dashboard

# 2. Work and commit
git add .
git commit -m &quot;feat: add user dashboard widget&quot;

# 3. Push and open a pull request
git push -u origin feature/add-user-dashboard
# Open a PR on the host, request review, discuss changes

# 4. After approval, merge on the host, then clean up locally
git checkout main
git pull origin main
git branch -d feature/add-user-dashboard
</code></pre>
<p>The defining rules of GitHub Flow are that <code>main</code> is always in a deployable state, and that any merging into <code>main</code> happens through a reviewed pull request. Because there is no dedicated release branch, the pipeline that deploys from <code>main</code> must be trustworthy — the model works because every merged change is immediately deployable.</p>
<h3 id="when-github-flow-fits">When GitHub Flow fits</h3><p>GitHub Flow is a good fit for small to medium teams deploying web applications and SaaS continuously, and for open-source projects where random contributors open PRs. It is simple, keeps every merge reviewed, and ships fast. It is less well suited to projects that must maintain multiple simultaneously supported versions, because there is no built-in mechanism for release branches.</p>
</section>
<section><h2 id="gitflow"><a href="#gitflow" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>GitFlow</h2><p>GitFlow is a branching model introduced by Vincent Driessen in 2010 that adds a permanent <code>develop</code> branch and a set of supporting branch types to handle releases and hotfixes explicitly. It is stricter than GitHub Flow and is designed for projects with scheduled releases and a formal release process.</p>
<h3 id="branch-structure-1">Branch structure</h3><p>GitFlow defines two long-lived branches plus three types of short-lived supporting branches:</p>
<ul>
<li><strong>main</strong> — always reflects the state of production, tagged for each release.</li>
<li><strong>develop</strong> — the integration branch where features are merged; the most recent &quot;in-development&quot; code.</li>
<li><em><em>feature/</em> <small>(from develop)</small></em>* — new functionality.</li>
<li><em><em>release/</em> <small>(from develop, merged to main and develop)</small></em>* — preparing a release: only bug fixes, version bumps, and documentation.</li>
<li><em><em>hotfix/</em> <small>(from main, merged to main and develop)</small></em>* — urgent fixes to production.</li>
</ul>
<h3 id="the-workflow-1">The workflow</h3><pre><code class="language-bash"># Start a feature from develop
git checkout develop
git checkout -b feature/user-auth

# ... work on the feature, commit ...

# Merge the finished feature back into develop
git checkout develop
git merge --no-ff feature/user-auth
git branch -d feature/user-auth

# Prepare a release: branch off develop
git checkout develop
git checkout -b release/2.0.0
# bump version, fix last-minute bugs
git checkout main
git merge --no-ff release/2.0.0
git tag -a v2.0.0 -m &quot;Release 2.0.0&quot;
git checkout develop
git merge --no-ff release/2.0.0
git branch -d release/2.0.0

# Hotfix straight to production
git checkout main
git checkout -b hotfix/emergency-fix
# fix the bug, commit
git checkout main
git merge --no-ff hotfix/emergency-fix
git tag -a v2.0.1 -m &quot;Hotfix 2.0.1&quot;
git checkout develop
git merge --no-ff hotfix/emergency-fix
</code></pre>
<h3 id="strengths-and-weaknesses">Strengths and weaknesses</h3><p>GitFlow&#39;s strength is structure: it separates stable production code from integration work, supports scheduled releases cleanly, and gives hotfixes a clear, low-risk path to production. It is a reasonable choice for larger teams and software that ships on a defined cadence and must maintain multiple versions.</p>
<p>The cost is complexity. For teams deploying continuously, GitFlow adds overhead — two permanent branches to keep in sync, release-branch bookkeeping, and merges in two directions for every release and hotfix. Many commentators, and Driessen himself in later reflections, have noted GitFlow is best suited to teams that do not deploy continuously and that the simpler continuous-delivery workflows fit most modern products better.</p>
</section>
<section><h2 id="comparing-the-three-strategies"><a href="#comparing-the-three-strategies" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Comparing the three strategies</h2><table>
<thead>
<tr>
<th>Factor</th>
<th>GitFlow</th>
<th>GitHub Flow</th>
<th>Trunk-based</th>
</tr>
</thead>
<tbody><tr>
<td>Long-lived branches</td>
<td><code>main</code> + <code>develop</code></td>
<td><code>main</code> only</td>
<td><code>main</code> only</td>
</tr>
<tr>
<td>Release model</td>
<td>Scheduled, formal</td>
<td>Continuous</td>
<td>Continuous</td>
</tr>
<tr>
<td>Feature branch lifetime</td>
<td>Days to weeks</td>
<td>Hours to days</td>
<td>Hours (or none)</td>
</tr>
<tr>
<td>CI/CD requirement</td>
<td>Optional</td>
<td>Important</td>
<td>Critical</td>
</tr>
<tr>
<td>Feature flags</td>
<td>Optional</td>
<td>Helpful</td>
<td>Often required</td>
</tr>
<tr>
<td>Merge conflicts</td>
<td>More common</td>
<td>Occasional</td>
<td>Rarely</td>
</tr>
<tr>
<td>Best for</td>
<td>Larger teams, versioned releases</td>
<td>Small-medium teams, SaaS, open source</td>
<td>High-velocity DevOps teams</td>
</tr>
</tbody></table>
</section>
<section><h2 id="how-to-choose-the-right-strategy"><a href="#how-to-choose-the-right-strategy" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How to choose the right strategy</h2><p>There is no single best workflow — the choice depends on your release cadence, team size, and deployment process.</p>
<ul>
<li>Choose <strong>GitFlow</strong> if you ship on a defined schedule, need a formal release and QA phase, or must maintain multiple supported versions of the same product.</li>
<li>Choose <strong>GitHub Flow</strong> if you are a small or medium team deploying continuously from <code>main</code> and want a simple, PR-driven workflow with little ceremony.</li>
<li>Choose <strong>trunk-based development</strong> if you ship very frequently, have strong automated testing and CI/CD, and want to minimize merge conflicts — and you can pair it with feature flags to keep incomplete work out of the production path.</li>
</ul>
<p>Whichever you pick, keep the workflow consistent, keep branches short-lived, and make the deploy pipeline trustworthy. The best strategy is the one your team can follow every day without friction.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-is-the-difference-between-a-branch-and-a-fork">What is the difference between a branch and a fork?</h3><p>A branch lives inside a repository and shares its history with the mainline. A fork is a copy of a whole repository on a hosting service, typically used when a contributor does not have write access to the original — changes travel back through pull requests. Branches are for isolating work within a project; forks are for collaborating across repositories.</p>
<h3 id="should-i-use-gitflow-or-github-flow">Should I use GitFlow or GitHub Flow?</h3><p>Use GitHub Flow if you deploy continuously, ship often, and want simplicity. Use GitFlow if you release on a schedule, need a formal release and QA phase, or must support multiple releases in parallel. For most modern web products, GitHub Flow or trunk-based development is the simpler, more appropriate choice.</p>
<h3 id="what-is-a-feature-flag-and-why-does-trunk-based-development-need-it">What is a feature flag and why does trunk-based development need it?</h3><p>A feature flag (or feature toggle) is a runtime switch that controls whether a piece of code is active. It lets you merge incomplete or risky work into the main branch behind a flag that is off by default, so the trunk stays deployable even while a feature is being built. Trunk-based development relies on flags because it has no long-lived feature branches to hide unfinished work.</p>
<h3 id="how-long-should-a-feature-branch-live">How long should a feature branch live?</h3><p>As a rule of thumb, a feature branch should live hours, not days, and rarely more than a couple of days. The longer a branch lives, the more it diverges from the mainline and the more merge conflicts and integration delays you accumulate. Trunk-based development keeps branches under a day; GitHub Flow keeps them short enough that a PR review can happen promptly.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>Flexbox vs Grid: which CSS layout should you choose?</title>
      <link>https://debod.id/en/posts/flexbox-vs-grid/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/flexbox-vs-grid/</guid>
      <pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate>
      <category>frontend</category>
      <category>web</category>
      <category>frontend</category>
      <category>html</category>
      <description>Flexbox and CSS Grid are two layout systems built into the browser. Flexbox is one-dimensional (a row or a column); Grid is two-dimensional (rows and columns at once). Learn how each one works, when to reach for it, and how to combine them.</description>
      <content:encoded><![CDATA[<section><p>Flexbox and CSS Grid are both built into the browser, both designed to replace the float-and-clear hacks that web developers lived with for a decade, and both are excellent at what they do. They are not interchangeable: each one solves a different problem. Flexbox lays things out in a single line — a row or a column. Grid lays things out in two dimensions at once — rows and columns together. Picking the right one starts with that distinction, and the rest of this guide shows you how to apply it.</p>
<p>The <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_grid_layout/Relationship_of_grid_layout_with_other_layout_methods">MDN relationship guide</a> puts the rule bluntly: &quot;The basic difference between CSS grid layout and CSS flexbox layout is that flexbox was designed for layout in one dimension - either a row or a column. Grid was designed for two-dimensional layout - rows, and columns at the same time.&quot; If you can already do the job in one axis, flexbox is the lighter tool. If your layout needs both axes to stay aligned, grid is the only one of the two that can do it cleanly.</p></section>
<section><h2 id="what-flexbox-is"><a href="#what-flexbox-is" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What Flexbox is</h2><p>Flexbox is the layout system you use for <strong>a single line of items in a row or a column</strong>. You turn any element into a flex container by setting <code>display: flex</code> (or <code>display: inline-flex</code>), and its direct children become flex items that the browser arranges along a <em>main axis</em> you control with the <code>flex-direction</code> property.</p>
<p>The four <code>flex-direction</code> values give you the four possible flows:</p>
<ul>
<li><code>row</code> — items line up horizontally, left to right in a left-to-right language.</li>
<li><code>row-reverse</code> — same as <code>row</code> but the start and end lines are swapped.</li>
<li><code>column</code> — items stack vertically, top to bottom.</li>
<li><code>column-reverse</code> — same as <code>column</code> but the start and end lines are swapped.</li>
</ul>
<p>The key insight from the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_flexible_box_layout/Basic_concepts_of_flexbox">MDN flexbox guide</a> is that flexbox has <em>two axes</em>: the main axis (set by <code>flex-direction</code>) and a cross axis that runs perpendicular to it. Every flex property you write either moves things along one of these axes, or grows and shrinks items along them. The two most useful flex-item properties are the <code>flex</code> shorthand and the alignment properties.</p>
<p>A typical flexbox row with three items that share the available space is two lines of CSS:</p>
<pre><code class="language-css">.nav {
  display: flex;
}
.nav &gt; a {
  flex: 1; /* flex: 1 1 0 — grow, shrink, zero base */
}
</code></pre>
<p>The <code>flex</code> shorthand expands to <code>flex-grow</code>, <code>flex-shrink</code>, and <code>flex-basis</code> in that order. Writing <code>flex: 1</code> is equivalent to <code>flex: 1 1 0</code>, which means the items start at zero size and grow equally to fill the available space. Writing <code>flex: 2 1 0</code> would give that item twice the share of the space.</p>
<p>Flexbox is also the right tool for centering an item inside its parent, which is famously awkward with earlier CSS. The <a href="https://web.dev/learn/css/flexbox">web.dev flexbox guide</a> walks through this exact pattern: <code>justify-content</code> aligns along the main axis, <code>align-items</code> aligns along the cross axis, and combining <code>center</code> on both gives you a centered item in any flex container.</p>
<p>The reason flexbox stays useful even when grid exists is that <strong>most layout work on a page is one-dimensional</strong>. A navigation bar, a toolbar, a row of action buttons, a list of cards that wraps to the next line, a footer with three columns of links — all of these are single-line layouts, and flexbox is the simplest, most predictable tool for each one.</p>
</section>
<section><h2 id="what-grid-is"><a href="#what-grid-is" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What Grid is</h2><p>CSS Grid is the layout system for <strong>two-dimensional layouts</strong>: when items need to line up in rows <em>and</em> columns at the same time. You turn any element into a grid container with <code>display: grid</code> (or <code>display: inline-grid</code>), and its direct children become grid items that the browser places into a <em>grid</em> you define.</p>
<p>The grid is defined with two properties: <code>grid-template-rows</code> and <code>grid-template-columns</code>. Each one lists the sizes of the rows and columns. The <code>fr</code> unit — short for &quot;fraction&quot; — is a grid-only length that represents a share of the available space in the container:</p>
<pre><code class="language-css">.page {
  display: grid;
  grid-template-columns: 1fr 2fr 1fr; /* three columns: 25% / 50% / 25% */
  gap: 16px;
}
</code></pre>
<p>This produces a page with a left sidebar, a main column twice as wide, and a right sidebar, with a 16-pixel gap between every track. The two outer columns are equal because they both have <code>1fr</code>; the middle column is twice as wide because it has <code>2fr</code>. If you resize the browser, all three columns resize proportionally without any media query.</p>
<p>The <code>repeat()</code> function and <code>minmax()</code> function take this further. A common pattern is a fluid card grid that always keeps the cards above a minimum readable width:</p>
<pre><code class="language-css">.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 16px;
}
</code></pre>
<p>The <code>auto-fit</code> keyword asks the browser to fit as many 250-pixel-minimum column tracks as it can into the container, and <code>minmax(250px, 1fr)</code> says each track can grow beyond 250 pixels to share leftover space. On a wide screen you might see four cards per row; on a tablet, three; on a phone, one. All from those two functions, with no media query.</p>
<p>The other defining feature of grid is that you can place items precisely. Grid lines are numbered from the start edge, and you can tell an item which lines to start and end at:</p>
<pre><code class="language-css">.header  { grid-column: 1 / 3; grid-row: 1; } /* spans columns 1 to 2, row 1 */
.sidebar { grid-column: 1;     grid-row: 2; }
.main    { grid-column: 2;     grid-row: 2; }
</code></pre>
<p>The <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_grid_layout/Basic_Concepts_of_Grid_Layout">MDN grid basics guide</a> shows the full longhand (<code>grid-column-start</code>, <code>grid-column-end</code>, <code>grid-row-start</code>, <code>grid-row-end</code>) as well as the shorthand. Either form works; the shorthand is what you will see in real code.</p>
<p>Grid is the right tool for whole-page layouts, dashboard layouts, image galleries, form layouts where labels and inputs need to line up across rows, and any other case where the relationship between items in <em>both</em> dimensions matters.</p>
</section>
<section><h2 id="the-key-differences"><a href="#the-key-differences" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The key differences</h2><table>
<thead>
<tr>
<th>Aspect</th>
<th>Flexbox</th>
<th>Grid</th>
</tr>
</thead>
<tbody><tr>
<td>Dimensions</td>
<td>One (row or column)</td>
<td>Two (rows and columns at once)</td>
</tr>
<tr>
<td>Mental model</td>
<td>Content-out — items decide how much space they take</td>
<td>Layout-in — you create the layout, then items fill it</td>
</tr>
<tr>
<td>Main axis</td>
<td>Set by <code>flex-direction</code> (row, column, two reverse variants)</td>
<td>Set implicitly by your row + column definition</td>
</tr>
<tr>
<td>Distribution of free space</td>
<td><code>flex-grow</code> / <code>flex-shrink</code> / <code>flex-basis</code> per item</td>
<td><code>fr</code> unit on tracks, <code>auto-fit</code> / <code>auto-fill</code> on track lists</td>
</tr>
<tr>
<td>Alignment</td>
<td><code>justify-content</code> (main), <code>align-items</code> + <code>align-self</code> (cross)</td>
<td>Same box-alignment properties, applied to grid areas</td>
</tr>
<tr>
<td>Overlapping items</td>
<td>Awkward; usually not the goal</td>
<td>Native — items can share cells with <code>z-index</code> ordering</td>
</tr>
<tr>
<td>Wrapping</td>
<td><code>flex-wrap: wrap</code> creates independent flex lines</td>
<td>The whole grid is one strict 2D structure (wrap is the default)</td>
</tr>
<tr>
<td>Best for</td>
<td>Navigation bars, toolbars, single rows, evenly-spaced items</td>
<td>Page layouts, card grids, dashboards, anything needing both axes</td>
</tr>
<tr>
<td>Source of truth</td>
<td>The w3.org <a href="https://www.w3.org/TR/css-flexbox-1/">CSS Flexbox Box Layout Module Level 1</a> spec</td>
<td>The w3.org <a href="https://www.w3.org/TR/css-grid-1/">CSS Grid Layout Module Level 1</a> spec</td>
</tr>
</tbody></table>
</section>
<section><h2 id="when-flexbox-is-the-better-choice"><a href="#when-flexbox-is-the-better-choice" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>When Flexbox is the better choice</h2><p>Reach for flexbox when the layout is genuinely one-dimensional, or when the items themselves should drive the sizing:</p>
<ul>
<li><strong>Navigation bars and toolbars</strong> — a row of links or buttons that should share the available width, or right-align a single action button. Flexbox is the simplest, most readable way to express this.</li>
<li><strong>Evenly spaced groups of items</strong> — tags, chips, action menus, where you want them distributed with <code>justify-content: space-between</code> or <code>space-around</code>. The MDN <code>justify-content</code> reference lists every value (<code>flex-start</code>, <code>flex-end</code>, <code>center</code>, <code>space-between</code>, <code>space-around</code>, <code>space-evenly</code>, and a few writing-mode-aware ones).</li>
<li><strong>Centering a single element in its parent</strong> — combining <code>justify-content: center</code> and <code>align-items: center</code> on a flex container is the cleanest one-liner.</li>
<li><strong>Lists that wrap to a new line gracefully</strong> — setting <code>flex-wrap: wrap</code> and giving each item a <code>flex-basis</code> produces a row that reflows on narrow screens without any media query.</li>
<li><strong>Component internals</strong> — the inside of a card, the inside of a button, the inside of a form field. Flexbox rarely surprises you when the scope is one component.</li>
</ul>
<p>If you are writing flexbox and find yourself setting <code>width</code> on a flex item to make it line up with the items above it, you are probably fighting the one-dimensional model. That is a strong signal to switch to grid.</p>
</section>
<section><h2 id="when-grid-is-the-better-choice"><a href="#when-grid-is-the-better-choice" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>When Grid is the better choice</h2><p>Reach for grid when the layout needs both axes, or when you are designing a layout in advance and want items to fall into it:</p>
<ul>
<li><strong>Whole-page layouts</strong> — header, sidebar, main, footer. The MDN relationship guide shows this exact pattern using <code>grid-template-areas</code> with named regions. It is the most readable CSS you can write for a page-level layout.</li>
<li><strong>Card grids and image galleries</strong> — the <code>repeat(auto-fit, minmax(250px, 1fr))</code> pattern from earlier handles a fluid grid with no media queries and no JavaScript.</li>
<li><strong>Dashboards and admin panels</strong> — multiple panels that need to align both horizontally and vertically. Grid&#39;s <code>align-items</code> and <code>justify-items</code> keep every cell of the dashboard aligned without you having to track heights.</li>
<li><strong>Forms with aligned labels and inputs</strong> — grid is the only way to make a column of labels line up with the inputs across every row, because that is a two-axis problem.</li>
<li><strong>Overlapping content</strong> — cards with badges that overlay the top-right corner, image captions that sit on top of a hero image. Grid&#39;s <code>grid-column</code> / <code>grid-row</code> placement and <code>z-index</code> make overlapping items clean.</li>
</ul>
<p>The MDN relationship guide also describes grid as &quot;layout-in&quot; — you create the layout first (the rows, the columns, the gaps), and then items slot into it. This is the opposite of flexbox&#39;s &quot;content-out&quot; model, where items ask for space and the container distributes it. Picking the right mental model is half the battle.</p>
</section>
<section><h2 id="they-work-well-together"><a href="#they-work-well-together" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>They work well together</h2><p>You do not have to pick one and use it everywhere. The MDN relationship guide is explicit that grid is designed to be part of a complete system that includes flexbox. A typical real page uses grid for the page-level skeleton and flexbox for the navigation bar, the toolbar, the inside of every card, and every other one-dimensional component inside the grid cells. Treat them as two complementary layout systems, not two competitors.</p>
<p>A common pattern is a grid for the page skeleton, and a flexbox for the page header:</p>
<pre><code class="language-css">.page {
  display: grid;
  grid-template-rows: auto 1fr auto; /* header / main / footer */
  min-height: 100vh;
}
.page &gt; header {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
</code></pre>
<p>The outer grid makes the footer stick to the bottom of the viewport on short pages. The inner flexbox lays out the logo on the left and the navigation links on the right. Neither layout system was used for something the other does better.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="can-i-use-flexbox-and-grid-on-the-same-page">Can I use Flexbox and Grid on the same page?</h3><p>Yes, and you usually should. Grid is the right tool for the page-level skeleton (the rows and columns that define where the major regions sit) and flexbox is the right tool for one-dimensional components inside those regions (the navigation bar, the toolbar, the contents of a card, the row of action buttons). The MDN relationship guide describes them as complementary parts of the same layout system, not alternatives. Most real-world designs use both.</p>
<h3 id="which-is-better-for-responsive-design">Which is better for responsive design?</h3><p>Neither is &quot;better&quot; — they solve different problems responsively. A flex container with <code>flex-wrap: wrap</code> reflows items to new lines as the container narrows, which is great for navbars and tag lists. A grid container with <code>repeat(auto-fit, minmax(250px, 1fr))</code> keeps cards in a strict grid while still reflowing the number of columns. For page-level layouts that need to align in both dimensions, grid is the only realistic choice. For one-dimensional rows, flexbox usually needs fewer properties to express the same behavior.</p>
<h3 id="when-should-i-use-grid-over-flexbox">When should I use Grid over Flexbox?</h3><p>The MDN rule is the cleanest test: if you only need to control the layout by row or column, use flexbox; if you need to control it by row <em>and</em> column, use grid. Concrete signals to switch from flexbox to grid: you find yourself setting <code>width</code> on flex items to make them line up across rows, you need items to overlap, or you are building any kind of dashboard, card grid, or form layout.</p>
<h3 id="does-grid-replace-flexbox">Does Grid replace Flexbox?</h3><p>No. Grid was added to CSS later, and for a moment the narrative was that grid was the modern replacement for flexbox. That turned out to be wrong. Flexbox is still the right tool for one-dimensional layouts, and the two systems now share most of their alignment properties through the CSS Box Alignment module. The web.dev flexbox guide and the MDN relationship guide both describe them as partners, not competitors.</p>
</section>
<section><h2 id="verdict"><a href="#verdict" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Verdict</h2><p>Pick flexbox for one-dimensional layouts (a row, a column, a wrapping list) and pick grid for two-dimensional layouts (a page, a card grid, a dashboard). If you find yourself disabling the flexibility of flexbox to force items into a grid shape, you want grid. If you find yourself creating a one-row grid to lay out a navigation bar, you want flexbox. Use them together: grid for the page skeleton, flexbox for the components inside.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>What is the JavaScript event loop? How the call stack, task queue, and microtask queue work</title>
      <link>https://debod.id/en/posts/event-loop-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/event-loop-explained/</guid>
      <pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate>
      <category>programming</category>
      <category>programming</category>
      <category>javascript</category>
      <category>web</category>
      <category>async</category>
      <description>The JavaScript event loop is the runtime model that lets single-threaded JavaScript run asynchronous code. Learn how the call stack, the task queue, and the microtask queue cooperate, and why Promise callbacks fire before setTimeout.</description>
      <content:encoded><![CDATA[<section><p>JavaScript is a single-threaded language: one call stack, one thing running at a time. The event loop is the runtime mechanism that lets that single thread handle asynchronous work — timers, network requests, user input, promise callbacks — without ever blocking. Every JavaScript host (browsers, Node.js, Deno, workers) implements some version of it, and once you understand the loop, behaviour that looks &quot;weird&quot; — like <code>setTimeout(fn, 0)</code> running after a <code>Promise.then(fn)</code> — becomes predictable.</p>
<p>The short version: JavaScript runs synchronous code on the call stack; when an async operation finishes, its callback is queued; the event loop&#39;s only job is to keep pulling work from the queues whenever the stack is empty. Modern hosts split queued work into two queues — a <strong>task queue</strong> (macrotasks) and a <strong>microtask queue</strong> — and microtasks always drain before the next task.</p></section>
<section><h2 id="the-pieces"><a href="#the-pieces" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The pieces</h2><p>The runtime that runs your JavaScript is built from a few small, cooperating parts:</p>
<ul>
<li><strong>Call stack</strong> — a LIFO (last-in, first-out) stack of execution contexts. The function you are inside is on top; the function that called it is below. When a function returns, its frame pops.</li>
<li><strong>Heap</strong> — where objects and closures live. Mostly out of the picture for the loop itself.</li>
<li><strong>Web APIs / Node APIs</strong> — the host&#39;s async primitives: <code>setTimeout</code>, <code>fetch</code>, <code>addEventListener</code>, file I/O, network sockets. These run outside the JS engine; when they finish, they place a callback into a queue.</li>
<li><strong>Task queue</strong> (a.k.a. macrotask queue) — a FIFO queue of tasks. Tasks include the initial script, event callbacks, <code>setTimeout</code>/<code>setInterval</code> callbacks, I/O completion callbacks.</li>
<li><strong>Microtask queue</strong> — a FIFO queue of microtasks. Microtasks include <code>Promise.then</code>/<code>catch</code>/<code>finally</code> callbacks, <code>queueMicrotask()</code> callbacks, and <code>MutationObserver</code> callbacks.</li>
<li><strong>Event loop</strong> — a simple loop that, on each iteration, runs one task, then drains the microtask queue, then optionally renders, then repeats.</li>
</ul>
<p>The &quot;host&quot; here matters: the exact list of what counts as a task vs a microtask is defined by HTML in browsers and by the libuv model in Node.js. The two are not identical, but the shape is.</p>
</section>
<section><h2 id="how-one-iteration-works"><a href="#how-one-iteration-works" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How one iteration works</h2><p>In HTML terms, each iteration of the event loop does roughly this:</p>
<ol>
<li>Pick the oldest task from the task queue. Run it to completion — that is, the call stack is never re-entered until the current task&#39;s top-level synchronous code finishes.</li>
<li>After the task ends, drain the microtask queue: keep popping microtasks and running them until the queue is empty. New microtasks scheduled while draining also run in the same pass.</li>
<li>If the document is visible, perform rendering and paint.</li>
<li>Go back to step 1 and wait for the next task.</li>
</ol>
<p>Two guarantees follow from that shape:</p>
<ul>
<li><strong>Run-to-completion</strong> — a task cannot be interrupted by another task. Once it starts, it finishes synchronously.</li>
<li><strong>Microtasks drain after every task</strong> — a microtask queued at any point will run before the next task starts, even if it was scheduled during rendering work or during the task itself.</li>
</ul>
<p>That second guarantee is the one that explains most &quot;why did the promise run first?&quot; questions.</p>
</section>
<section><h2 id="a-worked-example"><a href="#a-worked-example" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>A worked example</h2><p>Consider this snippet, which is the canonical event-loop interview question:</p>
<pre><code class="language-js">console.log(&quot;1&quot;);

setTimeout(() =&gt; console.log(&quot;2&quot;), 0);

Promise.resolve().then(() =&gt; console.log(&quot;3&quot;));

console.log(&quot;4&quot;);
</code></pre>
<p>Walk through it line by line:</p>
<ol>
<li><code>console.log(&quot;1&quot;)</code> runs synchronously on the call stack. Output: <code>1</code>.</li>
<li><code>setTimeout(..., 0)</code> schedules a task. The host&#39;s timer service remembers the callback and the (zero) delay; when the timer expires, the host places the callback into the <strong>task queue</strong>. Nothing runs yet.</li>
<li><code>Promise.resolve().then(...)</code> schedules a microtask via the promise machinery. The callback is placed into the <strong>microtask queue</strong>.</li>
<li><code>console.log(&quot;4&quot;)</code> runs synchronously. Output: <code>4</code>.</li>
<li>The current task (the initial script) is now complete. The event loop drains the microtask queue: the promise callback runs. Output: <code>3</code>.</li>
<li>The event loop picks the next task — the <code>setTimeout</code> callback — and runs it. Output: <code>2</code>.</li>
</ol>
<p>Final output: <code>1 4 3 2</code>. The key idea: microtasks (promises) beat tasks (timers) by design.</p>
</section>
<section><h2 id="why-microtasks-first"><a href="#why-microtasks-first" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Why microtasks first?</h2><p>The HTML spec made microtasks higher priority than tasks for a practical reason: a promise resolution is supposed to feel synchronous to user code. If you write:</p>
<pre><code class="language-js">const result = await fetch(url);
use(result);
</code></pre>
<p>you want the <code>use(result)</code> callback to run as soon as <code>fetch</code> finishes — not to wait behind an unrelated <code>setTimeout</code> from another piece of code. Draining the microtask queue after every task makes that promise nearly always true, while still letting tasks carry the bulkier work (I/O, rendering, events).</p>
<p>The same rule is what makes <code>queueMicrotask(fn)</code> a useful primitive: it lets you schedule a callback to run after the current synchronous code, but before the next task — a guaranteed &quot;very soon&quot; without blocking.</p>
</section>
<section><h2 id="how-this-looks-in-node-js"><a href="#how-this-looks-in-node-js" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How this looks in Node.js</h2><p>Node.js implements the same model on top of libuv, with a few extra phases to handle I/O more fairly. The user-facing story is the same: microtasks (promise callbacks) run after the current task and before the next I/O or timer phase. Node also exposes <code>process.nextTick(fn)</code>, which is a separate, even more eager queue that runs after the current operation and before any microtasks — a detail worth knowing because mixing it with promise code can produce surprising ordering.</p>
<p>If you debug &quot;the server responded first but the promise logged after the timer&quot;, the loop is usually where the answer lives: the timer had a 0 ms minimum, the I/O callback was scheduled into a later phase, and the microtask drained in between.</p>
</section>
<section><h2 id="common-pitfalls"><a href="#common-pitfalls" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Common pitfalls</h2><ul>
<li><strong>Long synchronous work blocks the loop.</strong> A <code>while</code> loop that takes 500 ms freezes input, timers, and rendering. The fix is the usual: chunk the work, or move it off the main thread with a Web Worker.</li>
<li><strong>Stack overflow on deeply nested calls.</strong> The call stack has a finite size; recursive code without a base case can throw <code>RangeError: Maximum call stack size exceeded</code>. Microtasks scheduled recursively from inside their own drain pass can also starve the loop — each new microtask runs before the queue is &quot;empty&quot;, so rendering and tasks never get a turn.</li>
<li><strong><code>setTimeout(fn, 0)</code> is &quot;after tasks and microtasks&quot;, not &quot;immediately&quot;.</strong> A 0 ms delay still yields to the loop. Use <code>queueMicrotask(fn)</code> if you really want the soonest possible callback.</li>
<li><strong>Order across I/O is not guaranteed by delay alone.</strong> Two <code>fetch</code> calls that complete at the same time will resolve in the order their responses arrived at the network, not the order you called them. Don&#39;t rely on the literal source order.</li>
</ul>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-is-the-difference-between-a-task-and-a-microtask">What is the difference between a task and a microtask?</h3><p>A <strong>task</strong> is the unit of work the event loop pulls on each iteration: the initial script, an event callback, a <code>setTimeout</code> callback, or an I/O completion. A <strong>microtask</strong> is a smaller, higher-priority unit that always runs after the current task and before the next task. Microtasks include promise callbacks (<code>then</code>/<code>catch</code>/<code>finally</code>), <code>queueMicrotask()</code> callbacks, and <code>MutationObserver</code> callbacks. The event loop drains the entire microtask queue after every task, so a queued microtask is guaranteed to run before the next task starts.</p>
<h3 id="why-does-promise-then-run-before-settimeout-fn-0">Why does Promise.then run before setTimeout(fn, 0)?</h3><p>Promise callbacks are microtasks; <code>setTimeout</code> callbacks are tasks. The event loop drains the microtask queue after every task and before pulling the next task from the task queue. So in a single iteration, a microtask scheduled during the current task — like the one a resolved promise places — always runs before any newly-pending task, including one whose timer has already expired. This is a deliberate HTML spec choice: it makes promise resolution feel synchronous to user code.</p>
<h3 id="is-the-event-loop-part-of-javascript">Is the event loop part of JavaScript?</h3><p>No. The event loop is provided by the host environment — the browser (per the HTML specification) or Node.js (per its libuv-backed model). The JavaScript language itself defines the execution model — a single thread of execution, a stack of execution contexts, a job queue for promise reactions — but the loop that ties it to timers, I/O, and rendering lives in the host. Different hosts can have different scheduler details while keeping the same user-facing shape.</p>
<h3 id="what-starves-the-event-loop">What starves the event loop?</h3><p>A microtask that schedules another microtask, recursively, will keep the microtask queue non-empty and starve tasks and rendering. Recursive <code>Promise.resolve().then(...)</code> chains are the classic example; a <code>while</code> loop that synchronously schedules microtasks is another. On the task side, a long synchronous computation — parsing a huge JSON, sorting a large array, a tight loop — blocks the call stack, which is the same thing as blocking the loop because nothing else gets a chance to run until the stack unwinds.</p>
</section>
<section><h2 id="summary"><a href="#summary" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Summary</h2><p>The event loop is the small, repeating rule that turns a single-threaded language into a responsive runtime: run one task, drain all microtasks, render if it&#39;s time, repeat. Once you internalize the stack/queue split and the microtask-first rule, the seemingly magical ordering of <code>setTimeout</code>, <code>fetch</code>, and <code>Promise.then</code> becomes obvious — and bugs caused by treating JavaScript as &quot;multi-threaded under the hood&quot; become much easier to spot.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>How to Use Environment Variables in Node.js: process.env, .env files, and secure secrets</title>
      <link>https://debod.id/en/posts/environment-variables-nodejs/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/environment-variables-nodejs/</guid>
      <pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate>
      <category>nodejs</category>
      <category>configuration</category>
      <category>nodejs</category>
      <category>security</category>
      <description>Environment variables are the standard way to configure a Node.js app without hardcoding values. Node reads them through process.env, you can load them from .env files with the built-in --env-file flag or the dotenv package, and secrets should stay out of your code and version control. This guide walks through each approach and when to use it.</description>
      <content:encoded><![CDATA[<section><p>Environment variables are key-value pairs that live outside your application code and configure how it runs in different situations. In Node.js you read them through <code>process.env</code>, which is an object pre-populated with your user&#39;s environment variables. Values you need across environments and should not hardcode — database URLs, API keys, ports, feature flags — belong in environment variables rather than in the source code. This guide shows how to read them, how to load them from <code>.env</code> files, and how to keep real secrets out of your repository.</p></section>
<section><h2 id="what-process-env-is"><a href="#what-process-env-is" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What process.env is</h2><p>In Node.js, <code>process.env</code> is a property that returns an object containing the user environment — the set of key-value variables the <code>node</code> process inherits from the shell or the environment you started it in. You read a value simply by accessing the property name:</p>
<pre><code class="language-js">console.log(process.env.NODE_ENV);
// &quot;development&quot; if you set NODE_ENV=development before starting the process
</code></pre>
<p>You can also set a value at runtime by assigning a property:</p>
<pre><code class="language-js">process.env.MY_VAR = &quot;value&quot;;
</code></pre>
<p>In older Node.js versions assigning a non-string value was implicitly converted to a string; that behavior is deprecated and future versions may throw when the value is not a string, number, or boolean. Use <code>delete process.env.KEY</code> to remove a property.</p>
<p>Environment variables are not namespaced per app — they are inherited from the process that launched Node. That is exactly why they are good for configuration that differs between your laptop and production: the same code reads <code>process.env.DB_URL</code>, and each environment supplies its own value.</p>
</section>
<section><h2 id="setting-variables-in-the-shell"><a href="#setting-variables-in-the-shell" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Setting variables in the shell</h2><p>Before the process starts, you set variables in the environment. In a POSIX shell:</p>
<pre><code class="language-sh">NODE_ENV=production node app.js
</code></pre>
<p>or export it for the current session:</p>
<pre><code class="language-sh">export DB_URL=postgres://localhost:5432
node app.js
</code></pre>
<p>Values set this way are visible to the process through <code>process.env</code>. For local development, however, exporting dozens of values by hand is tedious, which is why <code>.env</code> files exist.</p>
</section>
<section><h2 id="reading-env-files"><a href="#reading-env-files" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Reading .env files</h2><p>A <code>.env</code> file (also called a dotenv file) is a plain-text list of <code>KEY=VALUE</code> pairs that define environment variables an application can load. The format was popularized by the <code>dotenv</code> package, and modern Node.js can read these files natively without any dependency.</p>
<h3 id="option-1-node-s-built-in-env-file-node-js-20-6">Option 1: Node&#39;s built-in --env-file (Node.js 20.6+)</h3><p>Since Node.js 20.6 you can load a <code>.env</code> file directly from the command line with no package install. If you have a <code>.env</code> file:</p>
<pre><code class="language-text">DB_URL=postgres://localhost:5432
PORT=8080
</code></pre>
<p>start the process with:</p>
<pre><code class="language-sh">node --env-file=.env app.js
</code></pre>
<p>The variables in the file populate <code>process.env</code>, so your code reads them the same way as any other environment variable. If the same variable is defined both in the shell environment and in the file, the value from the environment takes precedence. You can pass multiple <code>--env-file</code> arguments, and later files override variables set by earlier ones; <code>--env-file-if-exists</code> is the variant that silently skips a missing file instead of failing.</p>
<p>Node.js also exposes two programmatic helpers: <code>process.loadEnvFile(path)</code> loads a file and populates <code>process.env</code>, and <code>util.parseEnv()</code> parses raw <code>.env</code> content into an object.</p>
<h3 id="option-2-the-dotenv-package">Option 2: the dotenv package</h3><p>The <code>dotenv</code> package loads a <code>.env</code> file at runtime. Install it, call <code>config()</code> early in your entry file, and <code>process.env</code> is populated:</p>
<pre><code class="language-sh">npm install dotenv
</code></pre>
<pre><code class="language-js">require(&quot;dotenv&quot;).config();
console.log(process.env.DB_URL);
</code></pre>
<p>You can point it at a custom path with <code>dotenv.config({ path: &quot;/custom/path/to/.env&quot; })</code>. dotenv is a fine choice when you want the loader inside your code or need to support older Node.js versions, but with a modern Node.js release the built-in <code>--env-file</code> flag removes the dependency entirely.</p>
</section>
<section><h2 id="env-file-format-rules"><a href="#env-file-format-rules" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>.env file format rules</h2><p>The <code>.env</code> format follows a few simple rules, and both Node&#39;s implementation and dotenv honor them:</p>
<ul>
<li><strong>Variable names</strong> must match <code>^[a-zA-Z_]+[a-zA-Z0-9_]*$</code> — start with a letter or underscore, then letters, digits, or underscores. <code>MY_VAR</code>, <code>my_var</code>, and <code>My_Var123</code> are valid; <code>1_VAR</code>, <code>my-var</code>, <code>my var</code>, and trailing <code>VAR_</code> are not.</li>
<li><strong>Values</strong> are arbitrary text and may be wrapped in single or double quotes. Quoted values can span multiple lines; unquoted ones are single-line. Values are always parsed as text — <code>0</code>, <code>true</code>, and <code>{&quot;hello&quot;:&quot;world&quot;}</code> all become the literal strings <code>&quot;0&quot;</code>, <code>&quot;true&quot;</code>, and <code>&#39;{&quot;hello&quot;:&quot;world&quot;}&#39;</code>, not a number, boolean, or object.</li>
<li><strong>Spacing</strong> around keys and values is ignored unless enclosed in quotes.</li>
<li><strong>Comments</strong> start with <code>#</code> and run to the end of the line; a <code>#</code> inside quotes is a normal character.</li>
<li><strong><code>export</code> prefixes</strong> are allowed and ignored — useful when you also want to <code>source</code> the same file in a shell.</li>
</ul>
<p>Example:</p>
<pre><code class="language-text"># application config
NODE_ENV=development
DB_URL=&quot;postgres://localhost:5432&quot;
# a value with a literal hashtag must be quoted
API_TOKEN=&#39;abc#123&#39;
</code></pre>
</section>
<section><h2 id="why-secrets-must-not-go-in-code"><a href="#why-secrets-must-not-go-in-code" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Why secrets must not go in code</h2><p>Environment variables solve a real security problem: configuration and credentials should not be committed to source control. Hardcoded values in code end up in your repository history, where anyone with access — and anyone who consumes a leaked snapshot — can read them. A <code>.env</code> file that contains real secrets should be listed in <code>.gitignore</code> so it is never committed, while a committed <code>.env.example</code> file documents which variables the app needs, with placeholder values.</p>
<p>Separating secrets from code also makes rotation possible without a redeploy. When a key leaks or expires, you update the variable in the deployment platform and restart, instead of editing source and shipping a new build. Managed secret stores — a secrets manager such as <a href="/en/tools/1password/">1Password</a> or a purpose-built store such as <a href="/en/tools/vault/">HashiCorp Vault</a> — take this further by injecting values into the runtime environment or into deployment pipelines, which is the approach most mature teams move toward.</p>
</section>
<section><h2 id="environment-variables-in-deployment-and-ci"><a href="#environment-variables-in-deployment-and-ci" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Environment variables in deployment and CI</h2><p>The same code that reads <code>process.env</code> in development reads it in production. Hosting platforms let you set environment variables in their dashboard or config rather than in code, and CI/CD systems expose them as encrypted secrets. In a CI pipeline such as <a href="/en/tools/github-actions/">GitHub Actions</a>, you define secrets in the repository settings and reference them in the workflow, so keys never appear in the workflow file or the log. Containerized apps follow the same principle — tools like <a href="/en/tools/docker/">Docker</a> pass environment variables into containers, keeping runtime configuration out of the image.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="should-i-use-env-file-or-the-dotenv-package">Should I use --env-file or the dotenv package?</h3><p>Use Node.js&#39;s built-in <code>--env-file</code> flag (Node.js 20.6+) when you can pick the Node.js version — it needs no dependency and is the most direct option. Use the <code>dotenv</code> package when you need to load the file from inside your code, call it at a specific point at runtime, or must support an older Node.js version. Both read the same <code>.env</code> format. The native flag also has the advantage that the loader runs before your application code, so configuration is ready from the first line.</p>
<h3 id="are-environment-variables-secure">Are environment variables secure?</h3><p>Environment variables keep secrets out of your source code and out of version control, which is a meaningful improvement — but they are not a complete security boundary. Anyone who can run code or read memory on the host can read them, and they can leak through logging or client-side exposure if you ship them to the browser. Never hardcode or log secrets, keep real ones out of committed <code>.env</code> files (<code>.env</code> should be in <code>.gitignore</code>), and use a dedicated secrets manager for the most sensitive credentials.</p>
<h3 id="what-is-the-difference-between-process-env-and-a-env-file">What is the difference between process.env and a .env file?</h3><p><code>process.env</code> is the live object Node exposes, pre-populated from the environment the process was launched in. A <code>.env</code> file is just a convenient on-disk format for declaring those variables so they can be loaded (via <code>--env-file</code> or dotenv) instead of being typed by hand. Loading a <code>.env</code> file populates <code>process.env</code>; it does not create a separate store.</p>
<h3 id="how-do-i-set-environment-variables-in-production">How do I set environment variables in production?</h3><p>You do not read or set them in your application code. Instead, configure them in your hosting platform&#39;s dashboard, config file, or deployment manifest, and for CI define them as encrypted repository secrets. The running process receives them just like local variables, so your code reads <code>process.env</code> unchanged across every environment.</p>
</section>
<section><h2 id="verdict"><a href="#verdict" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Verdict</h2><p>Use <code>process.env</code> to read configuration, and prefer Node.js&#39;s native <code>--env-file</code> flag (Node.js 20.6+) for loading local <code>.env</code> files so you need no extra dependency — or reach for <code>dotenv</code> if you must load from inside your code on an older runtime. Keep every real secret out of source control: put <code>.env</code> in <code>.gitignore</code>, commit an <code>.env.example</code> of placeholders, and let your deployment platform or a secrets manager like 1Password or HashiCorp Vault inject production values into the environment. The rule that makes all of it safe is simple: configuration belongs in the environment, never in the code.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>Docker vs Kubernetes: which one do you need?</title>
      <link>https://debod.id/en/posts/docker-vs-kubernetes/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/docker-vs-kubernetes/</guid>
      <pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate>
      <category>devops</category>
      <category>docker</category>
      <category>kubernetes</category>
      <category>containers</category>
      <category>devops</category>
      <category>orchestration</category>
      <category>cloud</category>
      <description>Docker and Kubernetes solve different problems: Docker packages and runs one application in a container, while Kubernetes orchestrates many containers across a cluster of machines. Compare what each does, when you need orchestration, and how to choose.</description>
      <content:encoded><![CDATA[<section><p>Docker and Kubernetes are often mentioned in the same sentence, but they solve different problems. Docker packages and runs a single application inside a container. Kubernetes orchestrates many containers running across many machines. They are complementary tools, not competitors you must pick between — yet you almost never need both on day one. This guide explains what each one actually does, where the real trade-offs sit, and how to decide.</p></section>
<section><h2 id="what-docker-is-the-container-platform"><a href="#what-docker-is-the-container-platform" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What Docker is: the container platform</h2><p>Docker is an open platform for developing, shipping, and running applications. Its core idea is the <strong>container</strong>: a lightweight, isolated environment that packages your application code together with its runtime, system libraries, and settings. Containers are self-contained and run the same way on a developer&#39;s laptop, a test server, or a production cloud VM — because they carry everything they need and do not depend on what is already installed on the host.</p>
<p>A container is not a virtual machine. A VM runs a full guest operating system on top of a hypervisor; a container shares the host operating system&#39;s kernel and runs as an isolated process. Because containers share the kernel and are not booting a whole OS, they are much lighter: container images are typically tens of megabytes, and a container can start in seconds or less.</p>
<p>Two key Docker concepts to hold onto:</p>
<ul>
<li>An <strong>image</strong> is a read-only template with instructions for creating a container — often built from another image, plus your application and its configuration. You define how to build it in a <code>Dockerfile</code>.</li>
<li>A <strong>container</strong> is a runnable instance of an image. You can start, stop, and restart it, and anything you write inside it that is not stored in a persistent volume disappears when the container is removed.</li>
</ul>
<p>Docker was launched in 2013 as an open-source project. It built on Linux primitives called <strong>cgroups</strong> and <strong>namespaces</strong>: cgroups limit and account for CPU, memory, and I/O, while namespaces isolate processes, networking, filesystems, and users so each container behaves as if it owns the machine. Docker also standardized how containers are built and shared through registries such as Docker Hub.</p>
</section>
<section><h2 id="what-kubernetes-is-the-orchestration-platform"><a href="#what-kubernetes-is-the-orchestration-platform" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What Kubernetes is: the orchestration platform</h2><p>If Docker answers &quot;how do I run one app in a container?&quot;, Kubernetes answers &quot;how do I run and manage many containers across many machines, reliably?&quot; Kubernetes (from the Greek for <em>helmsman</em> or <em>pilot</em>, abbreviated <strong>k8s</strong>) is the open-source system for automating deployment, scaling, and management of containerized applications.</p>
<p>Google open-sourced Kubernetes in June 2014, drawing on more than fifteen years of experience running production workloads at scale — an internal cluster-management system called Borg, and its successor Omega, pioneered the ideas. Version 1.0 shipped on July 21, 2015, and the same year Google donated the project to the newly formed Cloud Native Computing Foundation (CNCF), a Linux Foundation body. Kubernetes was the CNCF&#39;s first project to graduate from incubation, which happened on March 6, 2018.</p>
<p>A Kubernetes deployment is called a <strong>cluster</strong>, and it has two parts:</p>
<ul>
<li>The <strong>control plane</strong> makes global decisions about the cluster — scheduling work, detecting and responding to events — and exposes the API you talk to.</li>
<li><strong>Worker nodes</strong> are the machines (physical or virtual) that actually run your applications. Each node runs a <strong>kubelet</strong> agent that makes sure the containers are running as requested, and a <strong>kube-proxy</strong> that maintains network rules.</li>
</ul>
<p>The smallest deployable unit in Kubernetes is the <strong>Pod</strong>: one or more tightly coupled containers that share a network namespace and volumes. Higher-level objects like <strong>Deployments</strong> and <strong>Services</strong> build on pods to give you replicas, rolling updates, stable networking, and self-healing.</p>
<p>What Kubernetes actually gives you, in plain terms:</p>
<ul>
<li><strong>Self-healing</strong> — if a container or node fails, Kubernetes restarts or reschedules the work to meet the state you declared.</li>
<li><strong>Scaling</strong> — you say how many replicas you want, and Kubernetes adds or removes them, including automatically based on load.</li>
<li><strong>Rolling updates and rollbacks</strong> — you can ship a new version without taking the service down, and roll back if it misbehaves.</li>
<li><strong>Service discovery and load balancing</strong> — a stable address that routes traffic to the healthy pods of a service.</li>
</ul>
</section>
<section><h2 id="the-key-difference-one-container-vs-a-fleet"><a href="#the-key-difference-one-container-vs-a-fleet" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The key difference: one container vs a fleet</h2><p>Docker is about <strong>containers</strong> — packaging and running a single unit of software. Kubernetes is about <strong>orchestration</strong> — managing a fleet of containers distributed across a cluster, keeping them healthy, scaled, and reachable.</p>
<p>You can run Docker without Kubernetes: a single command, <code>docker run my-app</code>, starts one container. That is often enough for a local project or a small service. Kubernetes without Docker does not really apply to this question — Kubernetes <em>runs</em> containers (Docker-provided or via other container runtimes), so it sits on top of the packaging that tools like Docker provide.</p>
<p>Crucially, Kubernetes is <strong>not</strong> a faster or &quot;better Docker.&quot; It is a much larger and more complex piece of infrastructure. Running a production Kubernetes cluster means operating the control plane, handling node upgrades, securing the API, and maintaining the surrounding ecosystem of networking and storage. That overhead only pays off when you have enough moving parts that manual container management becomes the bottleneck.</p>
</section>
<section><h2 id="when-you-only-need-docker"><a href="#when-you-only-need-docker" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>When you only need Docker</h2><p>You probably only need Docker when you are running <strong>one application, or a handful of them on one machine</strong>:</p>
<ul>
<li>Developing or demoing a service locally with a consistent runtime.</li>
<li>Deploying a single containerized app to a single server or VM.</li>
<li>Using <strong>Docker Compose</strong> to run a small multi-container app (for example a web server plus a database) on one host — Compose manages a set of containers on a single machine without a cluster.</li>
</ul>
<p>For one or a few containers on one machine, a cluster manager adds nothing but complexity. You can start with <code>docker run</code>, then move to Compose when you have a few services, and keep using Docker for months or years without ever touching Kubernetes.</p>
</section>
<section><h2 id="when-kubernetes-becomes-worth-it"><a href="#when-kubernetes-becomes-worth-it" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>When Kubernetes becomes worth it</h2><p>Consider Kubernetes when you are running <strong>many containers across multiple machines</strong>, and you care about any of these:</p>
<ul>
<li><strong>High availability</strong> — your service must survive a machine or container failing.</li>
<li><strong>Horizontal scaling</strong> — you need many replicas that grow and shrink with load, across nodes.</li>
<li><strong>Rolling deployments</strong> — you ship continuously and want zero-downtime updates and easy rollbacks.</li>
<li><strong>Multi-service consistency</strong> — many services share networking, config, and secrets in one declarative system.</li>
</ul>
<p>The honest guideline: if you can run your workload on one machine, you do not need Kubernetes yet. If your workload spans several machines and one of the above concerns is real, Kubernetes starts earning its complexity. Many teams take the path Docker → Docker Compose → a managed Kubernetes service (like those offered by the major cloud providers, where the control plane is operated for you) rather than running the control plane themselves.</p>
</section>
<section><h2 id="docker-vs-kubernetes-compared"><a href="#docker-vs-kubernetes-compared" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Docker vs Kubernetes compared</h2><table>
<thead>
<tr>
<th>Aspect</th>
<th>Docker</th>
<th>Kubernetes</th>
</tr>
</thead>
<tbody><tr>
<td>What it does</td>
<td>Packages and runs one application in a container</td>
<td>Orchestrates many containers across a cluster of machines</td>
</tr>
<tr>
<td>Unit it manages</td>
<td>Image and container</td>
<td>Pod (one or more containers), Deployment, Service</td>
</tr>
<tr>
<td>Default scale</td>
<td>Single machine</td>
<td>Multiple nodes (a cluster)</td>
</tr>
<tr>
<td>Getting started</td>
<td><code>docker run</code> — one command</td>
<td>Cluster setup plus <code>kubectl</code></td>
</tr>
<tr>
<td>Operational complexity</td>
<td>Low</td>
<td>High (control plane, nodes, networking, storage)</td>
</tr>
<tr>
<td>Key features</td>
<td>Reproducible environments, consistent runtime</td>
<td>Self-healing, scaling, rolling updates, service discovery</td>
</tr>
<tr>
<td>Best for</td>
<td>A single app or a small set of services</td>
<td>Fleets of services needing availability and scale</td>
</tr>
<tr>
<td>Relationship</td>
<td>Provides the container packaging</td>
<td>Runs and manages containers at scale</td>
</tr>
</tbody></table>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="are-docker-and-kubernetes-competitors">Are Docker and Kubernetes competitors?</h3><p>No. Docker standardizes how you build and package a single container; Kubernetes manages many containers running across many machines. They work together: Kubernetes schedules and runs the containers that tools like Docker help you build. The more accurate mental model is &quot;Docker for packaging, Kubernetes for orchestration.&quot;</p>
<h3 id="do-i-need-kubernetes-if-i-use-docker">Do I need Kubernetes if I use Docker?</h3><p>Only if you run many containers across multiple machines and need high availability, scaling, or rolling deployments. For one or a few containers on a single machine, Docker (optionally with Docker Compose) is enough — adding Kubernetes would only add operational complexity.</p>
<h3 id="can-kubernetes-run-without-docker">Can Kubernetes run without Docker?</h3><p>Kubernetes can use other container runtimes besides Docker, but the question of &quot;Kubernetes vs Docker&quot; is really &quot;do you need orchestration?&quot; Kubernetes still runs containers; it manages the ones you package, however you build them. Orchestration does not replace packaging.</p>
<h3 id="should-i-start-learning-docker-before-kubernetes">Should I start learning Docker before Kubernetes?</h3><p>Yes. Kubernetes assumes you understand images, containers, and how to run them. Learn Docker, get comfortable running and shipping a container, add Docker Compose for multi-container apps, and move to Kubernetes only when a real deployment outgrows a single machine.</p>
</section>
<section><h2 id="verdict"><a href="#verdict" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Verdict</h2><p>Docker and Kubernetes are partners, not rivals. Start with Docker for reliable, reproducible single-container development and deployment. Reach for Docker Compose when a few services run together on one host. Move to Kubernetes when your workload spans several machines and you need self-healing, scaling, or rolling deployments — and if possible, start with a managed Kubernetes service to hand off the control-plane burden. When in doubt, the simpler option is usually right: you can always adopt orchestration after a real deployment demands it.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>How to containerize a Node.js app with Docker</title>
      <link>https://debod.id/en/posts/docker-containerize-nodejs/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/docker-containerize-nodejs/</guid>
      <pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate>
      <category>programming</category>
      <category>docker</category>
      <category>nodejs</category>
      <category>containers</category>
      <category>devops</category>
      <description>Containerize a Node.js app with Docker: write a Dockerfile, use base images and multi-stage builds, expose ports, manage dependencies and environment variables, and run your container locally — open-license sources.</description>
      <content:encoded><![CDATA[<section><p>Containerization packages your application together with its runtime and dependencies into a single, portable image that runs the same way on any machine that has Docker. This guide shows how to containerize a Node.js app: what goes into a <code>Dockerfile</code>, how to choose a base image, how to use a multi-stage build to keep the final image small, how to handle dependencies and environment variables, and how to build and run the container locally with real commands you can adapt to your own project.</p></section>
<section><h2 id="what-containerization-and-docker-mean"><a href="#what-containerization-and-docker-mean" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What containerization and Docker mean</h2><p>A <strong>container</strong> is a runnable instance of an image — a lightweight, standalone, executable package that includes everything the software needs to run: code, runtime, system tools, libraries, and settings. Containers share the host operating system kernel and run as isolated processes, which is why they start in seconds and use far fewer resources than a full virtual machine.</p>
<p><strong>Docker</strong> is a platform for building, sharing, and running containers. Its main pieces are:</p>
<ul>
<li><strong>Image</strong> — a read-only template with instructions for creating a container.</li>
<li><strong>Container</strong> — a runnable instance of an image.</li>
<li><strong>Dockerfile</strong> — a text file of instructions that build an image.</li>
<li><strong>Docker Engine</strong> — the background service that builds and runs images.</li>
<li><strong>Docker Hub / registries</strong> — places to store and share images.</li>
</ul>
<p>Because the image bundles the exact runtime and dependencies, the same image behaves identically on a laptop, a test server, or a cloud production machine. This removes the classic &quot;it works on my machine&quot; problem.</p>
</section>
<section><h2 id="prerequisites"><a href="#prerequisites" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Prerequisites</h2><ul>
<li><strong>Docker installed and running</strong> — Docker Desktop on Windows and macOS, Docker Engine + CLI on Linux. Verify with <code>docker --version</code>.</li>
<li>A Node.js project — at minimum a <code>package.json</code> with a <code>start</code> script and a source entry point (for example <code>index.js</code>).</li>
<li>Basic familiarity with the command line.</li>
</ul>
<p>You do not need Node.js installed locally to build or run the container, because the base image provides the runtime.</p>
</section>
<section><h2 id="step-1-understand-the-parts-of-a-dockerfile"><a href="#step-1-understand-the-parts-of-a-dockerfile" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Step 1 — Understand the parts of a Dockerfile</h2><p>A <code>Dockerfile</code> is a build script for your image. Each instruction is a line that Docker executes, and each instruction creates a layer. Common instructions:</p>
<ul>
<li><code>FROM</code> — the base image your image starts from (the runtime and OS it builds on).</li>
<li><code>WORKDIR</code> — sets the working directory for the commands that follow.</li>
<li><code>COPY</code> — copies files or directories from your build context into the image.</li>
<li><code>RUN</code> — executes a command during the build (used to install dependencies).</li>
<li><code>ENV</code> — sets an environment variable.</li>
<li><code>EXPOSE</code> — documents the port the app listens on (informational; it does not publish the port).</li>
<li><code>CMD</code> — the default command run when a container starts from the image.</li>
</ul>
<p>The simplest possible Node.js Dockerfile looks like this:</p>
<pre><code class="language-dockerfile">FROM node:20
WORKDIR /app
COPY . .
RUN npm install --omit=dev
EXPOSE 3000
CMD [&quot;node&quot;, &quot;index.js&quot;]
</code></pre>
<p>This is a valid starting point, but it has problems: it copies your whole directory, installs everything including tools you only need for building, and can be large. The next sections improve it.</p>
</section>
<section><h2 id="step-2-choose-a-good-base-image-and-use-dockerignore"><a href="#step-2-choose-a-good-base-image-and-use-dockerignore" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Step 2 — Choose a good base image and use <code>.dockerignore</code></h2><p>The base image in <code>FROM</code> provides the runtime. For Node.js, the official images are published as <code>node:&lt;version&gt;</code> with tags such as <code>node:20</code>, <code>node:20-alpine</code>, and <code>node:20-slim</code>. The plain variant is based on Debian, while the <code>-alpine</code> variant is much smaller because it is built on Alpine Linux. Smaller images are faster to pull and have a smaller attack surface, so a common choice for the final runtime stage is an <code>-alpine</code> or <code>-slim</code> variant.</p>
<p>Create a <code>.dockerignore</code> file in your project root. It works like <code>.gitignore</code> and tells <code>docker build</code> which files to leave out of the build context, keeping the build faster and preventing local folders such as <code>node_modules</code> from being copied in:</p>
<pre><code>node_modules
npm-debug.log
.git
.env
.containerignore
Dockerfile
.dockerignore
</code></pre>
<p>If you already have a local <code>node_modules</code>, excluding it here prevents the build from copying the wrong (host-specific) dependencies into the image.</p>
</section>
<section><h2 id="step-3-install-only-what-you-need"><a href="#step-3-install-only-what-you-need" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Step 3 — Install only what you need</h2><p>Inside the container you install dependencies with npm. A key detail is the difference between <code>npm install</code> and <code>npm ci</code>:</p>
<ul>
<li><code>npm ci</code> installs from the lock file (<code>package-lock.json</code>) and is faster and more reproducible — it is the recommended choice when a lock file is present.</li>
<li><code>npm install --omit=dev</code> installs only production dependencies, skipping <code>devDependencies</code> such as test frameworks and build tools.</li>
</ul>
<p>A production-first install looks like:</p>
<pre><code class="language-dockerfile">FROM node:20-alpine
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY . .
EXPOSE 3000
CMD [&quot;node&quot;, &quot;index.js&quot;]
</code></pre>
<p>Copying <code>package.json</code> and <code>package-lock.json</code> <strong>before</strong> the rest of the source lets Docker cache the dependency-install layer. Because <code>package.json</code> changes far less often than your source code, Docker reuses the cached layer on rebuilds and only reinstalls when the lock file actually changes — this makes most rebuilds much faster.</p>
</section>
<section><h2 id="step-4-use-a-multi-stage-build-to-keep-the-image-small"><a href="#step-4-use-a-multi-stage-build-to-keep-the-image-small" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Step 4 — Use a multi-stage build to keep the image small</h2><p>Many Node.js projects need a build step: compiling TypeScript, bundling a frontend, or running a build tool. If you run that build inside the same image that ships to production, the final image carries the build tools and dev dependencies with it — unnecessarily large.</p>
<p>A <strong>multi-stage build</strong> uses multiple <code>FROM</code> instructions. Each <code>FROM</code> starts a new stage, and you copy only selected artifacts from an earlier stage into the final one with <code>COPY --from</code>. The earlier stages are discarded, so the final image contains only what the app needs at runtime.</p>
<pre><code class="language-dockerfile"># Stage 1 — build
FROM node:20 AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build

# Stage 2 — production runtime
FROM node:20-alpine
ENV NODE_ENV=production
WORKDIR /app
COPY --from=build /app/package.json ./
COPY --from=build /app/dist ./dist
RUN npm ci --omit=dev
EXPOSE 3000
CMD [&quot;node&quot;, &quot;dist/index.js&quot;]
</code></pre>
<p>Here the <code>build</code> stage installs all dependencies (including dev ones) and runs <code>npm run build</code>, and the final stage copies only the built output and the lock file, then installs only production dependencies. The result is a small image that ships exactly the runtime artifacts.</p>
</section>
<section><h2 id="step-5-handle-environment-variables-and-non-root-users"><a href="#step-5-handle-environment-variables-and-non-root-users" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Step 5 — Handle environment variables and non-root users</h2><p>Configuration that changes between environments — database URLs, API keys, feature flags — should come from <strong>environment variables</strong>, not be baked into the image. You can set a default in the image with <code>ENV</code>, but you override it at runtime.</p>
<pre><code class="language-dockerfile">FROM node:20-alpine
ENV NODE_ENV=production
ENV PORT=3000
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY . .
EXPOSE 3000
CMD [&quot;node&quot;, &quot;index.js&quot;]
</code></pre>
<p>Pass real values when you run the container:</p>
<pre><code class="language-console">docker run -e PORT=8080 -e DATABASE_URL=postgres://user:pass@host/db -p 8080:8080 my-app
</code></pre>
<p><strong>Never store secrets in the image</strong> — do not put tokens or passwords in the <code>Dockerfile</code> or copy an <code>.env</code> file in. Pass secrets at runtime with <code>-e</code> (or a secrets mechanism) instead.</p>
<p>It is also good practice to avoid running the app as <code>root</code>. The official <code>node</code> images already provide a <code>node</code> user; switch to it before starting the app:</p>
<pre><code class="language-dockerfile">FROM node:20-alpine
WORKDIR /app
COPY . .
RUN npm ci --omit=dev
USER node
ENV PORT=3000
EXPOSE 3000
CMD [&quot;node&quot;, &quot;index.js&quot;]
</code></pre>
<p>Running as a non-root user reduces the impact if the container is compromised.</p>
</section>
<section><h2 id="step-6-build-and-run-the-container"><a href="#step-6-build-and-run-the-container" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Step 6 — Build and run the container</h2><p>With the <code>Dockerfile</code> in place, build the image from your project root:</p>
<pre><code class="language-console">docker build -t my-app .
</code></pre>
<p>The <code>-t my-app</code> gives the image a name and tag. (The trailing <code>.</code> is the build context directory.) You can view it with <code>docker images</code>.</p>
<p>Run a container from the image:</p>
<pre><code class="language-console">docker run -p 3000:3000 my-app
</code></pre>
<p>The <code>-p 3000:3000</code> maps port 3000 on your host to port 3000 in the container — this is what makes the app reachable from your browser. (The <code>EXPOSE</code> line in the Dockerfile only documents the port; the <code>-p</code> flag actually publishes it.) Open <code>http://localhost:3000</code> to see your app.</p>
<p>Run it detached in the background and give it a name:</p>
<pre><code class="language-console">docker run -d --name my-app -p 3000:3000 my-app
</code></pre>
<p>Manage containers with <code>docker ps</code> (running), <code>docker ps -a</code> (all), <code>docker logs my-app</code> (logs), <code>docker stop my-app</code>, and <code>docker rm my-app</code>. To remove an unneeded image, use <code>docker image rm my-app</code>.</p>
<p>The ordinary workflow is: change your code → <code>docker build</code> again → <code>docker run</code> the new image → test. Because the dependency layers are cached, iterative rebuilds after the first are fast.</p>
</section>
<section><h2 id="common-mistakes-to-avoid"><a href="#common-mistakes-to-avoid" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Common mistakes to avoid</h2><ul>
<li><strong>Missing <code>.dockerignore</code></strong> — your local <code>node_modules</code> gets copied into the image, producing a huge, broken image.</li>
<li><strong>Installing dev dependencies in production</strong> — use <code>npm ci --omit=dev</code> in the final stage.</li>
<li><strong>Copying all source before installing</strong> — copy <code>package.json</code>/<code>package-lock.json</code> first to keep the dependency cache valid.</li>
<li><strong>Ship the build environment</strong> — a single-stage build with a build step ships build tools; use a multi-stage build to copy only the output.</li>
<li><strong>Hard-coding secrets</strong> — never bake tokens or <code>.env</code> files into the image; pass them at runtime.</li>
<li><strong>Forgetting <code>-p</code></strong> — <code>EXPOSE</code> only documents the port; you must publish it with <code>-p</code> to reach the app from your host.</li>
<li><strong>Running as root</strong> — use the non-root <code>node</code> user unless you have a concrete reason not to.</li>
</ul>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-is-the-difference-between-an-image-and-a-container">What is the difference between an image and a container?</h3><p>An image is a read-only template with the instructions (layers) for creating a container. A container is a runnable instance of that image. You can build one image and run many containers from it, each isolated on the host.</p>
<h3 id="is-docker-free-to-use">Is Docker free to use?</h3><p>Docker Engine and the Docker CLI are open source, and the Docker platform offers a free tier; Docker Desktop&#39;s licensing has a paid tier for larger companies. The container format and the <code>docker build</code>/<code>docker run</code> commands themselves are free to use on your own machines, and many container registries offer free public storage.</p>
<h3 id="why-use-a-multi-stage-build">Why use a multi-stage build?</h3><p>A multi-stage build separates building from running. You do the heavy work (installing dev dependencies, compiling, bundling) in a build stage, then copy only the needed artifacts into a slim final stage. The final image is smaller, faster to pull, has a smaller attack surface, and does not leak build tools or secrets.</p>
<h3 id="what-does-p-3000-3000-do">What does <code>-p 3000:3000</code> do?</h3><p>It publishes the container&#39;s port 3000 to port 3000 on your host, so the application is reachable at <code>http://localhost:3000</code>. The left number is the host port, the right the container port; they can differ (for example <code>-p 8080:3000</code>).</p>
<h3 id="do-i-need-node-js-installed-to-run-the-container">Do I need Node.js installed to run the container?</h3><p>No. The base image (<code>node:&lt;version&gt;</code>) contains the Node.js runtime. The whole point of containerizing is that the image bundles everything the app needs, so the machine only needs Docker — not a local Node.js install.</p>
</section>
<section><h2 id="summary"><a href="#summary" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Summary</h2><p>Containerizing a Node.js app is a small, repeatable process: write a <code>Dockerfile</code> that selects a base image, copies your manifest first for fast cached installs, installs only production dependencies, exposes and maps the port, and starts your app with <code>CMD</code>. Use a <code>.dockerignore</code> to keep the build clean, use a multi-stage build when you have a build step so the production image stays small, keep secrets and environment configuration out of the image, and run as a non-root user. Then <code>docker build -t my-app .</code> and <code>docker run -p 3000:3000 my-app</code> give you a portable containerized version of your app that runs identically anywhere Docker is installed.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>How to use Docker Compose: multi-container apps explained</title>
      <link>https://debod.id/en/posts/docker-compose-guide/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/docker-compose-guide/</guid>
      <pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate>
      <category>programming</category>
      <category>docker</category>
      <category>containers</category>
      <category>devops</category>
      <category>yaml</category>
      <description>Learn Docker Compose to run multi-container apps from one YAML file: services, networks, volumes, health checks, Compose Watch, env vars, docker compose up and common commands — with a real Node.js example.</description>
      <content:encoded><![CDATA[<section><p>A single Docker container runs one process. Real applications usually need several: a web server, a database, a cache, a queue worker. Docker Compose lets you define and run all these services together from one YAML file, then start the whole stack with a single command. This guide shows how Compose works and how to use it with a practical Node.js example: a web app plus a Redis cache, with health checks, persistent volumes, environment variables, and the everyday <code>docker compose</code> commands you will actually run.</p></section>
<section><h2 id="what-docker-compose-is"><a href="#what-docker-compose-is" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What Docker Compose is</h2><p>Docker Compose is a tool for defining and running multi-container Docker applications. It controls your whole stack — services, networks, and volumes — in a single YAML configuration file. With one command you create and start all the services from that file, instead of running many long <code>docker run</code> commands with dozens of flags.</p>
<p>Compose lets you:</p>
<ul>
<li>Start, stop, and rebuild services</li>
<li>View the status of running services</li>
<li>Stream the log output of running services</li>
<li>Run a one-off command on a service</li>
</ul>
<p>Compose is included with Docker Desktop (Windows and macOS) and with the Docker Engine on Linux, so after installing Docker you usually already have it. Verify with <code>docker compose version</code>.</p>
</section>
<section><h2 id="the-compose-file"><a href="#the-compose-file" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The Compose file</h2><p>The default file is <code>compose.yaml</code> in your project directory (older <code>docker-compose.yml</code> is also accepted). It is written in YAML, and its most important top-level elements are <code>services</code>, <code>volumes</code>, and <code>networks</code>:</p>
<ul>
<li><strong>services</strong> — the containers to run, each built from an image or a Dockerfile.</li>
<li><strong>volumes</strong> — named storage that persists data between container restarts.</li>
<li><strong>networks</strong> — how services communicate (Compose creates a default network for you).</li>
</ul>
<p>A minimal two-service Compose file for a Node.js app plus a Redis cache looks like this:</p>
<pre><code class="language-yaml">services:
  web:
    build: .
    ports:
      - &quot;3000:3000&quot;
    volumes:
      - .:/app
      - /app/node_modules
    depends_on:
      redis:
        condition: service_healthy
    environment:
      - REDIS_HOST=redis
      - REDIS_PORT=6379

  redis:
    image: redis:7-alpine
    healthcheck:
      test: [&quot;CMD&quot;, &quot;redis-cli&quot;, &quot;ping&quot;]
      interval: 5s
      timeout: 3s
      retries: 5
      start_period: 10s
</code></pre>
<p>Here the <code>web</code> service is built from the <code>Dockerfile</code> in the current directory (note <code>build: .</code>), while <code>redis</code> uses the public <code>redis:7-alpine</code> image pulled from Docker Hub. Every Compose project gets a default network, so <code>web</code> can reach <code>redis</code> by its service name.</p>
</section>
<section><h2 id="starting-the-stack"><a href="#starting-the-stack" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Starting the stack</h2><p>The command that creates and starts every service from your file is:</p>
<pre><code class="language-console">$ docker compose up
</code></pre>
<p>This builds the images that need building, pulls any remote images, and starts all containers. Running it in the foreground shows streaming logs from every service. To run in the background and free your terminal, add the detached flag:</p>
<pre><code class="language-console">$ docker compose up -d
</code></pre>
</section>
<section><h2 id="viewing-status-and-logs"><a href="#viewing-status-and-logs" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Viewing status and logs</h2><p>Check the state of all services:</p>
<pre><code class="language-console">$ docker compose ps
</code></pre>
<p>Follow log output from all services, color-coded by service name:</p>
<pre><code class="language-console">$ docker compose logs -f
</code></pre>
<p>To follow logs for one service only, pass its name:</p>
<pre><code class="language-console">$ docker compose logs -f web
</code></pre>
</section>
<section><h2 id="running-commands-inside-a-running-container"><a href="#running-commands-inside-a-running-container" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Running commands inside a running container</h2><p><code>docker compose exec</code> runs a command inside an already-running container — the primary tool for live debugging. For example, to check that your Node.js app can reach Redis, resolve the service name:</p>
<pre><code class="language-console">$ docker compose exec web node -e &quot;const r=require(&#39;redis&#39;);console.log(&#39;ok&#39;)&quot;
</code></pre>
<p>To confirm environment variables propagated correctly:</p>
<pre><code class="language-console">$ docker compose exec web env | grep REDIS
</code></pre>
</section>
<section><h2 id="environment-variables-with-env"><a href="#environment-variables-with-env" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Environment variables with .env</h2><p>Keep configuration out of the Compose file where possible. Compose automatically reads a <code>.env</code> file in the project directory and makes its values available for interpolation in your YAML. For example, with <code>.env</code> containing:</p>
<pre><code class="language-text">APP_PORT=3000
REDIS_HOST=redis
REDIS_PORT=6379
</code></pre>
<p>You can reference these in <code>compose.yaml</code> as <code>${APP_PORT}</code>, <code>${REDIS_HOST}</code>, and <code>${REDIS_PORT}</code>. Keeping values in <code>.env</code> makes it easier to change configuration across environments without editing YAML.</p>
</section>
<section><h2 id="health-checks-and-startup-order"><a href="#health-checks-and-startup-order" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Health checks and startup order</h2><p>Containers start in parallel, so a web app often tries to connect to a database before it is ready. A health check tells Compose when a service is truly ready, and <code>depends_on</code> with a condition makes one service wait for another.</p>
<p>The <code>healthcheck</code> block on <code>redis</code> runs <code>redis-cli ping</code> and expects a <code>PONG</code> reply. The <code>start_period</code> of 10 seconds lets Redis initialize before checks begin, and <code>interval</code>/<code>timeout</code>/<code>retries</code> control how often the check runs and how many failures are tolerated.</p>
<p><code>depends_on</code> with <code>condition: service_healthy</code> makes <code>web</code> wait until <code>redis</code> is confirmed healthy before starting. This fixes the classic startup race where the app connects before the cache is up.</p>
</section>
<section><h2 id="persisting-data-with-named-volumes"><a href="#persisting-data-with-named-volumes" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Persisting data with named volumes</h2><p>Container filesystems are ephemeral — when you remove a container, everything written inside is lost. A named volume keeps data on the host and reattaches it on the next <code>up</code>. Attach a volume with <code>volume-name:/container/path</code>:</p>
<pre><code class="language-yaml">services:
  redis:
    image: redis:7-alpine
    volumes:
      - redis-data:/data

volumes:
  redis-data:
</code></pre>
<p>The top-level <code>volumes</code> key registers <code>redis-data</code> so it persists across <code>docker compose down</code> and <code>up</code> cycles. This is how you keep a database or cache between restarts.</p>
</section>
<section><h2 id="compose-watch-for-live-reload"><a href="#compose-watch-for-live-reload" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Compose Watch for live reload</h2><p>Compose&#39;s <code>develop.watch</code> block gives live updates during development. Running <code>docker compose up --watch</code>, Compose watches files and applies rules you define:</p>
<pre><code class="language-yaml">services:
  web:
    build: .
    develop:
      watch:
        - action: sync+restart
          path: .
          target: /app
</code></pre>
<ul>
<li><code>sync+restart</code> copies changed files into the running container, then restarts it.</li>
<li><code>rebuild</code> triggers a full image rebuild — useful when you add a dependency (for example a new <code>package.json</code> entry) that requires reinstalling packages.</li>
</ul>
</section>
<section><h2 id="inspecting-config-before-it-runs"><a href="#inspecting-config-before-it-runs" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Inspecting config before it runs</h2><p><code>docker compose config</code> shows the fully resolved configuration without starting anything. It works purely from your files and is invaluable for debugging variable substitution and file merging — it shows environment variables replaced, port notation expanded into canonical fields, and default network and volume names made explicit.</p>
</section>
<section><h2 id="tearing-down-the-stack"><a href="#tearing-down-the-stack" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Tearing down the stack</h2><p>Stop and remove the containers (named volumes are kept):</p>
<pre><code class="language-console">$ docker compose down
</code></pre>
<p>Remove containers and named volumes too (this permanently deletes stored data — use intentionally):</p>
<pre><code class="language-console">$ docker compose down -v
</code></pre>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-is-the-difference-between-docker-and-docker-compose">What is the difference between Docker and Docker Compose?</h3><p>Docker builds and runs individual containers; Docker Compose orchestrates multiple containers as one application defined in a single YAML file. You use Docker to build and run one image, and Compose to define and run a whole stack of services, networks, and volumes together.</p>
<h3 id="do-i-need-a-separate-install-for-docker-compose">Do I need a separate install for Docker Compose?</h3><p>No. Docker Compose is included with Docker Desktop on Windows and macOS and with the Docker Engine on Linux. After installing Docker, verify it with <code>docker compose version</code>.</p>
<h3 id="what-does-docker-compose-up-do">What does <code>docker compose up</code> do?</h3><p>It reads your <code>compose.yaml</code>, builds any services with a <code>build:</code> directive, pulls any remote images, and starts all containers. Adding <code>-d</code> runs the stack in detached (background) mode; <code>--watch</code> enables live reload during development.</p>
<h3 id="how-do-i-keep-data-when-i-restart-a-container">How do I keep data when I restart a container?</h3><p>Use a named volume. Declare it in the top-level <code>volumes</code> key and mount it at the data path (for example <code>redis-data:/data</code>). Named volumes persist across <code>docker compose down</code> and <code>up</code>. To delete stored data as well, run <code>docker compose down -v</code>.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>How to configure DNS records: A, AAAA, CNAME, MX, and TXT explained</title>
      <link>https://debod.id/en/posts/dns-records-guide/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/dns-records-guide/</guid>
      <pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate>
      <category>web</category>
      <category>dns</category>
      <category>domain</category>
      <category>networking</category>
      <category>hosting</category>
      <description>Configure DNS records for your domain: learn what A, AAAA, CNAME, MX, and TXT records do, when to use each, and how to verify your changes propagate correctly.</description>
      <content:encoded><![CDATA[<section><p>Every domain name needs DNS records to work. When you type a URL, DNS records tell the browser where to find your website, where to deliver your email, and how to prove you own the domain. This guide explains the five DNS record types you will configure most often — A, AAAA, CNAME, MX, and TXT — with practical examples you can adapt to your own domain.</p></section>
<section><h2 id="what-dns-records-do"><a href="#what-dns-records-do" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What DNS records do</h2><p>DNS (Domain Name System) is the phone book of the internet. When you visit <code>example.com</code>, your browser asks a DNS resolver for the IP address of that domain. The resolver queries authoritative nameservers, which return DNS records that map your domain name to specific destinations: web servers, mail servers, verification tokens, or other domains.</p>
<p>Every DNS record has a <strong>type</strong> (what it does), a <strong>name</strong> (usually <code>@</code> for the root domain or a subdomain), a <strong>value</strong> (the destination), and a <strong>TTL</strong> (Time To Live — how long resolvers cache the record before asking again). Most hosting providers give you a DNS management panel where you add and edit these records.</p>
</section>
<section><h2 id="a-record-point-a-domain-to-an-ipv4-address"><a href="#a-record-point-a-domain-to-an-ipv4-address" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>A record — point a domain to an IPv4 address</h2><p>The A record maps a domain or subdomain to a <strong>32-bit IPv4 address</strong> (for example, <code>192.0.2.1</code>). This is the most common record type — it tells browsers the numeric address of your web server.</p>
<table>
<thead>
<tr>
<th>Field</th>
<th>Value</th>
<th>Notes</th>
</tr>
</thead>
<tbody><tr>
<td>Type</td>
<td>A</td>
<td></td>
</tr>
<tr>
<td>Name</td>
<td><code>@</code> (or <code>www</code>)</td>
<td><code>@</code> = root domain</td>
</tr>
<tr>
<td>Content</td>
<td><code>192.0.2.1</code></td>
<td>Your server&#39;s IPv4 address</td>
</tr>
<tr>
<td>Proxy status</td>
<td>Proxy (CDN) or DNS only</td>
<td>CDN recommended for performance</td>
</tr>
<tr>
<td>TTL</td>
<td>Auto or 300</td>
<td>Lower = faster propagation</td>
</tr>
</tbody></table>
<p><strong>Example — root domain to a server:</strong></p>
<pre><code>Type: A
Name: @
Content: 192.0.2.1
TTL: 300
</code></pre>
<p><strong>Example — www subdomain redirect:</strong></p>
<pre><code>Type: A
Name: www
Content: 192.0.2.1
TTL: 300
</code></pre>
<p>If your hosting provider gives you a dedicated IP address, use an A record. Many modern platforms (Cloudflare Pages, Netlify, Vercel) use <strong>CNAME records</strong> instead of A records.</p>
</section>
<section><h2 id="aaaa-record-point-a-domain-to-an-ipv6-address"><a href="#aaaa-record-point-a-domain-to-an-ipv6-address" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>AAAA record — point a domain to an IPv6 address</h2><p>The AAAA record (quad-A) maps a domain to a <strong>128-bit IPv6 address</strong> (for example, <code>2001:db8::1</code>). It works identically to an A record but for the newer IP addressing scheme. Most websites have both A and AAAA records so visitors on both IPv4 and IPv6 networks can reach the site.</p>
<p><strong>Example:</strong></p>
<pre><code>Type: AAAA
Name: @
Content: 2001:db8::1
TTL: 300
</code></pre>
<p>If your host does not provide an IPv6 address, you can skip the AAAA record for now. Both records are optional, but having both improves reachability.</p>
</section>
<section><h2 id="cname-record-alias-one-domain-to-another"><a href="#cname-record-alias-one-domain-to-another" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>CNAME record — alias one domain to another</h2><p>A CNAME record makes one domain name an <strong>alias</strong> for another. When a resolver looks up a CNAME, it gets the canonical name and then resolves that instead. CNAMEs are commonly used for subdomains that point to a third-party service.</p>
<table>
<thead>
<tr>
<th>Field</th>
<th>Value</th>
<th>Notes</th>
</tr>
</thead>
<tbody><tr>
<td>Type</td>
<td>CNAME</td>
<td></td>
</tr>
<tr>
<td>Name</td>
<td>subdomain</td>
<td>e.g. <code>www</code>, <code>api</code>, <code>blog</code></td>
</tr>
<tr>
<td>Content</td>
<td>target domain</td>
<td>e.g. <code>example.com</code> or <code>ghs.googlehosted.com</code></td>
</tr>
<tr>
<td>Proxy status</td>
<td>DNS only</td>
<td>CNAME cannot be proxied</td>
</tr>
<tr>
<td>TTL</td>
<td>Auto or 3600</td>
<td></td>
</tr>
</tbody></table>
<p><strong>Example — www pointing to root domain:</strong></p>
<pre><code>Type: CNAME
Name: www
Content: @
TTL: 3600
</code></pre>
<p><strong>Example — custom subdomain for a third-party service:</strong></p>
<pre><code>Type: CNAME
Name: crm
Content: salesforce.com
TTL: 3600
</code></pre>
<p>A CNAME <strong>cannot</strong> be set on the root domain (<code>@</code>) in most DNS systems. The root domain must use an A or AAAA record. This is a common mistake — if you try to create a CNAME on <code>@</code>, the DNS provider will reject it.</p>
</section>
<section><h2 id="mx-record-direct-email-to-a-mail-server"><a href="#mx-record-direct-email-to-a-mail-server" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>MX record — direct email to a mail server</h2><p>The MX (Mail Exchange) record tells mail servers <strong>where to deliver email</strong> sent to your domain. Unlike other records, MX records point to mail server hostnames, not IP addresses, and include a <strong>priority</strong> value.</p>
<p>Lower priority = higher preference. If you list multiple MX records, mail servers try the lowest number first and fall back to higher numbers if the first server is unavailable.</p>
<table>
<thead>
<tr>
<th>Field</th>
<th>Value</th>
<th>Notes</th>
</tr>
</thead>
<tbody><tr>
<td>Type</td>
<td>MX</td>
<td></td>
</tr>
<tr>
<td>Name</td>
<td><code>@</code></td>
<td>Must be root domain</td>
</tr>
<tr>
<td>Content</td>
<td>mail server hostname</td>
<td>e.g. <code>ASPMX.L.GOOGLE.COM</code></td>
</tr>
<tr>
<td>Priority</td>
<td>1, 5, 10, etc.</td>
<td>Lower = higher priority</td>
</tr>
<tr>
<td>TTL</td>
<td>Auto or 3600</td>
<td></td>
</tr>
</tbody></table>
<p><strong>Example — Google Workspace mail:</strong></p>
<pre><code>Type: MX
Name: @
Content: ASPMX.L.GOOGLE.COM
Priority: 1
TTL: 3600
</code></pre>
<p><strong>Example — multiple mail providers (backup):</strong></p>
<pre><code>Type: MX    Name: @    Content: mail.primary.com    Priority: 10
Type: MX    Name: @    Content: mail.backup.com    Priority: 20
</code></pre>
<p>MX records must always point to a hostname — never an IP address. If you are using a managed email service (Google Workspace, Microsoft 365, Proton Mail), your provider gives you specific MX values to enter.</p>
</section>
<section><h2 id="txt-record-verification-spf-dkim-and-dmarc"><a href="#txt-record-verification-spf-dkim-and-dmarc" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>TXT record — verification, SPF, DKIM, and DMARC</h2><p>TXT records store <strong>arbitrary text</strong> and serve several purposes: domain ownership verification, email authentication (SPF, DKIM, DMARC), and security records.</p>
<h3 id="domain-verification">Domain verification</h3><p>When you connect a domain to a service (Google Workspace, Microsoft 365, a CDN, or an SSL provider), the service asks you to add a TXT record to prove you control the domain.</p>
<pre><code>Type: TXT
Name: @
Content: google-site-verification=abc123xyz789
TTL: 3600
</code></pre>
<p>The TXT value is unique to your account. Copy it exactly — spaces and quotes matter.</p>
<h3 id="spf-specify-which-servers-can-send-email-from-your-domain">SPF — specify which servers can send email from your domain</h3><p>SPF (Sender Policy Framework) is a TXT record that lists the mail servers authorized to send email on behalf of your domain. Receiving mail servers check this to detect forged sender addresses.</p>
<pre><code>Type: TXT
Name: @
Content: v=spf1 include:_spf.google.com ~all
TTL: 3600
</code></pre>
<ul>
<li><code>v=spf1</code> — SPF version 1</li>
<li><code>include:_spf.google.com</code> — allow Google&#39;s servers to send mail</li>
<li><code>~all</code> — softfail (mail from other servers is suspicious but not rejected)</li>
</ul>
<p>For a simple setup using one email provider, the provider gives you the full SPF string. For complex setups (multiple providers), you add <code>include:</code> entries for each.</p>
<h3 id="dkim-and-dmarc">DKIM and DMARC</h3><p>DKIM (DomainKeys Identified Mail) and DMARC (Domain-based Message Authentication, Reporting and Conformance) are more advanced email authentication records. Many email providers set these up automatically or provide the exact records to add.</p>
<table>
<thead>
<tr>
<th>Record</th>
<th>Purpose</th>
</tr>
</thead>
<tbody><tr>
<td>SPF</td>
<td>Lists authorized sending servers</td>
</tr>
<tr>
<td>DKIM</td>
<td>Cryptographic signature proving the email was not altered</td>
</tr>
<tr>
<td>DMARC</td>
<td>Tells receivers what to do with SPF/DKIM failures</td>
</tr>
</tbody></table>
</section>
<section><h2 id="dnssec-signing-your-dns-data"><a href="#dnssec-signing-your-dns-data" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>DNSSEC — signing your DNS data</h2><p>DNSSEC adds cryptographic signatures to your DNS records so resolvers can verify the records have not been tampered with in transit. When you enable DNSSEC, your domain registrar publishes a DS (Delegation Signer) record in the parent zone.</p>
<p><strong>Note:</strong> If your DNS provider supports DNSSEC, enable it in the DNS provider&#39;s dashboard — the registrar then needs the DS record information. Cloudflare, Google Cloud DNS, and Route 53 all support DNSSEC.</p>
</section>
<section><h2 id="how-to-verify-your-dns-changes"><a href="#how-to-verify-your-dns-changes" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How to verify your DNS changes</h2><p>DNS changes are not instant. After you update a record, it can take <strong>anywhere from a few seconds to 48 hours</strong> to propagate globally, depending on the TTL of the old record and the resolver&#39;s cache. The average is 5–30 minutes.</p>
<p><strong>Check propagation with a DNS lookup tool:</strong></p>
<ul>
<li><code>dig A example.com</code> — query A record from your default resolver</li>
<li><code>dig @1.1.1.1 A example.com</code> — query Cloudflare&#39;s resolver directly</li>
<li><code>dig @8.8.8.8 A example.com</code> — query Google&#39;s resolver</li>
<li>Online tools like <code>dnschecker.org</code> show how your records resolve from resolvers worldwide</li>
</ul>
<p><strong>Check email DNS:</strong></p>
<ul>
<li><code>dig MX example.com</code> — verify MX records are pointing to the right servers</li>
<li><code>dig TXT example.com</code> — verify SPF, DKIM, and verification TXT records</li>
</ul>
<p><strong>Flush your local cache:</strong>
If your browser or computer is still resolving the old value, flush the local DNS cache:</p>
<ul>
<li>macOS: <code>sudo dscacheutil -flushcache; sudo killall -HUP mDNSResponder</code></li>
<li>Windows: <code>ipconfig /flushdns</code></li>
<li>Linux: <code>systemd-resolve --flush-caches</code> or <code>service nscd restart</code></li>
</ul>
</section>
<section><h2 id="common-mistakes-to-avoid"><a href="#common-mistakes-to-avoid" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Common mistakes to avoid</h2><ul>
<li><strong>CNAME on root domain (@)</strong> — Most DNS providers block this because RFC 1034 prohibits it. Use an A/AAAA record instead.</li>
<li><strong>MX records pointing to IP addresses</strong> — MX must point to hostnames, not IP addresses.</li>
<li><strong>Forgetting the trailing dot</strong> — In some DNS UIs, values ending in a dot (<code>mail.example.com.</code>) are fully qualified and not relative to the domain. Check your provider&#39;s format rules.</li>
<li><strong>Conflicting records</strong> — Having both an A record and a CNAME on the same name causes conflicts. One name, one canonical record.</li>
<li><strong>Setting TTL too high</strong> — When planning a migration, lower your TTL to 300 seconds 24 hours in advance so the change propagates faster.</li>
<li><strong>TXT records with quotes</strong> — Enter the TXT value without surrounding quotes in most DNS UIs. The quotes are part of the zone file format, not the record value.</li>
</ul>
</section>
<section><h2 id="summary"><a href="#summary" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Summary</h2><table>
<thead>
<tr>
<th>Record</th>
<th>Points to</th>
<th>Use when</th>
</tr>
</thead>
<tbody><tr>
<td>A</td>
<td>IPv4 address</td>
<td>Your server has a static IP</td>
</tr>
<tr>
<td>AAAA</td>
<td>IPv6 address</td>
<td>Your server supports IPv6</td>
</tr>
<tr>
<td>CNAME</td>
<td>Another domain name</td>
<td>Subdomain points to third-party service</td>
</tr>
<tr>
<td>MX</td>
<td>Mail server hostname</td>
<td>Setting up email delivery</td>
</tr>
<tr>
<td>TXT</td>
<td>Arbitrary text</td>
<td>Verification, SPF, DKIM, DMARC</td>
</tr>
</tbody></table>
<p>Configure these five record types and you can set up virtually any domain for web hosting, email, and third-party services. Always verify changes with <code>dig</code> or an online DNS checker, and plan TTL changes before migrations to minimize downtime.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="how-long-does-it-take-for-dns-changes-to-propagate">How long does it take for DNS changes to propagate?</h3><p>Most changes take 5–30 minutes to reach the majority of resolvers globally. The old record&#39;s TTL and the resolver&#39;s cache behavior determine the actual time. After 48 hours, virtually all resolvers will have the new record.</p>
<h3 id="can-i-have-both-a-and-cname-records-for-the-same-subdomain">Can I have both A and CNAME records for the same subdomain?</h3><p>No. A name cannot be both a canonical record (A/AAAA) and an alias (CNAME) at the same time. If you need a CNAME on a subdomain, do not also create an A record on that same name.</p>
<h3 id="should-i-use-cloudflare-proxy-orange-cloud-with-my-a-record">Should I use Cloudflare proxy (orange cloud) with my A record?</h3><p>For web traffic, yes — Cloudflare proxy adds CDN caching, DDoS protection, and performance optimization. However, MX records should <strong>never</strong> be proxied. DNS-only MX records are required for email delivery to work correctly.</p>
<h3 id="what-is-the-difference-between-ttl-and-propagation">What is the difference between TTL and propagation?</h3><p>TTL (Time To Live) is a setting on each DNS record telling resolvers how many seconds to cache it before asking the authoritative nameserver again. Propagation is the informal term for the time it takes all resolvers worldwide to fetch the updated record after you change it.</p>
<h3 id="do-i-need-to-set-up-dnssec">Do I need to set up DNSSEC?</h3><p>DNSSEC is recommended for production domains. It prevents DNS spoofing attacks where an attacker redirects your visitors to a malicious server. Most DNS providers make it a one-click toggle once your registrar supports DS records.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>How to deploy a static site to Cloudflare Pages: step-by-step</title>
      <link>https://debod.id/en/posts/deploy-static-site-cloudflare-pages/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/deploy-static-site-cloudflare-pages/</guid>
      <pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate>
      <category>web</category>
      <category>hosting</category>
      <category>devops</category>
      <description>Deploy a static site to Cloudflare Pages free: connect your Git repo, set build command and output directory, add a custom domain, and understand the free-plan limits.</description>
      <content:encoded><![CDATA[<section><p>Cloudflare Pages is a free static website hosting and deployment platform that runs on Cloudflare&#39;s global edge network. After you connect a Git repository, Pages builds your site and publishes the output to hundreds of data centers worldwide, with HTTPS, a CDN, and global load-balancing included at no cost. This guide walks through the two deployment paths — Git integration for automatic rebuilds, and Direct Upload for prebuilt assets — plus the free-plan limits you should plan around.</p></section>
<section><h2 id="why-deploy-to-cloudflare-pages"><a href="#why-deploy-to-cloudflare-pages" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Why deploy to Cloudflare Pages</h2><p>A static site is a collection of fixed HTML, CSS, and JavaScript files — no server, no database, no runtime. Because everything is prebuilt, a static host only needs to store files and serve them fast. Cloudflare Pages does exactly that, serving your files from its global network with no bandwidth charges. The free plan removes the usual cost of hosting, which is why Pages is a common home for blogs, documentation, and marketing sites built with frameworks such as Next.js (static export), Eleventy, Hugo, Astro, or plain HTML.</p>
<p>Pages gives you a clean workflow: commit code, push to Git, and a new version goes live automatically. Every deployment is versioned, so you can roll back to a previous build if something breaks.</p>
</section>
<section><h2 id="path-1-deploy-from-a-git-repository"><a href="#path-1-deploy-from-a-git-repository" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Path 1: Deploy from a Git repository</h2><p>Git integration is the recommended path when you want changes to publish automatically on every push. Pages supports both GitHub and GitLab.</p>
<ol>
<li>Build your site locally and confirm the build command and output directory for your framework. For a Next.js static export the build command is <code>npx next build</code> and the output directory is <code>out</code>. For a Vite React app it is <code>npm run build</code> and <code>dist</code>. A plain HTML site with no build step can leave the build command blank.</li>
<li>In the Cloudflare dashboard, open the <strong>Workers &amp; Pages</strong> page and select <strong>Create application</strong> → <strong>Pages</strong> → <strong>Connect to Git</strong>.</li>
<li>Sign in with your Git provider and choose the repository to deploy. Both public and private repositories are supported.</li>
<li>In <strong>Set up builds and deployments</strong>, enter your <strong>project name</strong> (it becomes <code>&lt;project&gt;.pages.dev</code>) and choose your <strong>production branch</strong> — usually <code>main</code> or <code>master</code>. The production branch serves your live site; other branches become preview deployments.</li>
<li>Set your <strong>build command</strong> and <strong>build output directory</strong> from the framework preset, or leave the build command blank for a site with no build step.</li>
<li>Optionally add <strong>environment variables</strong> for the build, then select <strong>Save and Deploy</strong>.</li>
</ol>
<p>Pages runs the install and build steps, then uploads the output to its edge network. When a build process returns a non-zero exit code, Pages marks it as failed; an exit code of 0 means the build succeeded and the assets are uploaded. After the first deploy you get a URL like <code>&lt;project&gt;.pages.dev</code> for your live site, and Cloudflare injects system build variables such as <code>CF_PAGES</code> (set to <code>1</code>) and <code>CF_PAGES_BRANCH</code> so builds can detect they are running on Pages.</p>
</section>
<section><h2 id="path-2-direct-upload-with-prebuilt-assets"><a href="#path-2-direct-upload-with-prebuilt-assets" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Path 2: Direct Upload with prebuilt assets</h2><p>Choose Direct Upload when you build elsewhere — at a separate CI platform or on your own machine — and only want Cloudflare Pages to host the finished files. Assets are uploaded either through the Wrangler CLI or by dragging and dropping in the dashboard.</p>
<p>With Wrangler:</p>
<ol>
<li>Log in with <code>wrangler login</code>, then create a project: <code>npx wrangler pages project create</code>.</li>
<li>Deploy your build output: <code>npx wrangler pages deploy &lt;BUILD_OUTPUT_DIRECTORY&gt;</code>.</li>
<li>Your production site is served at <code>&lt;project&gt;.pages.dev</code>. To deploy a preview, pass <code>--branch=&lt;BRANCH_NAME&gt;</code> and the branch alias becomes available at <code>&lt;branch&gt;.&lt;project&gt;.pages.dev</code>.</li>
</ol>
<p>With drag and drop, open <strong>Workers &amp; Pages</strong> → <strong>Create application</strong> → <strong>Get started</strong> → <strong>Drag and drop your files</strong>, name the project, and upload a folder (or a zip) of your assets. Drag and drop is limited to 1,000 files, while Wrangler supports up to 20,000 files per project.</p>
<p>Direct Upload projects do not support the <code>functions</code> folder via the dashboard drag and drop — to deploy a <code>functions</code> folder you must use Wrangler.</p>
</section>
<section><h2 id="adding-a-custom-domain"><a href="#adding-a-custom-domain" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Adding a custom domain</h2><p>After your site is live at <code>&lt;project&gt;.pages.dev</code>, attach your own domain from the project&#39;s <strong>Custom domains</strong> settings. Cloudflare can issue an SSL/TLS certificate automatically, and the site continues to serve over HTTPS through the edge. The free plan allows up to 100 custom domains per project.</p>
</section>
<section><h2 id="free-plan-limits-to-plan-around"><a href="#free-plan-limits-to-plan-around" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Free plan limits to plan around</h2><p>Cloudflare Pages free plan limits (as documented by Cloudflare):</p>
<table>
<thead>
<tr>
<th>Resource</th>
<th>Free plan limit</th>
</tr>
</thead>
<tbody><tr>
<td>Files per site</td>
<td>20,000</td>
</tr>
<tr>
<td>Builds per month</td>
<td>500</td>
</tr>
<tr>
<td>Concurrent builds</td>
<td>1</td>
</tr>
<tr>
<td>Build timeout</td>
<td>20 minutes</td>
</tr>
<tr>
<td>Maximum file size</td>
<td>25 MiB</td>
</tr>
<tr>
<td>Custom domains per project</td>
<td>100</td>
</tr>
<tr>
<td>Projects per account</td>
<td>100</td>
</tr>
</tbody></table>
<p>Bandwidth and requests are unlimited on the free plan, which is why Pages suits a high-traffic static site without surprise bills. The practical constraint is the file count (20,000 per site) and the monthly build budget (500 builds). A large documentation site that publishes often can approach those numbers; a typical blog stays far below them.</p>
</section>
<section><h2 id="what-about-dynamic-features"><a href="#what-about-dynamic-features" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What about dynamic features?</h2><p>Pages also supports Pages Functions, which let you run server-side code for dynamic behavior such as handling forms or reading cookies, with requests counting toward your Workers quota. On the free plan this quota is modest, so most users keep dynamic work minimal and lean on the static asset hosting, which is effectively free.</p>
</section>
<section><h2 id="summary"><a href="#summary" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Summary</h2><p>Cloudflare Pages turns a static site into a fast, globally-cached deployment with no hosting bill. Connect a Git repository for automatic rebuilds, or use Direct Upload for manual builds. Set a custom domain, and keep the free-plan limits in mind — especially the 20,000-file and 500-builds-per-month caps. For purely static output, Pages is hard to beat on price and performance.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="is-cloudflare-pages-free">Is Cloudflare Pages free?</h3><p>Yes. The free plan provides 20,000 files per site, 500 builds per month, and unlimited bandwidth. Paid plans raise the file limit to 100,000 per site and increase monthly builds.</p>
<h3 id="what-is-the-difference-between-cloudflare-pages-and-cloudflare-workers">What is the difference between Cloudflare Pages and Cloudflare Workers?</h3><p>Pages is static hosting with optional Pages Functions; Workers is a serverless execution platform where you write code that runs at the edge. They are related products, and Pages Functions requests count toward your Workers quota. Many static sites use Pages alone, without Workers.</p>
<h3 id="can-i-use-cloudflare-pages-without-github-or-gitlab">Can I use Cloudflare Pages without GitHub or GitLab?</h3><p>Yes. Direct Upload lets you upload prebuilt assets with Wrangler or drag-and-drop without any Git provider. You lose automatic rebuilds on push, but still get the same edge hosting and HTTPS.</p>
<h3 id="does-cloudflare-pages-support-next-js">Does Cloudflare Pages support Next.js?</h3><p>Yes. Next.js can be deployed as a static HTML export using <code>npx next build</code> with the <code>out</code> output directory, or via the pages adapter for full-stack deployments. For a purely static site, the static export path needs no server runtime.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>What is a database index? How database indexes work and when to use them</title>
      <link>https://debod.id/en/posts/database-index-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/database-index-explained/</guid>
      <pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate>
      <category>database</category>
      <category>database</category>
      <category>sql</category>
      <category>performance</category>
      <description>A database index is a data structure that lets a database find rows without scanning the whole table, usually a balanced tree (B-tree) ordered by the indexed columns. It makes equality, range, sort and join queries fast, at the cost of write overhead and storage. This guide explains how indexes work, clustered vs non-clustered, and when (and when not) to create one.</description>
      <content:encoded><![CDATA[<section><p>A database index is a separate data structure — usually a balanced tree, most commonly a <strong>B-tree</strong> — that stores a sorted copy of the indexed columns together with a pointer to the actual row. When you look up a row by an indexed column, the database descends the tree in a handful of steps instead of reading every row of the table. The trade-off is real: every <code>INSERT</code>, <code>UPDATE</code>, and <code>DELETE</code> must also maintain every index on the table, so indexes cost write speed and disk space in exchange for faster reads. This guide walks through how indexes work, the clustered vs non-clustered distinction, and when an index is worth creating — and when it is not.</p></section>
<section><h2 id="what-happens-without-an-index"><a href="#what-happens-without-an-index" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What happens without an index</h2><p>Imagine a <code>users</code> table with one million rows and a query that fetches a single row by email:</p>
<pre><code class="language-sql">SELECT * FROM users WHERE email = &#39;ada@example.com&#39;;
</code></pre>
<p>Without an index on <code>email</code>, the database cannot know where the matching row is, so it must read the table from the first row to the last and compare <code>email</code> on every row until it finds a match. This is called a <strong>full table scan</strong>, and on a large table it means reading (potentially) a million rows. A full scan is correct but slow, and it becomes useless the moment the table grows.</p>
<p>An index changes this: it keeps the <code>email</code> values sorted in their own structure, with a pointer from each value back to the corresponding row. To satisfy the query, the database searches the sorted index, finds <code>ada@example.com</code>, follows the pointer, and reads only that one row. Instead of a million comparisons, the lookup takes roughly the depth of the tree — a few dozen steps even for huge tables.</p>
</section>
<section><h2 id="how-a-b-tree-index-works"><a href="#how-a-b-tree-index-works" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How a B-tree index works</h2><p>Most relational databases index with a <strong>B-tree</strong> — more precisely a B+ tree variant. A B-tree is a <em>multi-way balanced tree</em>: a root node points down to branch nodes, branch nodes point down to leaf nodes, and the <strong>leaf level holds the index keys in sorted order together with a pointer to each row</strong>. &quot;Balanced&quot; means every leaf sits at the same shallow distance from the root, so any value — common or rare — is found in the same small number of steps regardless of how many rows there are.</p>
<p>Because the leaves are sorted, a B-tree index accelerates more than exact equality lookups:</p>
<ul>
<li><strong>Equality:</strong> <code>WHERE email = &#39;x&#39;</code></li>
<li><strong>Range:</strong> <code>WHERE age BETWEEN 18 AND 25</code>, <code>WHERE price &lt; 100</code></li>
<li><strong>Prefix search:</strong> <code>WHERE name LIKE &#39;Ada%&#39;</code> (a leading wildcard, <code>&#39;%da&#39;</code>, cannot use the index)</li>
<li><strong>Ordering:</strong> <code>ORDER BY created_at</code></li>
<li><strong>Joins:</strong> matching rows between two tables on the indexed column</li>
</ul>
<p>PostgreSQL&#39;s documentation notes that any data type that can be sorted into a well-defined linear order can be indexed by a B-tree — which is why it is the default index type and covers the vast majority of use cases.</p>
</section>
<section><h2 id="clustered-vs-non-clustered-indexes"><a href="#clustered-vs-non-clustered-indexes" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Clustered vs non-clustered indexes</h2><p>The single most important distinction in indexing is <strong>clustered vs non-clustered</strong>, and it comes down to where the data actually lives.</p>
<p><strong>Clustered index.</strong> The leaf level of a clustered index <em>is</em> the table data — the rows are physically stored on disk in the order of the index keys. Clustered indexes are great for range queries and ordered reads, because once you find the start of a range, the rest of the matching rows are physically adjacent. But because the rows can be laid out in only one order, a table can have <strong>at most one clustered index</strong>. In MySQL&#39;s InnoDB engine the primary key is always the clustered index; SQL Server clusters the primary key by default.</p>
<p><strong>Non-clustered index.</strong> A non-clustered index is a completely separate B-tree that stores the sorted key columns plus a pointer back to the row — either a physical row locator or, in clustered-table engines, the clustered key value. The underlying table rows stay in their own natural order, so a table can have many non-clustered indexes, each tuned to a different query pattern.</p>
<p>In practice a typical table has one clustered index (often the primary key) and several non-clustered indexes for the queries that matter most.</p>
</section>
<section><h2 id="composite-and-covering-indexes"><a href="#composite-and-covering-indexes" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Composite and covering indexes</h2><p>You are not limited to a single column. A <strong>composite index</strong> covers several columns in a fixed order, for example <code>(last_name, first_name)</code>. Composite indexes obey the <strong>leftmost-prefix rule</strong>: a query can use the index when it filters on the first column, or the first <em>and</em> second, but not on the second column alone. So an index on <code>(last_name, first_name)</code> helps <code>WHERE last_name = &#39;Lovelace&#39;</code> and <code>WHERE last_name = &#39;Lovelace&#39; AND first_name = &#39;Ada&#39;</code>, but not <code>WHERE first_name = &#39;Ada&#39;</code> on its own.</p>
<p>A <strong>covering index</strong> (in SQL Server, an index with <em>included columns</em>) holds every column the query needs inside the index itself, so the database can answer the query without ever going back to the table — a &quot;covering&quot; or index-only scan. This is the fastest possible read, at the cost of a wider index.</p>
</section>
<section><h2 id="the-trade-offs-writes-storage-and-maintenance"><a href="#the-trade-offs-writes-storage-and-maintenance" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The trade-offs: writes, storage, and maintenance</h2><p>Indexes are not free. Every index on a table must be updated on every write to that table, so:</p>
<ul>
<li><strong>Write overhead.</strong> Each <code>INSERT</code>, <code>UPDATE</code>, and <code>DELETE</code> now has to maintain every index along with the table. A table with many indexes slows down writes and can slow down transactions.</li>
<li><strong>Storage.</strong> Each index is its own structure on disk. A wide or covering index can rival the table itself in size.</li>
<li><strong>Selectivity.</strong> An index only pays off when the query is selective — when a value matches a small fraction of rows. Indexing a column where most rows share the same value (like a boolean that is true 99% of the time) rarely helps, because the optimizer may decide that scanning the table is cheaper.</li>
<li><strong>Function calls.</strong> Wrapping the indexed column in a function, as in <code>WHERE LOWER(email) = &#39;ada@example.com&#39;</code>, usually prevents the index from being used unless a functional index exists for that expression.</li>
</ul>
<p>The practical guideline is to index the columns that appear in your most important <code>WHERE</code>, <code>JOIN</code>, and <code>ORDER BY</code> clauses, keep the number of indexes modest, and drop indexes that queries never use.</p>
</section>
<section><h2 id="how-to-verify-an-index-is-working"><a href="#how-to-verify-an-index-is-working" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How to verify an index is working</h2><p>After creating an index, confirm the query planner actually uses it. In PostgreSQL and MySQL you prefix a query with <code>EXPLAIN ANALYZE</code> (or just <code>EXPLAIN</code>) and read the plan:</p>
<pre><code class="language-sql">EXPLAIN ANALYZE SELECT * FROM users WHERE email = &#39;ada@example.com&#39;;
</code></pre>
<p>The plan shows whether the engine chose an <strong>index scan</strong> or a <strong>sequential scan</strong> (table scan). If a query you expect to be fast is still doing a sequential scan, the index may not be selective enough, or the query may be written in a way the index cannot serve (for example, a leading wildcard or a function call on the column).</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-is-the-difference-between-a-clustered-and-a-non-clustered-index">What is the difference between a clustered and a non-clustered index?</h3><p>A clustered index determines the physical order in which a table&#39;s rows are stored on disk — its leaf level <em>is</em> the data — and a table can have only one. A non-clustered index is a separate sorted structure that stores the key columns plus a pointer back to the row, and a table can have many of them. Clustered indexes suit range and ordered reads; non-clustered indexes support many different query patterns on the same table.</p>
<h3 id="why-do-database-indexes-slow-down-writes">Why do database indexes slow down writes?</h3><p>Every time a row is inserted, updated, or deleted, the database must also update every index that covers the affected columns. Indexes speed up reads by trading write cost and storage for that speed — the more indexes a table has, the more work each write has to do.</p>
<h3 id="when-should-i-not-create-an-index">When should I not create an index?</h3><p>Avoid an index when the column has low selectivity (most rows share the same value, so the query still matches most of the table), when the table is tiny and a full scan is already fast, or when the column is rarely used in <code>WHERE</code>, <code>JOIN</code>, or <code>ORDER BY</code>. Too many unused or low-value indexes add write overhead and consume storage without helping reads.</p>
<h3 id="why-is-my-index-not-being-used">Why is my index not being used?</h3><p>Common reasons the optimizer skips an index: the query is not selective enough (matching too large a fraction of rows), the column is wrapped in a function (<code>WHERE LOWER(email) = ...</code>), a <code>LIKE</code> pattern starts with a wildcard (<code>&#39;%ada&#39;</code>), or the query filters on a column that is not the leftmost column of a composite index. Running <code>EXPLAIN</code> shows whether the planner chose an index scan or a sequential scan.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>What is CSS Specificity? Selectors, Specificity, and the Cascade Explained</title>
      <link>https://debod.id/en/posts/css-specificity-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/css-specificity-explained/</guid>
      <pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate>
      <category>frontend</category>
      <category>web</category>
      <category>frontend</category>
      <description>CSS specificity is the algorithm browsers use to pick which CSS rule wins when two declarations target the same element and property. This guide explains selectors, specificity calculation, the cascade, and inheritance — with practical examples.</description>
      <content:encoded><![CDATA[<section><p>When two CSS rules target the same element and the same property, only one can win. The browser uses the <strong>cascade algorithm</strong> — which evaluates origin, importance, specificity, and source order — to decide which declaration applies. <strong>Specificity</strong> is the selector-weight part of that algorithm: the more specific your selector, the less likely another rule will override it.</p>
<p>The <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Cascade/Specificity">MDN specificity guide</a> is explicit: browsers evaluate specificity <em>after</em> determining cascade origin and importance, and it is compared only between selectors from the one cascade origin and layer that has precedence. This guide walks through every part of the system: how selectors work, how specificity is calculated, how the cascade resolves conflicts, and how inheritance fits in.</p></section>
<section><h2 id="css-selectors"><a href="#css-selectors" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>CSS Selectors</h2><p>A CSS selector is the pattern that tells the browser which HTML elements a rule applies to. The <a href="https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/Styling_basics/Basic_selectors">MDN basic selectors lesson</a> describes it as the first part of a CSS rule — a pattern of elements and terms that identifies the <em>subject</em> of the selector. Selectors range from trivially simple to precisely targeted.</p>
<h3 id="type-class-and-id-selectors">Type, Class, and ID Selectors</h3><p>A <strong>type selector</strong> (also called tag-name or element selector) targets an HTML tag directly:</p>
<pre><code class="language-css">p { color: blue; }
</code></pre>
<p>A <strong>class selector</strong> starts with a dot (<code>.</code>) and targets every element with that class. Multiple classes can be chained without a space — <code>p.highlight</code> targets only <code>&lt;p class=&quot;highlight&quot;&gt;</code> — and an element can carry several classes, each independently styleable. The <a href="https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/Styling_basics/Basic_selectors">MDN lesson</a> gives the example of a <code>.notebox</code> with additional <code>.warning</code> or <code>.danger</code> classes applied to the same element for layered styling.</p>
<p>An <strong>ID selector</strong> starts with <code>#</code> and targets exactly one element per page, since <code>id</code> must be unique in a document. Using the same ID twice produces invalid HTML. The MDN lesson warns: &quot;Using the same ID multiple times in a document may appear to work for styling purposes, but don&#39;t do this. It results in invalid code and will cause strange behavior in many places.&quot;</p>
<h3 id="attribute-selectors">Attribute Selectors</h3><p>Attribute selectors target elements based on the presence or value of an attribute. The <a href="https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/Styling_basics/Attribute_selectors">MDN attribute selectors lesson</a> covers two groups.</p>
<p>Presence and value selectors:</p>
<table>
<thead>
<tr>
<th>Selector</th>
<th>Example</th>
<th>Description</th>
</tr>
</thead>
<tbody><tr>
<td><code>[attr]</code></td>
<td><code>a[title]</code></td>
<td>Elements with that attribute</td>
</tr>
<tr>
<td><code>[attr=value]</code></td>
<td><code>a[href=&quot;https://example.com&quot;]</code></td>
<td>Exact value match</td>
</tr>
<tr>
<td><code>[attr~=value]</code></td>
<td><code>p[class~=&quot;special&quot;]</code></td>
<td>Value in space-separated list</td>
</tr>
<tr>
<td><code>[attr|=value]</code></td>
<td><code>div[lang|=&quot;zh&quot;]</code></td>
<td>Exact value or value-prefixed with hyphen</td>
</tr>
</tbody></table>
<p>Substring (pattern) selectors:</p>
<table>
<thead>
<tr>
<th>Selector</th>
<th>Example</th>
<th>Description</th>
</tr>
</thead>
<tbody><tr>
<td><code>[attr^=value]</code></td>
<td><code>li[class^=&quot;box-&quot;]</code></td>
<td>Value starts with</td>
</tr>
<tr>
<td><code>[attr$=value]</code></td>
<td><code>li[class$=&quot;-box&quot;]</code></td>
<td>Value ends with</td>
</tr>
<tr>
<td><code>[attr*=value]</code></td>
<td><code>li[class*=&quot;box&quot;]</code></td>
<td>Value contains anywhere</td>
</tr>
</tbody></table>
<h3 id="combinators">Combinators</h3><p>Combinators combine two selectors to express a relationship in the DOM tree. The <a href="https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/Styling_basics/Combinators">MDN combinators lesson</a> defines four:</p>
<ul>
<li><strong>Descendant</strong> (space): <code>article p</code> — any <code>&lt;p&gt;</code> inside <code>&lt;article&gt;</code>, at any depth.</li>
<li><strong>Child</strong> (<code>&gt;</code>): <code>article &gt; p</code> — <code>&lt;p&gt;</code> that is a direct child of <code>&lt;article&gt;</code> only.</li>
<li><strong>Next-sibling</strong> (<code>+</code>): <code>h1 + p</code> — the <code>&lt;p&gt;</code> immediately after an <code>&lt;h1&gt;</code>.</li>
<li><strong>Subsequent-sibling</strong> (<code>~</code>): <code>h1 ~ p</code> — every <code>&lt;p&gt;</code> that follows an <code>&lt;h1&gt;</code> at the same level.</li>
</ul>
<h3 id="pseudo-classes-and-pseudo-elements">Pseudo-Classes and Pseudo-Elements</h3><p><strong>Pseudo-classes</strong> start with a single colon and target elements in a particular <em>state</em> — <code>:hover</code>, <code>:focus</code>, <code>:first-child</code>. The <a href="https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/Styling_basics/Pseudo_classes_and_elements">MDN lesson</a> describes them as acting &quot;as if you had applied a class to some part of your document.&quot;</p>
<p><strong>Pseudo-elements</strong> start with a double colon and act &quot;as if you had added a whole new element to the DOM.&quot; <code>::before</code> and <code>::after</code> are used with the <code>content</code> property to insert generated content. <code>::first-line</code> styles the first line of an element reliably regardless of how the browser wraps text.</p>
</section>
<section><h2 id="the-cascade"><a href="#the-cascade" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The Cascade</h2><p>The cascade resolves conflicts when multiple CSS declarations compete for the same element and property. The <a href="https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/Styling_basics/Handling_conflicts">MDN handling conflicts lesson</a> lists the three factors — listed here in order of increasing importance:</p>
<ol>
<li><strong>Source order</strong> — later rules override earlier ones when everything else is equal.</li>
<li><strong>Specificity</strong> — more specific selectors win over less specific ones.</li>
<li><strong>Importance</strong> — declarations marked <code>!important</code> override normal declarations.</li>
</ol>
<h3 id="cascade-origin-and-layers">Cascade Origin and Layers</h3><p>Before specificity matters, the cascade origin determines which source of styles takes priority. The MDN lesson gives this complete precedence order (later over earlier):</p>
<ol>
<li>User agent stylesheets (browser default styles)</li>
<li>Normal user stylesheets (user-defined preferences)</li>
<li>Normal author stylesheets (your CSS)</li>
<li>Important author stylesheets (<code>!important</code> in your CSS)</li>
<li>Important user stylesheets</li>
<li>Important user agent stylesheets</li>
</ol>
<p>The reversal for <code>!important</code> means users with accessibility needs (for example, larger text) can override author styles. The <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Cascade/Specificity">MDN specificity reference</a> notes that origin and importance are evaluated <em>before</em> specificity — meaning an <code>!important</code> declaration from the author wins over a non-<code>!important</code> ID selector from the same author.</p>
<p>Inline styles (the <code>style</code> attribute in HTML) take precedence over all normal styles, but not over <code>!important</code>. The MDN specificity reference gives inline styles a specificity of <code>1-0-0-0</code> (ID column only).</p>
<h3 id="the-important-flag">The <code>!important</code> Flag</h3><p>Marking a declaration with <code>!important</code> flips its priority in the cascade:</p>
<pre><code class="language-css">.override {
  border: none !important; /* overrides an ID selector&#39;s border */
}
</code></pre>
<p>The MDN lesson advises caution: &quot;we strongly recommend that you never use <code>!important</code> unless you absolutely have to. The <code>!important</code> flag changes the way the cascade normally works, so it can make debugging CSS problems really hard to work out, especially in a large stylesheet.&quot; One legitimate use is working within a CMS where you cannot edit the core stylesheet.</p>
</section>
<section><h2 id="how-specificity-is-calculated"><a href="#how-specificity-is-calculated" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How Specificity Is Calculated</h2><p>Specificity is a three-column value written as <strong>ID – CLASS – TYPE</strong> (or sometimes presented as three numbers). The <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Cascade/Specificity">MDN specificity reference</a> defines each column:</p>
<ul>
<li><strong>ID</strong> — counts ID selectors (<code>#example</code>). Each adds <code>1-0-0</code>.</li>
<li><strong>CLASS</strong> — counts class selectors (<code>.myClass</code>), attribute selectors (<code>[type=&quot;radio&quot;]</code>), and pseudo-classes (<code>:hover</code>, <code>:nth-of-type(3n)</code>). Each adds <code>0-1-0</code>.</li>
<li><strong>TYPE</strong> — counts type selectors (<code>p</code>, <code>h1</code>, <code>td</code>) and pseudo-elements (<code>::before</code>, <code>::first-line</code>). Each adds <code>0-0-1</code>.</li>
</ul>
<p>The universal selector (<code>*</code>) and <code>:where()</code> add <code>0-0-0</code> — they match elements but do not affect specificity. Combinators (<code>&gt;</code>, <code>+</code>, <code>~</code>, space) add no specificity weight either; only the selectors they connect count.</p>
<h3 id="the-cascade-order-within-specificity">The Cascade Order Within Specificity</h3><p>The three columns are compared <strong>column by column from left to right</strong>. The MDN specificity reference gives this hierarchy:</p>
<ol>
<li>The ID column is compared first. A selector with one ID always beats any number of classes or types — regardless of how many classes or elements the other selector has.</li>
<li>If the ID count is equal, the CLASS column is compared.</li>
<li>If the CLASS count is also equal, the TYPE column is compared.</li>
<li>If all three columns are equal, source order breaks the tie — the later rule wins.</li>
</ol>
<p>The MDN lesson illustrates this with a well-known rule: &quot;a <em>million</em> class selectors combined would not be able to overwrite the specificity of <em>one</em> ID selector.&quot;</p>
<h3 id="specificity-examples">Specificity Examples</h3><table>
<thead>
<tr>
<th>Selector</th>
<th>IDs</th>
<th>Classes</th>
<th>Types</th>
<th>Specificity</th>
</tr>
</thead>
<tbody><tr>
<td><code>h1</code></td>
<td>0</td>
<td>0</td>
<td>1</td>
<td>0-0-1</td>
</tr>
<tr>
<td><code>p + p::first-letter</code></td>
<td>0</td>
<td>0</td>
<td>3</td>
<td>0-0-3</td>
</tr>
<tr>
<td><code>li &gt; a[href*=&quot;en-US&quot;] &gt; .inline-warning</code></td>
<td>0</td>
<td>2</td>
<td>2</td>
<td>0-2-2</td>
</tr>
<tr>
<td><code>#identifier</code></td>
<td>1</td>
<td>0</td>
<td>0</td>
<td>1-0-0</td>
</tr>
<tr>
<td><code>li:nth-child(2):hover</code></td>
<td>0</td>
<td>2</td>
<td>1</td>
<td>0-2-1</td>
</tr>
<tr>
<td><code>:not(.error)</code></td>
<td>0</td>
<td>1</td>
<td>0</td>
<td>0-1-0</td>
</tr>
</tbody></table>
<p>The MDN lesson provides an in-depth example: <code>#outer #inner a</code> (specificity 2-0-1) beats <code>#outer a</code> (1-0-1) on background-color, because the extra ID wins even though both have the same TYPE count.</p>
<h3 id="is-not-and-nesting"><code>:is()</code>, <code>:not()</code>, and Nesting</h3><p>The <code>:is()</code>, <code>:not()</code>, and <code>:has()</code> pseudo-classes do not add specificity themselves — but their parameters do. The MDN specificity reference explains that the weight used is the <em>highest</em> specificity among the parameters. This lets you game specificity:</p>
<pre><code class="language-css">:is(p, #override) { /* specificity: 1-0-0 — from the ID */ }
:not(#fake)         { /* specificity: 1-0-0 */ }
</code></pre>
<p>CSS nesting (<code>&amp;</code>) behaves the same as <code>:is()</code> for specificity purposes.</p>
</section>
<section><h2 id="inheritance"><a href="#inheritance" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Inheritance</h2><p>When a CSS property is set on a parent element, its descendants inherit that value by default — unless a more specific rule overrides it. The <a href="https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/Styling_basics/Handling_conflicts">MDN lesson</a> uses <code>color</code> and <code>font-family</code> as the canonical example: setting them on <code>&lt;body&gt;</code> applies them to every element inside it.</p>
<p>Some properties do not inherit. The MDN lesson gives <code>width</code>, <code>margin</code>, and <code>padding</code> as examples: &quot;If you set a <code>width</code> of <code>50%</code> on an element, all of its descendants do not get a <code>width</code> of <code>50%</code> of their parent&#39;s width. If this was the case, CSS would be very frustrating to use!&quot; Every MDN CSS property reference page lists whether the property is inherited under &quot;Formal definition.&quot;</p>
<p>Five universal values control inheritance for any property:</p>
<ul>
<li><code>inherit</code> — use the parent&#39;s value (effectively &quot;turn on&quot; inheritance for this property).</li>
<li><code>initial</code> — use the property&#39;s initial value (the CSS specification default).</li>
<li><code>revert</code> — use the user agent&#39;s default, not the spec&#39;s initial value.</li>
<li><code>revert-layer</code> — revert to the value from the previous cascade layer.</li>
<li><code>unset</code> — act like <code>inherit</code> for inherited properties, <code>initial</code> for non-inherited ones.</li>
</ul>
<p>The shorthand <code>all: unset</code> resets every property on an element to its natural value, which the MDN lesson calls &quot;a convenient way to undo changes made to styles so that you can get back to a known starting point.&quot;</p>
</section>
<section><h2 id="how-the-three-interact"><a href="#how-the-three-interact" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How the Three Interact</h2><p>The MDN lesson describes the full resolution order: first the cascade origin and importance determine which layer has precedence; then specificity resolves conflicts <em>within</em> that layer; then source order breaks remaining ties. Only <em>after</em> all of that does inheritance apply — and only for properties that received no cascade value.</p>
<p>The practical debugging sequence the MDN lesson recommends: ask &quot;is this cascade, specificity, or inheritance?&quot; The answer is usually specificity. Inspect the element in browser DevTools, look at the applied rules, note each one&#39;s origin and specificity, and remove rules until the conflict resolves.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-is-the-difference-between-specificity-and-the-cascade">What is the difference between specificity and the cascade?</h3><p>The cascade is the full algorithm that decides which CSS declaration wins — it evaluates origin (user agent, user, author), importance (<code>!important</code>), specificity, and source order. Specificity is one <em>factor</em> within the cascade, applied only after origin and importance. The MDN specificity reference puts it clearly: specificity is &quot;relevant and compared only between selectors from the one cascade origin and layer that has precedence.&quot;</p>
<h3 id="why-should-i-avoid-important">Why should I avoid <code>!important</code>?</h3><p>The <code>!important</code> flag flips the cascade&#39;s priority order, making higher-specificity declarations lose to lower-specificity ones that carry the flag. The MDN lesson warns this &quot;changes the way the cascade normally works, so it can make debugging CSS problems really hard to work out, especially in a large stylesheet.&quot; Only reach for it in CMS environments where you cannot edit the core stylesheet and need to override an inline style or a framework default.</p>
<h3 id="does-specificity-count-pseudo-classes-and-pseudo-elements-the-same-way">Does specificity count pseudo-classes and pseudo-elements the same way?</h3><p>Pseudo-classes (<code>:hover</code>, <code>:first-child</code>, <code>:not()</code>) add to the CLASS column (0-1-0). Pseudo-elements (<code>::before</code>, <code>::first-line</code>) add to the TYPE column (0-0-1). Both are counted — but the <code>:not()</code> pseudo-class is an exception: it counts only the specificity of its arguments, not the pseudo-class itself. So <code>:not(.error)</code> is worth 0-1-0, and <code>:not(#unique)</code> is worth 1-0-0.</p>
<h3 id="can-i-use-all-unset-safely-on-any-element">Can I use <code>all: unset</code> safely on any element?</h3><p><code>all: unset</code> resets every property to its natural value (inherit for inherited properties, initial for non-inherited). This is safe and useful for restoring an element to its browser default state. It removes any author styles you had applied, but it does not remove inline styles or <code>!important</code> declarations. The MDN lesson describes it as &quot;a convenient way to undo changes&quot; for debugging or resetting component styles.</p>
<h3 id="how-do-i-debug-a-css-rule-that-is-not-applying">How do I debug a CSS rule that is not applying?</h3><p>The MDN lesson&#39;s recommended sequence: open browser DevTools, select the element, find the property in the Styles panel — DevTools shows each rule&#39;s origin (author/user-agent), specificity (displayed as a weight), and source file. If a rule is crossed out, something with higher specificity, importance, or cascade origin is overriding it. If nothing is crossed out, inheritance is likely the cause — the element may be getting its value from an ancestor. Removing rules one by one until the style appears is the fastest way to narrow it down.</p>
</section>
<section><h2 id="verdict"><a href="#verdict" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Verdict</h2><p>CSS specificity is a three-column weight (ID – CLASS – TYPE) that resolves selector conflicts within the same cascade origin and layer. IDs always beat classes, classes always beat element selectors, and source order breaks ties when the weight is equal. The <code>!important</code> flag overrides this entire system and should be a last resort. Understanding specificity — combined with knowledge of inheritance and cascade origin — is what separates CSS that behaves predictably from CSS that surprises you.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>What is CORS? Cross-Origin Resource Sharing explained</title>
      <link>https://debod.id/en/posts/cors-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/cors-explained/</guid>
      <pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate>
      <category>api</category>
      <category>api</category>
      <category>http</category>
      <category>security</category>
      <category>web</category>
      <category>browser</category>
      <description>Cross-Origin Resource Sharing (CORS) is the mechanism browsers use to allow or block requests between different origins. This guide explains how CORS works, why it exists, common errors, and how to fix them.</description>
      <content:encoded><![CDATA[<section><p>Browsers block web pages from reading data from a different origin — by design. This security rule, called the same-origin policy, prevents one site from reading another site&#39;s private data. But it also blocks legitimate uses: a front-end app fetching data from an API on a different domain, a page loading fonts from a CDN, or a JavaScript module importing from a separate host. Cross-Origin Resource Sharing (CORS) is the mechanism that solves this, letting servers explicitly allow cross-origin access when it is intended.</p></section>
<section><h2 id="what-is-an-origin"><a href="#what-is-an-origin" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What is an origin?</h2><p>An <strong>origin</strong> is the combination of three things about a URL: the <strong>scheme</strong> (http or https), the <strong>host</strong> (domain name), and the <strong>port</strong> (if specified). Two URLs have the same origin only when all three match.</p>
<table>
<thead>
<tr>
<th>URL</th>
<th>Origin</th>
</tr>
</thead>
<tbody><tr>
<td><code>https://example.com/page</code></td>
<td><code>https://example.com</code></td>
</tr>
<tr>
<td><code>https://api.example.com/data</code></td>
<td><code>https://api.example.com</code> — different host</td>
</tr>
<tr>
<td><code>https://example.com:8080/api</code></td>
<td><code>https://example.com:8080</code> — different port</td>
</tr>
<tr>
<td><code>https://example.com/page</code> vs <code>http://example.com/page</code></td>
<td>Different scheme</td>
</tr>
</tbody></table>
<p>A cross-origin request is any request where the origin of the requesting page differs from the origin of the resource it is fetching. For example, a page at <code>https://myapp.com</code> using <code>fetch(&#39;https://api.myservice.com/users&#39;)</code> is a cross-origin request.</p>
</section>
<section><h2 id="why-browsers-block-cross-origin-requests"><a href="#why-browsers-block-cross-origin-requests" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Why browsers block cross-origin requests</h2><p>The same-origin policy has been a cornerstone of web browser security since Netscape Navigator 2.0 in 1996. Without it, a malicious page loaded in your browser could read emails from <code>mail.google.com</code>, access your bank account data from <code>bank.com</code>, or extract sensitive information from any site you were logged into.</p>
<p>Browsers enforce this by restricting what scripts running on page A can read from origin B. Both <code>fetch()</code> and <code>XMLHttpRequest</code> follow the same-origin policy. If origin B does not explicitly permit the request, the browser blocks the response — even if the server sent it successfully.</p>
<p>This is important to understand: the <strong>server receives the request</strong>. The browser is the one blocking the response body from reaching your JavaScript code.</p>
</section>
<section><h2 id="how-cors-works"><a href="#how-cors-works" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How CORS works</h2><p>CORS is an HTTP header mechanism. The server receiving a cross-origin request adds headers to its response; the browser reads those headers and decides whether to let the JavaScript code see the response.</p>
<p>There are two types of requests that CORS handles differently.</p>
<h3 id="simple-requests">Simple requests</h3><p>A simple request meets all three of these conditions:</p>
<ol>
<li>Uses <code>GET</code>, <code>HEAD</code>, or <code>POST</code></li>
<li>Uses only safe headers (no custom headers like <code>Authorization</code> or <code>X-Custom-Header</code>)</li>
<li>Sends a Content-Type of <code>application/x-www-form-urlencoded</code>, <code>multipart/form-data</code>, or <code>text/plain</code></li>
</ol>
<p>For simple requests, the browser sends the <code>Origin</code> header automatically:</p>
<pre><code>Origin: https://myapp.com
</code></pre>
<p>If the server allows that origin, it responds with the <code>Access-Control-Allow-Origin</code> header:</p>
<pre><code>Access-Control-Allow-Origin: https://myapp.com
</code></pre>
<p>Or to allow any origin:</p>
<pre><code>Access-Control-Allow-Origin: *
</code></pre>
<p>If the origin is not allowed, the browser receives the response but blocks JavaScript from reading it, and throws a CORS error in the console.</p>
<h3 id="preflight-requests">Preflight requests</h3><p>For requests that are not simple — for example, a <code>DELETE</code> request, a <code>POST</code> with <code>application/json</code>, or a request with a custom header — the browser first sends a <strong>preflight</strong> request using the <code>OPTIONS</code> HTTP method.</p>
<p>A preflight asks the server: &quot;Do you actually allow this cross-origin request?&quot; The browser sends two headers:</p>
<pre><code>Origin: https://myapp.com
Access-Control-Request-Method: DELETE
Access-Control-Request-Headers: Authorization, Content-Type
</code></pre>
<p>The server responds with:</p>
<pre><code>Access-Control-Allow-Origin: https://myapp.com
Access-Control-Allow-Methods: GET, POST, DELETE
Access-Control-Allow-Headers: Authorization, Content-Type
Access-Control-Max-Age: 86400
</code></pre>
<p><code>Access-Control-Max-Age</code> tells the browser how long (in seconds) it can cache this preflight result, so it does not have to repeat the OPTIONS request for every subsequent call.</p>
<p>Only if the preflight succeeds does the browser send the actual request. If the preflight fails, the actual request is never sent.</p>
</section>
<section><h2 id="cors-response-headers-in-detail"><a href="#cors-response-headers-in-detail" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>CORS response headers in detail</h2><p>These are the headers a server can send to control cross-origin access:</p>
<ul>
<li><strong><code>Access-Control-Allow-Origin</code></strong> — Required. Specifies which origins can access the resource. Use a specific origin (<code>https://myapp.com</code>) or <code>*</code> for public resources.</li>
<li><strong><code>Access-Control-Allow-Methods</code></strong> — Lists permitted HTTP methods for non-simple requests (sent in response to preflight).</li>
<li><strong><code>Access-Control-Allow-Headers</code></strong> — Lists permitted request headers for non-simple requests.</li>
<li><strong><code>Access-Control-Allow-Credentials</code></strong> — Set to <code>true</code> to allow cookies, authorization headers, or TLS client certificates to be sent cross-origin. Cannot be <code>true</code> when <code>Access-Control-Allow-Origin</code> is <code>*</code>.</li>
<li><strong><code>Access-Control-Expose-Headers</code></strong> — Lists response headers (beyond the six safe ones) that JavaScript can read.</li>
<li><strong><code>Access-Control-Max-Age</code></strong> — How long the browser may cache the preflight result.</li>
</ul>
</section>
<section><h2 id="common-cors-errors-and-how-to-fix-them"><a href="#common-cors-errors-and-how-to-fix-them" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Common CORS errors and how to fix them</h2><h3 id="access-to-fetch-has-been-blocked-by-cors-policy">&quot;Access to fetch has been blocked by CORS policy&quot;</h3><p>The most common CORS error. The browser blocked the response because the server did not include the right <code>Access-Control-Allow-Origin</code> header.</p>
<p><strong>Fix:</strong> Add the <code>Access-Control-Allow-Origin</code> header on the server. For a Node.js/Express server:</p>
<pre><code class="language-javascript">app.use((req, res, next) =&gt; {
  res.header(&quot;Access-Control-Allow-Origin&quot;, &quot;https://myapp.com&quot;);
  next();
});
</code></pre>
<p>Or for multiple origins:</p>
<pre><code class="language-javascript">const allowedOrigins = [&quot;https://myapp.com&quot;, &quot;https://staging.myapp.com&quot;];
app.use((req, res, next) =&gt; {
  const origin = req.headers.origin;
  if (allowedOrigins.includes(origin)) {
    res.setHeader(&quot;Access-Control-Allow-Origin&quot;, origin);
  }
  next();
});
</code></pre>
<h3 id="preflight-returning-403-or-404">Preflight returning 403 or 404</h3><p>If the OPTIONS preflight route is not handled, the server returns an error instead of the expected headers.</p>
<p><strong>Fix:</strong> Ensure your server handles <code>OPTIONS</code> requests for the relevant routes, even if there is no actual data to return:</p>
<pre><code class="language-javascript">app.options(&quot;/api/resource&quot;, (req, res) =&gt; {
  res.setHeader(&quot;Access-Control-Allow-Origin&quot;, &quot;https://myapp.com&quot;);
  res.setHeader(&quot;Access-Control-Allow-Methods&quot;, &quot;GET, POST, DELETE&quot;);
  res.setHeader(&quot;Access-Control-Allow-Headers&quot;, &quot;Content-Type, Authorization&quot;);
  res.sendStatus(200);
});
</code></pre>
<p>In Next.js App Router, the default behavior handles OPTIONS automatically as long as the middleware sets the right headers for non-matching routes.</p>
<h3 id="credentials-and-wildcard">Credentials and wildcard</h3><p>You cannot use <code>Access-Control-Allow-Origin: *</code> with <code>Access-Control-Allow-Credentials: true</code>. The browser rejects this combination.</p>
<p><strong>Fix:</strong> Always specify the exact origin when credentials are needed:</p>
<pre><code class="language-javascript">res.header(&quot;Access-Control-Allow-Origin&quot;, &quot;https://myapp.com&quot;); // not *
res.header(&quot;Access-Control-Allow-Credentials&quot;, &quot;true&quot;);
</code></pre>
</section>
<section><h2 id="cors-on-static-sites-and-cdns"><a href="#cors-on-static-sites-and-cdns" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>CORS on static sites and CDNs</h2><p>When hosting on a static site with a CDN (Cloudflare Pages, Netlify, Vercel, S3+CloudFront), you configure CORS at the CDN or object storage layer, not in application code.</p>
<p>On <strong>Cloudflare Pages</strong>, CORS headers are set via a <code>_headers</code> file in the project root:</p>
<pre><code>/api/*
  Access-Control-Allow-Origin: https://myapp.com
  Access-Control-Allow-Methods: GET, POST, OPTIONS
  Access-Control-Allow-Headers: Content-Type, Authorization
</code></pre>
<p>On <strong>AWS S3</strong>, you set CORS configuration in the bucket properties under &quot;CORS configuration&quot;, using JSON:</p>
<pre><code class="language-json">[
  {
    &quot;AllowedOrigins&quot;: [&quot;https://myapp.com&quot;],
    &quot;AllowedMethods&quot;: [&quot;GET&quot;, &quot;POST&quot;],
    &quot;AllowedHeaders&quot;: [&quot;Content-Type&quot;],
    &quot;MaxAgeSeconds&quot;: 86400
  }
]
</code></pre>
</section>
<section><h2 id="cors-vs-json-p"><a href="#cors-vs-json-p" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>CORS vs JSON-P</h2><p>Before CORS existed, developers worked around the same-origin policy using <strong>JSON with Padding (JSON-P)</strong>: wrapping JSON data inside a callback function and loading it via a <code>&lt;script&gt;</code> tag. Because <code>&lt;script&gt;</code> tags are not subject to the same-origin policy, this allowed cross-origin data retrieval.</p>
<p>JSON-P has two significant problems: it requires the server to cooperate (it must wrap its response in a function call), and it is inherently insecure — it executes the returned JavaScript in your page context, so the server can inject arbitrary code.</p>
<p>CORS is the modern, correct solution. If you see JSON-P in use, it is a sign that the API predates CORS or was not designed with security in mind.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="does-cors-affect-server-to-server-requests">Does CORS affect server-to-server requests?</h3><p>No. CORS is a <strong>browser-enforced</strong> mechanism. If a server makes a request to another server using <code>curl</code>, a server-side HTTP client, or any non-browser tool, the same-origin policy does not apply and no CORS headers are checked. CORS only matters when JavaScript running inside a browser initiates the request.</p>
<h3 id="is-access-control-allow-origin-ever-safe-to-use">Is <code>Access-Control-Allow-Origin: *</code> ever safe to use?</h3><p>Yes, but only for <strong>public, non-sensitive resources</strong> that do not require credentials. For example, a CDN serving fonts, a public API with no authentication, or static assets. Any resource that uses cookies, session tokens, or any form of credentials must specify the exact allowed origin — <code>*</code> is not permitted with credentials.</p>
<h3 id="does-cors-protect-against-attacks">Does CORS protect against attacks?</h3><p>CORS does <strong>not</strong> prevent malicious cross-origin requests. A malicious site can still send a POST or GET to your API — CORS headers do not stop the request from being sent; they only control whether the browser lets JavaScript read the response. Protect your API with authentication, rate limiting, and CSRF tokens. CORS is a convenience mechanism for legitimate cross-origin access, not a security firewall.</p>
<h3 id="why-does-my-browser-send-an-options-request-before-every-post">Why does my browser send an OPTIONS request before every POST?</h3><p>Your POST request likely has a <code>Content-Type</code> of <code>application/json</code> or includes a custom header. That makes it a non-simple request, triggering a preflight OPTIONS. This is normal behavior. The OPTIONS request is fast — it only carries headers, no body. If preflights are causing latency issues, you can reduce their frequency by setting <code>Access-Control-Max-Age</code> to a higher value (e.g., <code>3600</code> for one hour).</p>
</section>
<section><h2 id="verdict"><a href="#verdict" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Verdict</h2><p>CORS is a browser security mechanism that gives servers control over who can read their resources from a web page. It uses HTTP headers to let servers permit cross-origin access, and the browser enforces those decisions. Understanding CORS means understanding the same-origin policy, the difference between simple and preflighted requests, and which headers control what. When you encounter a CORS error in development, the fix is almost always on the server — add the right <code>Access-Control-Allow-Origin</code> header and handle <code>OPTIONS</code> for preflight requests. CORS is not a security wall for your API, but it is the correct and standard way to enable legitimate cross-origin resource sharing.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>What is cloud computing? The NIST model explained (IaaS, PaaS, SaaS)</title>
      <link>https://debod.id/en/posts/cloud-computing-guide/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/cloud-computing-guide/</guid>
      <pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate>
      <category>devops</category>
      <category>cloud-computing</category>
      <category>devops</category>
      <description>Cloud computing is a model for enabling ubiquitous, convenient, on-demand network access to a shared pool of configurable computing resources that can be rapidly provisioned and released with minimal management effort. The NIST definition breaks it into five essential characteristics, three service models (SaaS, PaaS, IaaS) and four deployment models (public, private, community, hybrid). This guide explains each part and how to choose between them.</description>
      <content:encoded><![CDATA[<section><p>Cloud computing is a model for enabling ubiquitous, convenient, on-demand network access to a shared pool of configurable computing resources — networks, servers, storage, applications, and services — that can be rapidly provisioned and released with minimal management effort. That wording is the official definition from NIST SP 800-145, the standard reference for what counts as cloud. The model is built from five essential characteristics, three service models (IaaS, PaaS, SaaS), and four deployment models (public, private, community, hybrid). This guide explains each piece and how to pick the right one.</p></section>
<section><h2 id="what-makes-something-cloud-the-five-characteristics"><a href="#what-makes-something-cloud-the-five-characteristics" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What makes something &quot;cloud&quot;? The five characteristics</h2><p>According to NIST, a service is only truly cloud if it has all five essential characteristics:</p>
<ul>
<li><strong>On-demand self-service.</strong> A consumer can provision computing capabilities — servers, storage, applications — unilaterally, whenever needed, without requiring human interaction with the provider. You sign up and spin resources up yourself.</li>
<li><strong>Broad network access.</strong> Capabilities are available over the network through standard mechanisms, and can be used from heterogeneous client platforms: laptops, phones, tablets, workstations.</li>
<li><strong>Resource pooling.</strong> The provider&#39;s computing resources are pooled to serve multiple consumers, with physical and virtual resources dynamically assigned and reassigned according to demand. The customer generally has no control or knowledge over the exact location of the provided resources — hence the common &quot;your data lives somewhere&quot; trade-off.</li>
<li><strong>Rapid elasticity.</strong> Capabilities can be elastically provisioned and released, in some cases automatically, to scale rapidly outward and inward. To the consumer, the available capacity often appears unlimited.</li>
<li><strong>Measured service.</strong> Resource usage is monitored, controlled, and reported, providing transparency for both provider and consumer. This metering is what makes pay-as-you-go billing possible.</li>
</ul>
<p>If a product is missing one of these — say, you must email a salesperson to provision anything (no self-service), or capacity is fixed and cannot scale (no elasticity) — it is not cloud in the NIST sense, even if it is hosted online.</p>
</section>
<section><h2 id="the-three-service-models"><a href="#the-three-service-models" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The three service models</h2><p>The service models describe how much of the stack the provider manages for you.</p>
<h3 id="infrastructure-as-a-service-iaas">Infrastructure as a Service (IaaS)</h3><p>IaaS gives you the fundamental building blocks: processing, storage, networks, and other computing resources, on demand. You can deploy and run arbitrary software — including an operating system of your choice — and you manage the OS, storage, and applications yourself. The provider manages the physical infrastructure below that. Examples include Amazon EC2 (AWS), Google Compute Engine, Microsoft Azure virtual machines, and DigitalOcean Droplets. IaaS suits teams that want full control and are willing to handle patching, configuration, and capacity planning.</p>
<h3 id="platform-as-a-service-paas">Platform as a Service (PaaS)</h3><p>PaaS gives you the capability to deploy your own applications onto a managed platform, using the languages, libraries, and tools the provider supports. The provider runs the underlying infrastructure — servers, operating systems, networking — so you only manage your application and its configuration. Heroku and Render are classic PaaS examples: push your code and the platform runs it. PaaS removes most operations work and suits teams that want to ship applications quickly without managing servers.</p>
<h3 id="software-as-a-service-saas">Software as a Service (SaaS)</h3><p>SaaS gives you the provider&#39;s applications, running on their infrastructure, accessible from any client device — usually a browser or a programmatic interface. You configure the application for your users but manage none of the infrastructure beneath it. Google Workspace, Microsoft 365, Salesforce, Slack, and Zoom are everyday SaaS. This is the model most non-technical users interact with daily.</p>
<p>In practice the lines blur — most clouds sell all three, and a managed database or a serverless function sits somewhere between IaaS and PaaS.</p>
</section>
<section><h2 id="the-four-deployment-models"><a href="#the-four-deployment-models" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The four deployment models</h2><p>Deployment models describe where the cloud infrastructure lives and who uses it.</p>
<ul>
<li><strong>Public cloud.</strong> Provisioned for open use by the general public. It is owned and operated by a cloud provider — a business, academic, or government organization — and exists on the provider&#39;s premises. AWS, Azure, and Google Cloud are public clouds. Anyone can sign up.</li>
<li><strong>Private cloud.</strong> Provisioned for the exclusive use of a single organization with multiple consumers (business units). It may be owned, managed, and operated by the organization itself, a third party, or a mix, and it may live on or off the organization&#39;s premises. Private cloud is chosen for control, compliance, or data-residency reasons.</li>
<li><strong>Community cloud.</strong> Provisioned for exclusive use by a specific community of consumers from organizations that share concerns — mission, security requirements, policy, or compliance. It can be owned and operated by one or more of those organizations, a third party, or a combination.</li>
<li><strong>Hybrid cloud.</strong> A composition of two or more distinct cloud infrastructures (private, community, or public) that remain unique entities but are bound together by technology that enables data and application portability. The classic pattern: keep sensitive workloads in a private cloud and burst to a public cloud for scale.</li>
</ul>
</section>
<section><h2 id="serverless-and-modern-extensions"><a href="#serverless-and-modern-extensions" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Serverless and modern extensions</h2><p>The NIST model was published in 2011 and predates the serverless era. Functions as a Service (FaaS) — AWS Lambda, Cloudflare Workers — and managed services like managed databases or object storage are best understood as extensions of the PaaS idea: the provider manages even more of the stack, and you pay per use instead of per provisioned machine. They still exhibit all five NIST characteristics, so they qualify as cloud; they just sit higher up the managed-services spectrum.</p>
</section>
<section><h2 id="which-one-should-you-use"><a href="#which-one-should-you-use" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Which one should you use?</h2><p>There is no universal answer — it depends on your team, your workload, and your constraints:</p>
<ul>
<li><strong>You want maximum control over the full stack</strong> (custom kernels, specific OS, compliance): IaaS on a public cloud, or a private cloud for strict data residency.</li>
<li><strong>You want to ship applications fast and avoid managing servers</strong>: PaaS. Deploy from Git and let the platform handle operations.</li>
<li><strong>You want a ready-made product with no infrastructure at all</strong>: SaaS. Sign up and configure.</li>
<li><strong>Your workload is bursty or intermittent</strong>: serverless/FaaS, so you pay only for actual execution.</li>
<li><strong>You need both compliance and scale</strong>: hybrid — keep sensitive data in a private or community cloud, expand into public cloud for elasticity.</li>
</ul>
<p>As a rule of thumb, use the highest level of abstraction that still gives you the control you need: SaaS where a product exists, PaaS for your own applications, IaaS when you need raw infrastructure, and serverless when your workload is event-driven or unpredictable.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-is-the-difference-between-iaas-paas-and-saas">What is the difference between IaaS, PaaS and SaaS?</h3><p>IaaS provides raw infrastructure (compute, storage, networking) that you manage yourself; PaaS provides a managed platform where you deploy your own applications without managing servers; SaaS provides complete applications you use directly. They differ in how much of the stack the provider manages — IaaS is the most control and most operations work, SaaS the least of both. Examples: DigitalOcean Droplets (IaaS), Heroku (PaaS), Google Workspace (SaaS).</p>
<h3 id="what-are-the-five-characteristics-of-cloud-computing">What are the five characteristics of cloud computing?</h3><p>The NIST SP 800-145 definition names five essential characteristics: on-demand self-service, broad network access, resource pooling, rapid elasticity, and measured service. A service that lacks any of these — such as one requiring manual human provisioning, or one with fixed capacity — is not considered cloud in the NIST sense.</p>
<h3 id="what-are-the-four-cloud-deployment-models">What are the four cloud deployment models?</h3><p>The four NIST deployment models are public cloud (open to the general public, owned by a provider), private cloud (exclusive to one organization), community cloud (exclusive to a group of organizations with shared concerns), and hybrid cloud (a composition of two or more of these, bound together by technology that enables data and application portability).</p>
<h3 id="what-is-the-nist-definition-of-cloud-computing">What is the NIST definition of cloud computing?</h3><p>NIST SP 800-145 defines cloud computing as &quot;a model for enabling ubiquitous, convenient, on-demand network access to a shared pool of configurable computing resources (e.g., networks, servers, storage, applications, and services) that can be rapidly provisioned and released with minimal management effort or service provider interaction.&quot; It is the standard technical definition used by industry and government.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>CI/CD Explained: Continuous Integration, Delivery &amp; Deployment</title>
      <link>https://debod.id/en/posts/cd-ci-pipeline-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/cd-ci-pipeline-explained/</guid>
      <pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate>
      <category>devops</category>
      <category>devops</category>
      <category>automation</category>
      <category>github</category>
      <description>CI/CD automates the path from code commit to production: continuous integration builds and tests every change, continuous delivery keeps releases ready, continuous deployment ships them automatically.</description>
      <content:encoded><![CDATA[<section><p>CI/CD turns software delivery into an automated pipeline: developers merge code often, a build server compiles, tests and checks every change, and the artifact that passes those checks can be released at any moment. The acronym stands for <strong>continuous integration</strong> (CI) and <strong>continuous delivery or deployment</strong> (CD) — three distinct practices that together cut the time, cost and risk of shipping software.</p></section>
<section><h2 id="continuous-integration-build-and-test-every-change"><a href="#continuous-integration-build-and-test-every-change" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Continuous integration: build and test every change</h2><p>Continuous integration (CI) is the practice of merging source-code changes into a shared branch frequently and proving the integrated codebase still works. In practice, developers push to a shared repository many times a day; every push triggers an automated process that builds the project and runs checks such as unit tests, linting and static analysis. A single-command build is a core recommendation: if the whole system can be built and tested with one command, a server can run it on every commit and report failures in minutes instead of days. CI gives teams fast feedback, so integration problems surface when the change is still small and fresh.</p>
</section>
<section><h2 id="continuous-delivery-always-release-ready"><a href="#continuous-delivery-always-release-ready" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Continuous delivery: always release-ready</h2><p>Continuous delivery (CD) takes the pipeline further: every change that passes the automated pipeline is in a releasable state, but the final release to production is still a human decision. The deployment pipeline gives visibility (everyone can see the state of a change), feedback (failures are reported as soon as they happen) and a continually deployable artifact (any version can be deployed to any environment). Teams that practice continuous delivery can release on demand — daily or even hourly — because deploying is a routine, low-risk act rather than a quarterly event.</p>
</section>
<section><h2 id="continuous-deployment-ship-automatically"><a href="#continuous-deployment-ship-automatically" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Continuous deployment: ship automatically</h2><p>Continuous deployment automates the last step: every change that passes the full pipeline is deployed to production without a human gate. This is the fastest feedback loop — a fix a developer commits can be live minutes later. It demands strong automated test coverage and fast rollback, because the safety net is the pipeline itself. Many teams start with continuous integration, add continuous delivery with a manual approval step, and only then consider full continuous deployment.</p>
</section>
<section><h2 id="a-typical-pipeline-stage-by-stage"><a href="#a-typical-pipeline-stage-by-stage" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>A typical pipeline, stage by stage</h2><p>A running example using GitHub Actions — a widely used CI/CD platform from GitHub — shows the shape of a modern pipeline:</p>
<ol>
<li><strong>Trigger.</strong> A push to a branch or a pull request starts the workflow.</li>
<li><strong>Checkout and setup.</strong> The runner checks out the repository and installs the toolchain (for example Node.js).</li>
<li><strong>Install dependencies.</strong> <code>npm ci</code> (or the equivalent) installs the exact locked dependencies.</li>
<li><strong>Quality checks.</strong> Linting, formatting and type checks run first — they are fast and catch obvious problems.</li>
<li><strong>Tests.</strong> Unit tests and integration tests run against the built code, often with a test database.</li>
<li><strong>Build.</strong> The project compiles into a deployable artifact.</li>
<li><strong>Security and analysis.</strong> Optional stages scan dependencies and code for known vulnerabilities.</li>
<li><strong>Deploy.</strong> The artifact is published to a staging or production environment once every previous stage passed.</li>
</ol>
<p>GitLab CI/CD (the pipeline engine built into GitLab) follows the same philosophy, describing the workflow as &quot;build, test, and deploy your code&quot; through a pipeline defined in a YAML file in the repository. The mechanics differ — GitHub Actions uses workflows, GitLab uses <code>.gitlab-ci.yml</code> pipelines and runners — but the goal is identical: verify every change automatically and make shipping routine.</p>
</section>
<section><h2 id="why-teams-adopt-ci-cd"><a href="#why-teams-adopt-ci-cd" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Why teams adopt CI/CD</h2><p>The Wikipedia article on continuous delivery summarizes the benefit: software can &quot;be reliably released at any time,&quot; reducing the cost, time and risk of delivering changes through more incremental updates. CI/CD also changes team mindset — any commit may become a release, so the pipeline keeps the codebase in a perpetually shippable state instead of freezing features before a big-bang launch.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-is-the-difference-between-ci-and-cd">What is the difference between CI and CD?</h3><p>CI (continuous integration) automatically builds and tests every code change merged into a shared branch. CD (continuous delivery) keeps the passing artifact release-ready, and continuous deployment goes one step further by releasing it to production automatically. CI makes integration safe; CD makes release routine.</p>
<h3 id="does-ci-cd-require-containers">Does CI/CD require containers?</h3><p>No. Containers (like Docker) are a common way to make builds and deployments reproducible, but CI/CD works on any environment a runner can provide: a virtual machine, a bare server, or even a local machine. Many pipelines run fine with just a scripted build and test step.</p>
<h3 id="how-long-should-a-ci-cd-pipeline-take">How long should a CI/CD pipeline take?</h3><p>The right answer depends on the project, but the faster the feedback the better. Small projects often finish in 5–15 minutes; large monorepos or suites with heavy integration tests may take longer. Long pipelines are usually split into fast &quot;pre-merge&quot; checks and a slower deploy stage.</p>
<h3 id="is-continuous-deployment-dangerous">Is continuous deployment dangerous?</h3><p>It looks risky, but it is the strictest discipline: every change must pass the full automated pipeline — including tests, builds and security scans — before it reaches production. Teams that deploy continuously invest in test coverage, observability and rollback, which makes each individual release smaller and safer than a rare big-bang release.</p>
</section>
<section><h2 id="summary"><a href="#summary" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Summary</h2><p>CI/CD automates the path from commit to production: continuous integration builds and tests every change, continuous delivery keeps the result release-ready, and continuous deployment ships the final step automatically. The pipeline — trigger, build, test, deploy — gives fast feedback, routine releases and lower risk. Whether you use GitHub Actions, GitLab CI/CD or a self-hosted runner, the practice is the same: verify everything, ship often, and keep the codebase shippable at all times.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>What is a canary deployment? How canary releases work and when to use them</title>
      <link>https://debod.id/en/posts/canary-deployments-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/canary-deployments-explained/</guid>
      <pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate>
      <category>devops</category>
      <category>devops</category>
      <category>kubernetes</category>
      <category>containers</category>
      <category>monitoring</category>
      <description>A canary deployment rolls out a new version to a small subset of users first, then gradually expands if metrics stay healthy. Compare canary vs rolling update vs blue-green, learn the metrics to watch, and see when this progressive-delivery pattern fits your team.</description>
      <content:encoded><![CDATA[<section><p>A <strong>canary deployment</strong> is a release strategy that routes a small percentage of live traffic to a new version of an application, monitors key metrics for regressions, and only expands the rollout when the new version proves stable. The name comes from the historical practice of coal miners carrying a canary in a cage: if toxic gases were present, the canary would succumb first, warning the miners before they were affected. In software, the &quot;canary&quot; users receive the new version first; if error rates, latency, or business metrics degrade, the rollout stops or rolls back before the broader population is exposed.</p>
<p>Canary deployments sit between a <strong>rolling update</strong> (gradual replacement of all instances) and a <strong>blue-green deployment</strong> (full swap between two identical environments). A rolling update replaces instances incrementally but every user eventually hits the new version at the same pace; a blue-green deployment validates the entire new environment before switching all traffic at once; a canary deployment exposes the new version to a controlled slice of real production traffic, measures, then decides whether to proceed, pause, or roll back. This makes canary the most data-driven of the three — but it also demands the most operational maturity: version-segmented metrics, fast decision loops, and automated rollback.</p></section>
<section><h2 id="what-a-canary-deployment-is"><a href="#what-a-canary-deployment-is" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What a canary deployment is</h2><p>A canary deployment runs two (or more) versions of an application simultaneously in the same environment. A traffic router — often a service mesh, an ingress controller, a load balancer, or a feature-flag platform — directs a configurable percentage of requests to the new version while the majority remain on the stable version. The canary population is typically small at first (1–5% of traffic), then grows in stages (10%, 25%, 50%, 100%) as confidence builds.</p>
<p>The core loop:</p>
<ol>
<li><strong>Deploy</strong> the new version alongside the stable version (same cluster, same infrastructure).</li>
<li><strong>Route</strong> a small traffic slice to the canary (via weighted routing, header-based routing, or feature flags).</li>
<li><strong>Observe</strong> version-segmented metrics: error rate, latency percentiles (p50, p95, p99), throughput, business KPIs (conversion, revenue, engagement).</li>
<li><strong>Decide</strong>: if metrics are within acceptable thresholds, increase the canary percentage; if not, roll back immediately by shifting traffic back to stable.</li>
<li><strong>Repeat</strong> until 100% of traffic is on the new version, then retire the old version.</li>
</ol>
<p>This loop can be manual (engineer watches dashboards and clicks buttons) or automated (deployment controller evaluates metrics and promotes/rolls back). Automation is the end goal for teams that deploy frequently — it reduces decision latency from minutes to seconds.</p>
<h3 id="why-the-name-canary">Why the name &quot;canary&quot;?</h3><p>The term originates from coal mining. Miners carried caged canaries into tunnels because birds are more sensitive to carbon monoxide and methane than humans. If the canary stopped singing or died, the miners knew the air was toxic and evacuated immediately. The software analogy: a small group of real users (the canary) encounters the new code first; if it &quot;suffocates&quot; (crashes, errors, degrades), the team learns before the whole fleet is affected.</p>
<p>Martin Fowler describes the pattern: &quot;By slowly ramping up the load, you can monitor and capture the impact of the change before it affects everyone. The migrate phase lasts until all the users have been routed to the new version.&quot; (<a href="https://martinfowler.com/bliki/CanaryRelease.html">CanaryRelease</a>)</p>
</section>
<section><h2 id="canary-vs-rolling-update-vs-blue-green"><a href="#canary-vs-rolling-update-vs-blue-green" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Canary vs rolling update vs blue-green</h2><table>
<thead>
<tr>
<th>Aspect</th>
<th>Rolling Update</th>
<th>Blue-Green</th>
<th>Canary</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Traffic split</strong></td>
<td>Gradual, uniform across all users</td>
<td>Instant full switch</td>
<td>Controlled percentage, incremental</td>
</tr>
<tr>
<td><strong>Infrastructure</strong></td>
<td>Single environment</td>
<td>Two full environments</td>
<td>Single environment, two versions</td>
</tr>
<tr>
<td><strong>Rollback speed</strong></td>
<td>Gradual (scale down new, scale up old)</td>
<td>Instant (switch router back)</td>
<td>Instant (shift traffic % to 0)</td>
</tr>
<tr>
<td><strong>Risk profile</strong></td>
<td>Medium — all users eventually exposed</td>
<td>Low for validated env, high if validation missed something</td>
<td>Lowest — blast radius bounded by %</td>
</tr>
<tr>
<td><strong>Metrics needed</strong></td>
<td>Basic health checks</td>
<td>Pre-switch validation suite</td>
<td>Version-segmented production metrics</td>
</tr>
<tr>
<td><strong>Complexity</strong></td>
<td>Low (built into Kubernetes Deployments)</td>
<td>Medium (two envs, data sync)</td>
<td>High (traffic splitting, metrics, automation)</td>
</tr>
<tr>
<td><strong>Best for</strong></td>
<td>Stateless services, low-risk changes</td>
<td>Critical systems with full pre-prod validation</td>
<td>High-traffic services, frequent deploys, data-driven teams</td>
</tr>
</tbody></table>
<p>Kubernetes Deployments default to <code>RollingUpdate</code> with <code>maxSurge: 25%</code> and <code>maxUnavailable: 25%</code>. This is a rolling update, not a canary — it replaces Pods gradually but does not segment metrics by version. To implement a true canary in Kubernetes, you run <strong>two Deployments</strong> (stable + canary) and use a Service or Ingress with weighted routing (e.g., <code>nginx.ingress.kubernetes.io/canary-weight: &quot;10&quot;</code>) or a service mesh (Istio, Linkerd) to split traffic. The Kubernetes docs note: &quot;If you want to roll out releases to a subset of users or servers using the Deployment, you can create multiple Deployments, one for each release, following the canary pattern.&quot;</p>
</section>
<section><h2 id="what-you-need-to-run-canary-deployments"><a href="#what-you-need-to-run-canary-deployments" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What you need to run canary deployments</h2><h3 id="1-version-segmented-observability">1. Version-segmented observability</h3><p>You must be able to query &quot;error rate for version v2.3.1&quot; separately from &quot;error rate for version v2.3.0&quot;. This requires:</p>
<ul>
<li>A metric label or dimension for <code>version</code> (or <code>deployment</code>, <code>replicaSet</code>).</li>
<li>Latency percentiles (p50, p95, p99) per version.</li>
<li>Business metrics per version (conversion rate, revenue per user, error-driven drop-offs).</li>
<li>Tools: Prometheus + Grafana, Datadog, New Relic, Grafana Cloud, or cloud-managed equivalents.</li>
</ul>
<h3 id="2-traffic-splitting-mechanism">2. Traffic splitting mechanism</h3><p>Options from simplest to most sophisticated:</p>
<ul>
<li><strong>Feature flags</strong> (LaunchDarkly, Unleash, ConfigCat, homegrown): route users by attribute/cookie; easiest to start, works at application layer.</li>
<li><strong>Ingress/load balancer weighted routing</strong> (NGINX Ingress canary annotations, ALB weighted target groups, Cloudflare Load Balancing): operates at L7, no code changes.</li>
<li><strong>Service mesh</strong> (Istio, Linkerd, Consul): L4/L7 splitting with mutual TLS, retries, timeouts built in; highest operational overhead.</li>
<li><strong>Kubernetes Service + two Deployments</strong>: basic, uses <code>spec.selector</code> overlap; limited to round-robin, no sticky sessions or advanced rules.</li>
</ul>
<h3 id="3-automated-decision-loop-progressive-delivery">3. Automated decision loop (progressive delivery)</h3><p>Manual canaries work for occasional releases. For daily or hourly deploys, you want a controller that:</p>
<ul>
<li>Evaluates metrics every 30–60 seconds.</li>
<li>Promotes (increases canary %) if all SLOs pass.</li>
<li>Rolls back (drops canary % to 0) if any SLO breaches.</li>
<li>Records the decision audit trail.
Tools: Argo Rollouts, Flagger, Flux (with canary), Spinnaker, or custom controllers.</li>
</ul>
<h3 id="4-database-migration-compatibility">4. Database migration compatibility</h3><p>Canary means old and new code run <strong>simultaneously</strong> against the same database. Schema changes must be backward-compatible:</p>
<ul>
<li><strong>Expand</strong>: add new columns/tables (non-breaking), deploy canary writing to both old and new.</li>
<li><strong>Contract</strong>: after full rollout, remove old columns/tables.</li>
<li>Never drop a column the old version still reads. Never rename a column the old version writes.</li>
</ul>
</section>
<section><h2 id="typical-canary-rollout-schedule"><a href="#typical-canary-rollout-schedule" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Typical canary rollout schedule</h2><table>
<thead>
<tr>
<th>Stage</th>
<th>Canary %</th>
<th>Duration</th>
<th>Gate</th>
</tr>
</thead>
<tbody><tr>
<td>Internal / employees</td>
<td>1–5%</td>
<td>5–15 min</td>
<td>No critical errors, p99 latency &lt; baseline</td>
</tr>
<tr>
<td>Small external cohort</td>
<td>10%</td>
<td>10–30 min</td>
<td>Error rate &lt; 0.1%, business KPIs flat</td>
</tr>
<tr>
<td>Expanded cohort</td>
<td>25%</td>
<td>15–30 min</td>
<td>Same + no new alert patterns</td>
</tr>
<tr>
<td>Majority</td>
<td>50%</td>
<td>15–30 min</td>
<td>Same</td>
</tr>
<tr>
<td>Full</td>
<td>100%</td>
<td>—</td>
<td>Retire old version after stability window</td>
</tr>
</tbody></table>
<p>Total time: 45 minutes to a few hours depending on traffic volume and confidence thresholds. Low-traffic services need longer stages to accumulate statistical significance (minimum ~10K requests per stage for p99 reliability).</p>
</section>
<section><h2 id="when-to-use-canary-deployments"><a href="#when-to-use-canary-deployments" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>When to use canary deployments</h2><p><strong>Use canary when:</strong></p>
<ul>
<li>You deploy frequently (daily or more) and want to limit blast radius.</li>
<li>You have version-segmented observability (or can invest in it).</li>
<li>Production traffic patterns are hard to replicate in staging (real data, real scale, real user behavior).</li>
<li>You can tolerate running two versions briefly (schema compatibility, session handling).</li>
<li>You need data-driven confidence, not just &quot;tests passed.&quot;</li>
</ul>
<p><strong>Consider alternatives when:</strong></p>
<ul>
<li>You deploy infrequently (weekly or less) — blue-green or rolling update is simpler.</li>
<li>You lack version-segmented metrics — canary without metrics is theater.</li>
<li>The change is a database migration that isn&#39;t backward-compatible — use expand/contract or a maintenance window.</li>
<li>The service is low-traffic/internal — rolling update is sufficient.</li>
<li>Your team is early in CI/CD maturity — build the prerequisites first (automated tests, staging parity, basic monitoring).</li>
</ul>
</section>
<section><h2 id="canary-and-feature-flags"><a href="#canary-and-feature-flags" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Canary and feature flags</h2><p>Feature flags (feature toggles) are an <strong>alternative mechanism</strong> to achieve canary-like rollouts without infrastructure traffic splitting. Instead of routing at the network layer, the application code checks a flag at runtime: <code>if (flags.enabled(&#39;new-checkout&#39;)) { renderNewCheckout() } else { renderOldCheckout() }</code>. The flag service (LaunchDarkly, Unleash, etc.) controls the rollout percentage per user segment.</p>
<p>Pros: works at any layer (frontend, backend, mobile); granular targeting (geo, user tier, cohort); no infra changes. Cons: adds conditional code paths (toggle debt); requires flag lifecycle management; flags can leak into business logic if not cleaned up.</p>
<p>Wikipedia notes: &quot;Advanced roll out strategies such as canary roll out and A/B testing are easier to handle&quot; with feature toggles, but &quot;toggle debt arises due to the dead code present in software after a feature has been toggled on permanently.&quot; (<a href="https://en.wikipedia.org/wiki/Feature_toggle">Feature toggle</a>)</p>
</section>
<section><h2 id="common-pitfalls"><a href="#common-pitfalls" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Common pitfalls</h2><table>
<thead>
<tr>
<th>Pitfall</th>
<th>Consequence</th>
<th>Fix</th>
</tr>
</thead>
<tbody><tr>
<td>No version-segmented metrics</td>
<td>Can&#39;t tell if canary or stable is failing</td>
<td>Add <code>version</code> label to all metrics; instrument business KPIs</td>
</tr>
<tr>
<td>Stages too short / traffic too low</td>
<td>Statistical noise triggers false rollbacks</td>
<td>Minimum 10K requests per stage; extend duration for low-traffic services</td>
</tr>
<tr>
<td>Manual decision loop</td>
<td>Slow response, human error, inconsistent gates</td>
<td>Automate with Argo Rollouts / Flagger / custom controller</td>
</tr>
<tr>
<td>Database incompatibility</td>
<td>Old version crashes on new schema</td>
<td>Expand/contract migrations; dual-write during transition</td>
</tr>
<tr>
<td>Session affinity broken</td>
<td>User flips between versions mid-session</td>
<td>Sticky sessions at router; or design stateless versions</td>
</tr>
<tr>
<td>Canary runs too long</td>
<td>Two versions in prod indefinitely = operational burden</td>
<td>Automate promotion; enforce max canary duration (e.g., 2 hours)</td>
</tr>
</tbody></table>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-is-the-difference-between-a-canary-deployment-and-a-rolling-update">What is the difference between a canary deployment and a rolling update?</h3><p>A rolling update gradually replaces all instances of the old version with the new version, but every user sees the new version at roughly the same pace (the replacement is uniform). A canary deployment routes a <strong>controlled percentage of real traffic</strong> to the new version while keeping the rest on the old version, and <strong>only expands</strong> that percentage after verifying metrics. Rolling updates are built into Kubernetes Deployments; canaries require additional traffic splitting and version-segmented observability.</p>
<h3 id="what-is-the-difference-between-a-canary-deployment-and-a-blue-green-deployment">What is the difference between a canary deployment and a blue-green deployment?</h3><p>Blue-green runs two <strong>complete environments</strong> (blue = current, green = new) and switches <strong>all traffic at once</strong> after validation. Canary runs both versions in the <strong>same environment</strong> and shifts traffic <strong>incrementally</strong>. Blue-green rollback is instant (switch router back); canary rollback is also instant (drop canary % to 0). Blue-green needs double the infrastructure during the switch; canary needs only the extra capacity for the canary slice. Blue-green validates in a staging-like environment; canary validates in <strong>production</strong> with real traffic.</p>
<h3 id="do-i-need-a-service-mesh-to-do-canary-deployments">Do I need a service mesh to do canary deployments?</h3><p>No. A service mesh (Istio, Linkerd) provides sophisticated L4/L7 traffic splitting with mTLS, retries, and observability built in — but it adds significant operational complexity. You can start with:</p>
<ul>
<li>Feature flags (application-level, easiest to adopt).</li>
<li>Ingress controller canary annotations (NGINX, Traefik, ALB — L7, no code changes).</li>
<li>Two Kubernetes Deployments + a Service with weighted routing (basic, limited).
Move to a service mesh when you need cross-service canaries, mutual TLS, or fine-grained fault injection across many services.</li>
</ul>
<h3 id="how-long-should-a-canary-stage-last">How long should a canary stage last?</h3><p>Long enough to collect statistically significant metrics. For error-rate detection at 0.1% threshold, you need ~10,000 requests in that stage (rule of thumb: 3 / target-error-rate). At 1,000 RPS, that&#39;s 10 seconds; at 10 RPS, that&#39;s ~17 minutes. Most teams set a <strong>minimum duration</strong> (e.g., 10–15 minutes) regardless of volume, to catch issues that only appear under sustained load (memory leaks, connection pool exhaustion, cache warming).</p>
<h3 id="can-i-use-canary-deployments-for-frontend-applications">Can I use canary deployments for frontend applications?</h3><p>Yes, but with caveats. Frontend assets (JS bundles, CSS) are immutable and cached by CDNs. You cannot &quot;gradually deploy&quot; a single bundle — the browser either loads v1 or v2. Common patterns:</p>
<ul>
<li><strong>Edge-based traffic splitting</strong> (Cloudflare Workers, Vercel Edge, CloudFront Functions): route by cookie/header to different static asset versions.</li>
<li><strong>Feature flags in the bundle</strong>: ship both code paths, toggle at runtime.</li>
<li><strong>Separate deployments per version</strong>: deploy v1 and v2 to different paths, route via edge logic.
CDN cache invalidation takes 30–300 seconds, so rollback is not instant. Hydration mismatches (SSR from v1, client JS from v2) are a real risk — monitor hydration errors specifically.</li>
</ul>
</section>
<section><h2 id="summary"><a href="#summary" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Summary</h2><p>A canary deployment is a progressive-delivery pattern that limits the blast radius of a release by exposing the new version to a small, measurable slice of production traffic first. It requires three pillars: <strong>version-segmented observability</strong> (metrics per version), <strong>traffic splitting</strong> (feature flags, ingress, or service mesh), and <strong>automated decision loops</strong> (promote/rollback based on SLOs). When these are in place, canary deployments let teams deploy frequently with confidence — the data, not the calendar, decides whether a release proceeds. For teams not yet ready, rolling updates and blue-green deployments remain valid, simpler alternatives. The progression is: rolling update → blue-green → canary → automated progressive delivery. Each step adds operational maturity and reduces release risk.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>What is an API Gateway? A practical guide for developers</title>
      <link>https://debod.id/en/posts/api-gateway-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/api-gateway-explained/</guid>
      <pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate>
      <category>api</category>
      <category>api</category>
      <category>microservices</category>
      <category>networking</category>
      <category>devops</category>
      <category>backend</category>
      <description>An API gateway is a single front door for all client requests to your back-end services. This guide explains what an API gateway does, the problems it solves, common patterns, and how to choose one.</description>
      <content:encoded><![CDATA[<section><p>An API gateway is a single server that sits between clients and a collection of back-end services, accepting every API request, routing it to the correct service, and returning the response. It exists because, as an application grows from one monolith into many small services, clients need a single, stable address to talk to — and the operator needs a single place to enforce authentication, rate limits, and observability.</p></section>
<section><h2 id="why-you-need-an-api-gateway"><a href="#why-you-need-an-api-gateway" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Why you need an API gateway</h2><p>In a single-service application, the client connects directly to the one server. When you break that application into multiple services (a pattern often called <strong>microservices</strong>), the client would otherwise have to know the address of every service, handle authentication separately for each, retry failed calls, and parse different response shapes. That quickly becomes unworkable.</p>
<p>The API gateway solves several problems at once:</p>
<table>
<thead>
<tr>
<th>Problem</th>
<th>What the gateway does</th>
</tr>
</thead>
<tbody><tr>
<td>Clients must know many service URLs</td>
<td>Provides a single, stable endpoint</td>
</tr>
<tr>
<td>Every service re-implements auth</td>
<td>Centralizes authentication and authorization</td>
</tr>
<tr>
<td>Each service has its own rate-limit policy</td>
<td>Enforces quotas once at the edge</td>
</tr>
<tr>
<td>Cross-cutting concerns (logging, CORS, TLS) duplicated in every service</td>
<td>Applied once at the gateway</td>
</tr>
<tr>
<td>Services change addresses (scale, fail over)</td>
<td>Clients unaffected; gateway handles routing</td>
</tr>
<tr>
<td>Different protocols between client and back end</td>
<td>Gateway translates (REST → gRPC, HTTP → WebSocket)</td>
</tr>
</tbody></table>
<p>The trade-off is that the gateway itself becomes a critical component. If it goes down, requests stop reaching the back end. This is why most production deployments run the gateway as a redundant cluster (multiple instances behind a load balancer) and keep its logic thin.</p>
</section>
<section><h2 id="core-responsibilities"><a href="#core-responsibilities" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Core responsibilities</h2><p>Not every gateway does every job, but most handle some combination of these.</p>
<p><strong>Routing.</strong> The gateway maps each incoming request to a specific back-end service based on the path, host, or header. A request to <code>https://api.example.com/users/42</code> might go to the <code>users</code> service, while <code>https://api.example.com/orders/77</code> goes to the <code>orders</code> service. Clients see a single URL; routing happens server-side.</p>
<p><strong>Authentication and authorization.</strong> Instead of every service verifying API keys or JWT tokens, the gateway validates credentials once. If the token is valid, the gateway forwards the request (often with headers like <code>X-User-Id</code> added) so the back end knows who is calling. The OpenID and OAuth 2.0 standards describe the delegation patterns most gateways implement.</p>
<p><strong>Rate limiting and throttling.</strong> To protect back-end services from overload, the gateway counts requests per client (by API key, IP, or user ID) and rejects or delays excess requests. This prevents a single noisy client from exhausting shared resources.</p>
<p><strong>Request and response transformation.</strong> The gateway can rewrite paths, add or strip headers, convert JSON to XML, translate a REST-style request into a gRPC call, or aggregate multiple back-end calls into a single client response. Aggregation is especially common in mobile apps, where the device would otherwise make dozens of round trips.</p>
<p><strong>Caching.</strong> Some responses are identical for many clients (a public product catalog, a static configuration blob). The gateway can cache these and serve them directly, offloading the back end entirely for the cache-hit case.</p>
<p><strong>Observability.</strong> Because every request passes through the gateway, it is the natural place to collect metrics (latency, error rates, request volume), emit structured logs, and report on traffic patterns. Most gateways expose a Prometheus-compatible metrics endpoint.</p>
<p><strong>Transport security.</strong> The gateway terminates TLS, centralizes certificate management, and (in some setups) requires <strong>mutual TLS (mTLS)</strong> so that calling services must present a client certificate in addition to a token.</p>
</section>
<section><h2 id="the-request-lifecycle"><a href="#the-request-lifecycle" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The request lifecycle</h2><p>A typical request through an API gateway follows this path:</p>
<ol>
<li>The client sends <code>GET https://api.example.com/users/42</code> with an <code>Authorization: Bearer &lt;token&gt;</code> header.</li>
<li>The gateway terminates TLS and parses the request.</li>
<li>The gateway validates the token. If invalid, it returns <code>401 Unauthorized</code> immediately — the back end never sees the request.</li>
<li>The gateway applies rate-limit counters for the calling client. If over the quota, it returns <code>429 Too Many Requests</code>.</li>
<li>The gateway looks up the route (in this case: <code>/users/*</code> → <code>users-service</code>) and forwards the request to a healthy back-end instance.</li>
<li>The back-end service processes the request and returns a response.</li>
<li>The gateway records latency, increments counters, and returns the response to the client.</li>
</ol>
<p>For a well-tuned gateway, steps 2–5 take a few milliseconds. The back-end call dominates the total time.</p>
</section>
<section><h2 id="common-patterns"><a href="#common-patterns" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Common patterns</h2><h3 id="single-gateway-one-team">Single gateway, one team</h3><p>The simplest pattern: one gateway routes everything to many services. Most teams start here. It works well until traffic exceeds what one gateway instance can handle.</p>
<h3 id="gateway-per-region">Gateway per region</h3><p>For globally distributed users, you may run a gateway in every region (for example, one in Frankfurt, one in Singapore, one in Virginia). Each region routes to back-end services in or near its region, reducing latency.</p>
<h3 id="gateway-per-bounded-context">Gateway per Bounded Context</h3><p>In larger organizations, a single shared gateway can become a deployment bottleneck (every team needs every other team&#39;s changes to ship). Splitting into one gateway per bounded context (e.g., a &quot;Payments Gateway&quot; and a &quot;Catalog Gateway&quot;) trades operational complexity for team independence.</p>
<h3 id="bff-back-end-for-front-end">BFF (Back-end for Front-end)</h3><p>When a web app, iOS app, and Android app need different response shapes, you can run a separate gateway for each. Each BFF aggregates and reshapes back-end calls to fit its client&#39;s needs.</p>
<h3 id="edge-gateway">Edge gateway</h3><p>When the gateway lives in a CDN edge network (Cloudflare, Fastly, AWS CloudFront), the back ends are protected by a layer that is geographically close to users and absorbs attacks before they reach your infrastructure. Cloudflare API Gateway, for example, provides JWT validation, schema validation, and OWASP Top 10 protections at the edge.</p>
</section>
<section><h2 id="api-gateway-vs-reverse-proxy-vs-load-balancer"><a href="#api-gateway-vs-reverse-proxy-vs-load-balancer" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>API gateway vs reverse proxy vs load balancer</h2><p>These terms overlap and the boundaries are fuzzy. A useful summary:</p>
<table>
<thead>
<tr>
<th>Component</th>
<th>Primary role</th>
<th>Knows about</th>
</tr>
</thead>
<tbody><tr>
<td>Reverse proxy</td>
<td>Forwards HTTP to one or more servers</td>
<td>URLs, HTTP</td>
</tr>
<tr>
<td>Load balancer</td>
<td>Distributes traffic across instances</td>
<td>TCP/HTTP, health</td>
</tr>
<tr>
<td>API gateway</td>
<td>Routes + enforces policies for API traffic</td>
<td>URLs, headers, auth, rate limits, schemas</td>
</tr>
</tbody></table>
<p>In practice, the same software (Nginx, Envoy, HAProxy, Kong) often plays all three roles at once. The distinction is in the <em>configuration</em> and the <em>features enabled</em>, not necessarily the binary.</p>
</section>
<section><h2 id="popular-implementations"><a href="#popular-implementations" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Popular implementations</h2><table>
<thead>
<tr>
<th>Gateway</th>
<th>Type</th>
<th>License</th>
<th>Notes</th>
</tr>
</thead>
<tbody><tr>
<td>Nginx</td>
<td>Reverse proxy + gateway</td>
<td>BSD-2-Clause</td>
<td>The most widely deployed web server; widely used as a lightweight API gateway through its <code>proxy_pass</code> and module ecosystem. The paid <strong>NGINX Plus</strong> product adds advanced load balancing and an API management layer.</td>
</tr>
<tr>
<td>Kong</td>
<td>API gateway</td>
<td>Apache 2.0 (open-source core)</td>
<td>Built on Nginx, focused on API management: auth, rate limiting, plugins.</td>
</tr>
<tr>
<td>Envoy</td>
<td>Service proxy</td>
<td>Apache 2.0</td>
<td>Originally from Lyft; common in service-mesh (Istio) deployments.</td>
</tr>
<tr>
<td>HAProxy</td>
<td>Load balancer + proxy</td>
<td>GPL / commercial</td>
<td>Very fast, well-tested; common as a Kubernetes Ingress.</td>
</tr>
<tr>
<td>Cloudflare API Gateway</td>
<td>Managed edge service</td>
<td>Proprietary SaaS</td>
<td>JWT, mTLS, schema validation, OWASP Top 10 protection at the edge; part of the Cloudflare API Shield product.</td>
</tr>
<tr>
<td>AWS API Gateway</td>
<td>Managed</td>
<td>Proprietary</td>
<td>Pairs with Lambda and other AWS services; charges per request.</td>
</tr>
<tr>
<td>Azure API Management</td>
<td>Managed</td>
<td>Proprietary</td>
<td>Full API lifecycle (publish, monetize, analyze).</td>
</tr>
<tr>
<td>Apache APISIX</td>
<td>API gateway</td>
<td>Apache 2.0</td>
<td>Cloud-native, plugin-based, often compared to Kong.</td>
</tr>
</tbody></table>
</section>
<section><h2 id="when-you-probably-do-not-need-one"><a href="#when-you-probably-do-not-need-one" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>When you probably do not need one</h2><p>A gateway adds a network hop, a new component to operate, and a new failure mode. For a single-service application, this is unnecessary. You only need a gateway once you have multiple services, multiple clients with different needs, or a need for centralized rate limiting / auth that you would otherwise repeat in every service.</p>
<p>A reasonable rule of thumb: <strong>start with a reverse proxy (Nginx, Caddy) and add gateway features (auth, rate limiting) only when you actually need them.</strong> Reach for a full API gateway when you have at least three back-end services or when you must enforce policies consistently across teams.</p>
</section>
<section><h2 id="what-to-look-for-when-choosing-a-gateway"><a href="#what-to-look-for-when-choosing-a-gateway" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What to look for when choosing a gateway</h2><ol>
<li><strong>Performance overhead.</strong> A gateway that adds 50 ms to every request is a problem; one that adds 1–2 ms is fine. Benchmark before committing.</li>
<li><strong>Plugin ecosystem.</strong> Kong and APISIX have rich plugin libraries; Nginx requires you to wire most features yourself.</li>
<li><strong>Dynamic configuration.</strong> Can you change routes and policies without reloading? For high-traffic services, a config reload that drops connections is unacceptable.</li>
<li><strong>Observability.</strong> Does it emit Prometheus metrics, OpenTelemetry traces, and structured logs out of the box?</li>
<li><strong>Authentication options.</strong> JWT, OAuth 2.0, mTLS, API keys, custom — what does it support natively?</li>
<li><strong>Where it runs.</strong> In your cluster, at the edge, or as a managed service? Each has trade-offs in latency, control, and operational burden.</li>
</ol>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="is-an-api-gateway-the-same-as-a-service-mesh">Is an API gateway the same as a service mesh?</h3><p>No. An API gateway handles <strong>north-south traffic</strong> — requests from outside your system (clients, mobile apps, partners) to your services. A service mesh (Istio, Linkerd) handles <strong>east-west traffic</strong> — requests between your services. They solve different problems but the underlying proxies (Envoy, Nginx) are often the same.</p>
<h3 id="does-an-api-gateway-replace-my-load-balancer">Does an API gateway replace my load balancer?</h3><p>Usually not. A gateway can include load-balancing features, but for raw TCP/UDP traffic or for distributing across many instances of a single service, a dedicated load balancer is still common. Many architectures use both: a load balancer in front of the gateway, then the gateway in front of the services.</p>
<h3 id="can-an-api-gateway-add-latency">Can an API gateway add latency?</h3><p>Yes — every request now goes through an extra hop. A well-tuned gateway adds 1–5 ms. A poorly configured one (deep plugin chains, synchronous external auth calls, no connection pooling) can add tens to hundreds of milliseconds. Measure under load, not just at idle.</p>
<h3 id="should-the-api-gateway-also-handle-caching">Should the API gateway also handle caching?</h3><p>For some responses, yes. Caching public or near-static data at the gateway is a common win, especially for mobile clients. But for highly dynamic or per-user data, caching at the gateway can introduce correctness bugs (stale data) and is usually better handled by the back-end service or a dedicated cache (Redis, Varnish).</p>
<h3 id="what-happens-if-the-api-gateway-goes-down">What happens if the API gateway goes down?</h3><p>If the gateway is a single instance, the entire API goes down with it. Production deployments run multiple gateway instances behind a load balancer, with health checks so a failed instance is removed automatically. The gateway should be stateless (no local session data) so any instance can handle any request.</p>
</section>
<section><h2 id="verdict"><a href="#verdict" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Verdict</h2><p>An API gateway is a useful piece of architecture once you have multiple back-end services and want consistent cross-cutting policies. Start simple: a reverse proxy with rate limiting and a single auth check is enough for many teams. Reach for a full gateway — Kong, Envoy, Cloudflare API Gateway, or a managed service — once you have a clear pain point that it solves: too many duplicated auth checks, inconsistent rate limits, the need for request aggregation, or a need to enforce schema validation centrally. A gateway is a tool, not a goal; the goal is to keep your services focused on business logic and your clients talking to one stable, secure endpoint.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>What is YAML? The complete guide to YAML syntax and use cases</title>
      <link>https://debod.id/en/posts/yaml-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/yaml-explained/</guid>
      <pubDate>Wed, 02 Sep 2026 00:00:00 GMT</pubDate>
      <category>devops</category>
      <category>devops</category>
      <category>configuration</category>
      <category>docker</category>
      <category>kubernetes</category>
      <category>yaml</category>
      <description>YAML (YAML Ain&apos;t Markup Language) is a human-readable data serialization format used for configuration files, CI/CD pipelines, and cross-language data exchange. This guide explains YAML syntax, key features, common tools, and security pitfalls.</description>
      <content:encoded><![CDATA[<section><p>YAML — short for &quot;YAML Ain&#39;t Markup Language&quot; — is a human-readable data serialization format. Unlike markup languages designed to describe document structure, YAML is designed to represent data in a way that people can read and write directly. It is widely used for configuration files, CI/CD pipelines, container orchestration, and exchanging data between programming languages. This guide covers what YAML is, how its syntax works, where it is used, and the security pitfalls to watch out for.</p></section>
<section><h2 id="what-yaml-is-and-why-it-exists"><a href="#what-yaml-is-and-why-it-exists" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What YAML is and why it exists</h2><p>YAML was first published in 2001 and its current stable version is YAML 1.2.2 (October 2021). It was designed with five core goals in order of priority: readability by humans, compatibility with the data structures of dynamic programming languages, portability between programming languages, support for one-pass processing, and expressiveness. The result is a format that looks clean on screen and maps naturally to the objects, arrays, and primitives that programs work with — making it far more pleasant to write than JSON for hand-edited configuration.</p>
<p>YAML sits between JSON (machine-optimized) and natural language (human-optimized). Valid JSON is always valid YAML, but YAML adds features that JSON lacks: comments, multi-line strings, anchors and aliases for reusing values, and a more forgiving syntax that lets you omit quotes in many cases.</p>
</section>
<section><h2 id="yaml-syntax-basics"><a href="#yaml-syntax-basics" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>YAML syntax basics</h2><h3 id="key-value-pairs-and-scalars">Key-value pairs and scalars</h3><p>The fundamental unit in YAML is a key-value pair. A key is a string, followed by a colon and a space, then a value:</p>
<pre><code class="language-yaml">app_name: my-service
version: &quot;1.0&quot;
port: 8080
</code></pre>
<p>Keys are case-sensitive. Values can be strings, numbers, booleans, or null.</p>
<p>Strings do not always need quotes. YAML treats an unquoted word as a string:</p>
<pre><code class="language-yaml">environment: production
</code></pre>
<p>Quotes are required when a string contains special characters that YAML might misinterpret, or when you want to preserve leading or trailing spaces:</p>
<pre><code class="language-yaml">message: &quot;Hello, world!&quot;
multiline: &quot;First line\nSecond line&quot;
</code></pre>
<p>Single quotes preserve the literal value inside them (no escape sequences); double quotes allow escape sequences.</p>
<h3 id="numbers-booleans-and-null">Numbers, booleans, and null</h3><pre><code class="language-yaml">timeout: 30          # integer
rate: 3.14            # float
debug: true           # boolean (also: on, yes)
verbose: false        # boolean (also: off, no)
record: null          # null (also: ~)
</code></pre>
<h3 id="lists-arrays">Lists (arrays)</h3><p>Lists use a dash and a space at the start of each item:</p>
<pre><code class="language-yaml">servers:
  - web-1
  - web-2
  - web-3
</code></pre>
<p>Or use inline syntax (JSON-compatible):</p>
<pre><code class="language-yaml">ports: [80, 443, 8080]
</code></pre>
<h3 id="dictionaries-objects">Dictionaries (objects)</h3><p>Nested dictionaries use indentation (two spaces is the convention — never tabs):</p>
<pre><code class="language-yaml">database:
  host: db.example.com
  port: 5432
  name: myapp
  ssl: true
</code></pre>
<h3 id="comments">Comments</h3><p>YAML supports comments that start with a <code>#</code> and continue to the end of the line:</p>
<pre><code class="language-yaml"># Production configuration
environment: production
max_connections: 100  # raised from default of 10
</code></pre>
<p>Comments are stripped when YAML is parsed, so they never appear in the resulting data structure.</p>
</section>
<section><h2 id="multi-line-strings"><a href="#multi-line-strings" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Multi-line strings</h2><p>YAML has two ways to write a string that spans multiple lines:</p>
<p><strong>Folded style</strong> (<code>&gt;</code>) treats newlines as spaces, producing a single logical line:</p>
<pre><code class="language-yaml">description: &gt;
  This is a long description that spans
  multiple lines. YAML will collapse these
  into a single line of text.
</code></pre>
<p><strong>Literal style</strong> (<code>|</code>) preserves newlines exactly as written:</p>
<pre><code class="language-yaml">script: |
  #!/bin/bash
  echo &quot;Starting service&quot;
  ./start.sh
</code></pre>
<p>Both styles remove the trailing newline unless you explicitly add it back.</p>
</section>
<section><h2 id="anchors-and-aliases"><a href="#anchors-and-aliases" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Anchors and aliases</h2><p>YAML lets you define a value once with an anchor (<code>&amp;</code>) and reuse it with an alias (<code>*</code>):</p>
<pre><code class="language-yaml">defaults: &amp;defaults
  timeout: 30
  retries: 3

production:
  &lt;&lt;: *defaults
  timeout: 60
</code></pre>
<p>The <code>&lt;&lt;</code> merge key copies all key-value pairs from the aliased node. This is useful for base configurations that are shared across environments.</p>
</section>
<section><h2 id="where-yaml-is-used"><a href="#where-yaml-is-used" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Where YAML is used</h2><p>YAML appears in dozens of common developer tools:</p>
<ul>
<li><strong>Docker Compose</strong> — <code>docker-compose.yml</code> defines multi-container applications.</li>
<li><strong>GitHub Actions</strong> — workflow files in <code>.github/workflows/</code> are YAML.</li>
<li><strong>Kubernetes</strong> — every resource manifest (Deployment, Service, ConfigMap, Ingress) is YAML.</li>
<li><strong>Ansible</strong> — playbooks and roles use YAML to describe desired state.</li>
<li><strong>CircleCI, GitLab CI, Travis CI</strong> — all use YAML for pipeline definitions.</li>
<li><strong>Prometheus</strong> — <code>prometheus.yml</code> configures scrape targets and rules.</li>
<li><strong>Terraform</strong> (state files and some configurations) — though Terraform also uses HCL, Terraform state files are JSON, and provider configurations can be YAML in some contexts.</li>
<li><strong>OpenAPI / Swagger</strong> — API specifications are written in YAML.</li>
<li><strong>RSS feeds and many config files</strong> — YAML is a common choice for human-edited structured data.</li>
</ul>
</section>
<section><h2 id="yaml-vs-json-vs-toml"><a href="#yaml-vs-json-vs-toml" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>YAML vs JSON vs TOML</h2><table>
<thead>
<tr>
<th>Feature</th>
<th>YAML</th>
<th>JSON</th>
<th>TOML</th>
</tr>
</thead>
<tbody><tr>
<td>Human-readable</td>
<td>High</td>
<td>Medium</td>
<td>High</td>
</tr>
<tr>
<td>Comments</td>
<td>Yes</td>
<td>No</td>
<td>Yes</td>
</tr>
<tr>
<td>Multiline strings</td>
<td>Yes</td>
<td>No</td>
<td>No (requires escaping)</td>
</tr>
<tr>
<td>Anchors/aliases</td>
<td>Yes</td>
<td>No</td>
<td>No</td>
</tr>
<tr>
<td>Schema</td>
<td>No (schema-less)</td>
<td>Strict</td>
<td>Strict</td>
</tr>
<tr>
<td>Typical use</td>
<td>Config files, CI/CD</td>
<td>APIs, data exchange</td>
<td>Config files</td>
</tr>
</tbody></table>
<p>TOML (Tom&#39;s Obvious, Minimal Language) is popular for application-level config (Rust&#39;s <code>Cargo.toml</code>). JSON is the standard for data interchange. YAML wins for configuration that humans read and write frequently.</p>
</section>
<section><h2 id="common-yaml-pitfalls-and-security-notes"><a href="#common-yaml-pitfalls-and-security-notes" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Common YAML pitfalls and security notes</h2><h3 id="indentation-matters">Indentation matters</h3><p>YAML uses indentation to define structure. Mixing tabs and spaces, or inconsistent indentation, is a common source of errors. Always use spaces (two is the community convention) and configure your editor to show whitespace.</p>
<h3 id="type-coercion-is-implicit">Type coercion is implicit</h3><p>YAML parsers may coerce unquoted values:</p>
<pre><code class="language-yaml">yes: no      # if parser treats &#39;yes&#39; as boolean, this is contradictory
</code></pre>
<p>Unquoted <code>yes</code>, <code>no</code>, <code>on</code>, <code>off</code>, <code>true</code>, <code>false</code>, <code>null</code> are interpreted as their respective types. Quote values when they are meant to be literal strings.</p>
<h3 id="unsafe-yaml-loading">Unsafe YAML loading</h3><p>Python&#39;s <code>yaml.load()</code> with an unsafe loader, Ruby&#39;s <code>YAML.load</code>, and similar can execute arbitrary Python/Ruby code through YAML tags:</p>
<pre><code class="language-python"># Dangerous — never do this with untrusted input:
yaml.load(user_yaml, Loader=yaml.SafeLoader)
</code></pre>
<p>Always use a safe loader when parsing untrusted YAML. Many YAML libraries have a safe mode by default; only use the unsafe loader for trusted, locally-produced configuration.</p>
<h3 id="template-injection-in-ci-cd">Template injection in CI/CD</h3><p>CI/CD systems (GitHub Actions, GitLab CI) run YAML workflows. If part of the YAML is constructed from user input, a template injection attack is possible. For example, a workflow that reads an issue title and inserts it into a YAML step without sanitization can break out of the string and inject additional YAML keys.</p>
<h3 id="confusion-with-json">Confusion with JSON</h3><p>Since valid JSON is valid YAML, a YAML parser will silently parse a <code>.json</code> file. This is useful but can also be surprising: a misnamed file with YAML syntax may be interpreted as JSON or vice versa, depending on the tool.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-is-yaml-used-for-in-devops">What is YAML used for in devops?</h3><p>In devops, YAML is the standard format for configuration and pipeline definitions. Docker Compose, Kubernetes manifests, GitHub Actions, GitLab CI, Ansible playbooks, and Helm charts all use YAML. Its human-readable format makes it ideal for version-controlled configuration that teams review in pull requests.</p>
<h3 id="how-do-i-validate-a-yaml-file">How do I validate a YAML file?</h3><p>Use a YAML parser in your language of choice — <code>yaml</code> npm package, <code>PyYAML</code> for Python, <code>js-yaml</code> for JavaScript, or the <code>yq</code> command-line tool. Online validators like yamllint.com can also catch syntax errors. Always validate YAML before applying it to a production system, especially CI/CD pipelines and Kubernetes manifests.</p>
<h3 id="what-are-yaml-anchors-and-aliases">What are YAML anchors and aliases?</h3><p>Anchors (<code>&amp;name</code>) let you define a value once. Aliases (<code>*name</code>) reuse that value elsewhere in the same document. The merge key (<code>&lt;&lt;</code>) spreads an alias&#39;s key-value pairs into a mapping. Anchors are useful for reducing repetition in configuration files that share defaults across environments.</p>
<h3 id="is-yaml-secure-to-parse">Is YAML secure to parse?</h3><p>YAML parsers can be unsafe if you load untrusted input with a full-featured loader that can instantiate arbitrary objects. Always use a safe loader (<code>SafeLoader</code> in PyYAML, <code>SafeConstructor</code> in Ruby) when processing YAML from user input or external sources. Safe parsing prevents deserialization attacks where a malicious YAML payload executes code through specially crafted tags.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>What is web accessibility (a11y)? WCAG principles and practical techniques</title>
      <link>https://debod.id/en/posts/web-accessibility-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/web-accessibility-explained/</guid>
      <pubDate>Wed, 02 Sep 2026 00:00:00 GMT</pubDate>
      <category>frontend</category>
      <category>web</category>
      <category>frontend</category>
      <category>html</category>
      <description>Web accessibility (a11y) means designing sites that anyone can use, regardless of ability. This guide explains the four WCAG principles — perceivable, operable, understandable, robust — and practical techniques: semantic HTML, keyboard navigation, alt text, and color contrast.</description>
      <content:encoded><![CDATA[<section><p>Web accessibility, often shortened to <strong>a11y</strong>, means designing and building sites so that as many people as possible can use them, including people with limited vision, hearing, movement, or cognitive ability. The name is a numeronym: &quot;a&quot;, then 11 characters, then &quot;y&quot;. Accessibility is not a checklist you bolt on at the end — it is a design and development practice. When done well it also benefits people with situational disabilities, such as someone reading in bright sunlight or using the phone one-handed.</p></section>
<section><h2 id="why-accessibility-matters"><a href="#why-accessibility-matters" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Why accessibility matters</h2><p>The web was designed to work for all people, whatever their hardware, software, language, location, or ability. For many people with disabilities, technology is not a convenience but the only way to access information and services. Accessibility removes the barriers that would otherwise lock them out.</p>
<p>It is also a legal and business concern in many regions: rules such as the European Accessibility Act and the US Americans with Disabilities Act increasingly require accessible sites, and most of those rules point to the <strong>Web Content Accessibility Guidelines (WCAG)</strong> as the measuring standard.</p>
</section>
<section><h2 id="the-four-wcag-principles-pour"><a href="#the-four-wcag-principles-pour" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The four WCAG principles: POUR</h2><p>WCAG is organized around four principles. Every accessibility requirement falls under one of them, and the acronym is <strong>POUR</strong>:</p>
<ul>
<li><strong>Perceivable</strong> — users can perceive the information. Content must be presentable in ways they can sense (not only through sight or hearing).</li>
<li><strong>Operable</strong> — users can operate the interface. Navigation and controls must work for a keyboard, a mouse, a touch screen, or a switch device.</li>
<li><strong>Understandable</strong> — users can understand the information and how the interface behaves. Content is readable and predictable.</li>
<li><strong>Robust</strong> — content works with current and future user agents, including assistive technologies such as screen readers.</li>
</ul>
<p>Under each principle sit guidelines, and under each guideline sit testable <strong>success criteria</strong> at three conformance levels: <strong>A</strong> (basic), <strong>AA</strong> (the common legal target), and <strong>AAA</strong> (the highest). Many sites aim for WCAG AA conformance. WCAG versions are backward compatible: content that meets WCAG 2.2 also meets WCAG 2.1 and 2.0.</p>
</section>
<section><h2 id="start-with-semantic-html"><a href="#start-with-semantic-html" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Start with semantic HTML</h2><p>A large share of accessibility is free if you use the right HTML element for the right job. Correct semantic HTML gives screen readers and browser navigation a page&#39;s structure for free: <code>nav</code>, <code>main</code>, <code>article</code>, <code>aside</code>, <code>header</code>, <code>footer</code>, and <code>section</code> define landmarks; <code>h1</code> through <code>h6</code> define a heading hierarchy; <code>a</code> gives you a real link; <code>button</code> gives you a focusable, activatable control.</p>
<pre><code class="language-html">&lt;header&gt;
  &lt;nav aria-label=&quot;Main&quot;&gt;…&lt;/nav&gt;
&lt;/header&gt;
&lt;main&gt;
  &lt;h1&gt;Page title&lt;/h1&gt;
  &lt;p&gt;Content…&lt;/p&gt;
&lt;/main&gt;
</code></pre>
<p>Problems appear when developers replace these with styled <code>div</code>s. A <code>div</code> carries no semantic meaning, so a screen reader user hears nothing useful about what it is. Prefer native elements, add a proper heading hierarchy (one <code>h1</code> per page, then <code>h2</code>, <code>h3</code> in order), and reserve ARIA for cases where there is no native equivalent.</p>
</section>
<section><h2 id="make-everything-keyboard-accessible"><a href="#make-everything-keyboard-accessible" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Make everything keyboard accessible</h2><p>Keyboard accessibility is part of the minimum requirements. People who cannot use a mouse — because they have a motor impairment, use speech recognition, or simply prefer the keyboard — rely on <code>Tab</code>, <code>Shift+Tab</code>, <code>Enter</code>, and arrow keys to move through a page. The <code>Tab</code> key order must follow a logical reading order, and every interactive element must be reachable and operable with the keyboard.</p>
<p>Checklist:</p>
<ul>
<li>Every link and control is focusable and can be triggered with the keyboard.</li>
<li>A visible <strong>focus indicator</strong> shows where you are (do not replace it with <code>outline: none</code>).</li>
<li>No interactive element is reachable only by hover or drag.</li>
<li>Focus moves sensibly when content changes, for example after loading new results in a search box.</li>
</ul>
</section>
<section><h2 id="write-good-alt-text-and-captions"><a href="#write-good-alt-text-and-captions" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Write good alt text and captions</h2><p>Images need text alternatives so their meaning is available to screen readers and to users with images disabled. The rule is: if the image conveys meaning, write a short alt text describing that meaning; if the image is purely decorative, use an empty <code>alt=&quot;&quot;</code> so it is skipped.</p>
<pre><code class="language-html">&lt;img src=&quot;architecture-diagram.png&quot; alt=&quot;How the request flows from browser to database&quot;&gt;
&lt;img src=&quot;spacer.png&quot; alt=&quot;&quot;&gt;
</code></pre>
<p>For images that include text, the alt text should repeat that text. Videos need captions for deaf and hard-of-hearing users, and audio-only content needs a text transcript. Provide text for anything you cannot convey through images alone.</p>
</section>
<section><h2 id="ensure-sufficient-color-contrast"><a href="#ensure-sufficient-color-contrast" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Ensure sufficient color contrast</h2><p>Color should never be the only way information is conveyed. Links that differ from body text only by color, for example, are invisible to color-blind users — differentiate them with an underline or another cue as well.</p>
<p>Text also needs sufficient <strong>contrast ratio</strong> against its background. WCAG AA requires at least <strong>4.5:1</strong> for normal text and <strong>3:1</strong> for large text (18pt and larger, or 14pt bold and larger). Non-text elements such as the borders of form inputs and UI component outlines need at least <strong>3:1</strong> against adjacent colors.</p>
</section>
<section><h2 id="use-aria-only-when-needed"><a href="#use-aria-only-when-needed" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Use ARIA only when needed</h2><p>ARIA (Accessible Rich Internet Applications) adds semantics that HTML does not provide for complex widgets and dynamic content — roles such as <code>role=&quot;dialog&quot;</code> or <code>role=&quot;tablist&quot;</code>, and properties such as <code>aria-label</code> or <code>aria-live</code> for announcing dynamic updates. But ARIA is a supplement, not a substitute. The web.dev guidance is to prefer correct HTML semantics first; use ARIA for the cases native HTML cannot express, and always test what you add, because misused ARIA can be worse than none at all.</p>
</section>
<section><h2 id="test-both-automated-and-manual"><a href="#test-both-automated-and-manual" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Test, both automated and manual</h2><p>Automated tools (such as axe-core, Lighthouse&#39;s accessibility audit, and W3C&#39;s validator) catch a meaningful share of issues — missing alt text, low contrast, unlabeled form fields. They do not catch everything, because much of accessibility is about meaning and behavior, which only a human can judge.</p>
<p>A reliable manual pass:</p>
<ol>
<li>Tab through the whole page with the keyboard only — is everything reachable and understandable?</li>
<li>Run a screen reader over the main flows — does it announce headings, links, and form fields correctly?</li>
<li>Check content under a browser zoom of 200% and on a narrow mobile viewport.</li>
<li>Review contrast for the color palette.</li>
</ol>
<p>Accessibility is continuous: every new component and every refactor is a chance to keep the site usable for everyone.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-does-a11y-mean">What does a11y mean?</h3><p>A11y is a numeronym for &quot;accessibility&quot;: the letter &quot;a&quot;, 11 letters, then &quot;y&quot;. In web development it refers to building sites that are usable by as many people as possible, including people with vision, hearing, movement, or cognitive disabilities.</p>
<h3 id="what-are-the-four-principles-of-wcag">What are the four principles of WCAG?</h3><p>They are perceivable, operable, understandable, and robust (POUR). Perceivable means users can sense the content; operable means they can use the controls; understandable means the content and behavior are clear; robust means content works with current and future assistive technologies.</p>
<h3 id="is-wcag-aa-or-aaa-the-target">Is WCAG AA or AAA the target?</h3><p>Most organizations and laws target WCAG AA, which is a middle conformance level that addresses the most common accessibility barriers. AAA is the highest level but is not always achievable or appropriate for every piece of content.</p>
<h3 id="does-correct-html-matter-for-accessibility">Does correct HTML matter for accessibility?</h3><p>Yes, a great deal. Semantic elements such as <code>nav</code>, <code>main</code>, <code>h1</code>–<code>h6</code>, <code>a</code>, and <code>button</code> give assistive technologies a page&#39;s structure for free, and native controls are keyboard accessible by default. Styled <code>div</code>s carry no such meaning, so they require extra work and ARIA to be accessible.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>What is unit testing? How to write, structure, and run unit tests</title>
      <link>https://debod.id/en/posts/unit-testing-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/unit-testing-explained/</guid>
      <pubDate>Wed, 02 Sep 2026 00:00:00 GMT</pubDate>
      <category>programming</category>
      <category>devops</category>
      <category>programming</category>
      <category>javascript</category>
      <category>automation</category>
      <description>Unit testing is the practice of testing the smallest testable parts of a program — functions, methods, or classes — in isolation. Learn what a unit test is, the arrange-act-assert structure, test doubles and mocking, the testing pyramid, and best practices for writing good unit tests.</description>
      <content:encoded><![CDATA[<section><p>A unit test is an automated test that exercises the smallest testable part of a program — typically a single function, method, or class — in isolation, and checks that the code produces the expected output for a given input. Unit testing is the practice of writing and running many such tests so that you can catch regressions early, document how your code is meant to be used, and refactor with confidence. The idea dates back to the early days of software engineering and was popularised in its modern form in 1997, when Kent Beck and Erich Gamma released JUnit, a unit-testing framework for Java whose design was quickly ported to nearly every mainstream language.</p>
<p>Unlike a manual check — running the program, typing values, and eyeballing the result — a unit test is a piece of code that runs other code and reports pass or fail automatically. It can be executed thousands of times during development, integrated into a CI pipeline, and run again on every commit. If the behavior under test changes unexpectedly, the test fails and tells you exactly which case broke and where.</p></section>
<section><h2 id="what-counts-as-a-unit"><a href="#what-counts-as-a-unit" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What counts as a &quot;unit&quot;</h2><p>The &quot;unit&quot; in unit testing is deliberately the smallest meaningful piece of code you can test on its own. In practice this usually means:</p>
<ul>
<li>A pure function, such as <code>addTax(price, rate)</code> or <code>slugify(title)</code>.</li>
<li>A single method on a class, such as <code>Cart.add(item)</code> updating the running total.</li>
<li>A small module or component whose only collaborators are passed in as arguments.</li>
</ul>
<p>The point of the unit boundary is isolation. A unit test should not talk to a real database, hit the network, read from the file system, or depend on a specific wall-clock time. When those external systems are required, you reach for integration tests instead and stub the boundary so the unit test stays fast and deterministic.</p>
</section>
<section><h2 id="the-arrange-act-assert-structure"><a href="#the-arrange-act-assert-structure" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The arrange–act–assert structure</h2><p>Most unit tests follow a simple three-step shape that web.dev&#39;s testing course calls <em>arrange, act, assert</em>:</p>
<ul>
<li><strong>Arrange</strong> — set up the inputs, the system under test, and any collaborators the function needs. This is where you create the object, prepare the arguments, and decide what mocks to install.</li>
<li><strong>Act</strong> — invoke the function or method under test exactly once. If you find yourself calling the code many times in one test, that is usually a sign the test is checking more than one thing.</li>
<li><strong>Assert</strong> — compare the actual result against the expected one. The test passes if the assertion holds; it fails otherwise, with a clear message pointing to the difference.</li>
</ul>
<p>Keeping these three steps distinct — and naming the cases so the failure tells you the scenario, not just the line number — is what makes unit tests useful as documentation of the code&#39;s intended behavior.</p>
</section>
<section><h2 id="test-doubles-mocks-stubs-fakes-and-spies"><a href="#test-doubles-mocks-stubs-fakes-and-spies" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Test doubles: mocks, stubs, fakes, and spies</h2><p>When the code under test depends on something you do not want to exercise in a unit test — a database, an HTTP client, the system clock, a payment gateway — you replace that dependency with a <em>test double</em>. Wikipedia&#39;s article on unit testing groups these under the umbrella of test doubles; in practice the four most common kinds are:</p>
<ul>
<li><strong>Stub</strong> — returns canned answers to specific calls. Used to force the code down a known path (for example, making a &quot;fetch user&quot; call always return a fixed user object).</li>
<li><strong>Mock</strong> — a stub that also records how it was called, so the test can assert that the function called the dependency the right number of times with the right arguments.</li>
<li><strong>Fake</strong> — a lightweight working implementation, such as an in-memory database, that behaves realistically enough for the test but does not touch real infrastructure.</li>
<li><strong>Spy</strong> — wraps a real implementation and records the calls made to it, useful when you want to keep the original behavior but still observe how it was used.</li>
</ul>
<p>Mocking is a powerful tool, but it has a sharp edge. As the web.dev article on what to test warns, heavily mocking dependencies means you are testing your mocks, not your code. The best unit tests minimise mocking, prefer fakes over hand-rolled stubs where possible, and only mock at boundaries that are genuinely slow, non-deterministic, or external.</p>
</section>
<section><h2 id="the-testing-pyramid"><a href="#the-testing-pyramid" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The testing pyramid</h2><p>Tests come in many sizes. web.dev describes a common <em>testing pyramid</em> that places unit tests at the wide base, integration tests in the middle, and end-to-end (E2E) tests at the narrow top. The shape is intentional:</p>
<ul>
<li><strong>Unit tests</strong> are cheap to write, fast to run, and pinpoint exactly which unit broke. Because they are fast you can afford hundreds or thousands of them.</li>
<li><strong>Integration tests</strong> combine a few units and verify they work together, often using real databases, real HTTP calls to a local server, or fakes. They are slower and fewer.</li>
<li><strong>End-to-end tests</strong> drive the whole system from the outside — for example, opening a real browser, logging in as a test user, and clicking through a checkout flow. They are powerful but slow, flaky, and expensive to maintain, so you write fewer of them.</li>
</ul>
<p>The pyramid is a guideline rather than a law. Different codebases use different shapes — testing diamonds, ice cones, trophies — but the core idea is the same: lots of small fast tests, a moderate number of medium tests, and a small set of high-value end-to-end tests. Unit tests form the foundation because they are the cheapest way to catch the largest class of bugs.</p>
</section>
<section><h2 id="test-driven-development"><a href="#test-driven-development" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Test-driven development</h2><p>Test-driven development (TDD) is a workflow in which unit tests drive the design of the code, not the other way around. Wikipedia describes the cycle as:</p>
<ol>
<li>Write a failing unit test for the next small piece of behavior you want.</li>
<li>Write the simplest production code that makes the test pass.</li>
<li>Refactor the code and the test together, removing duplication and improving names.</li>
<li>Repeat.</li>
</ol>
<p>The benefit is not that every test is written first, but that every line of production code exists because a test demanded it. This makes the test suite a thorough specification of what the code does, and it forces the author to think about inputs, outputs, and edge cases before settling on an implementation. TDD is one common way to produce a healthy unit test suite, but it is not the only one — many teams write tests after the code and still keep them passing.</p>
</section>
<section><h2 id="what-makes-a-good-unit-test"><a href="#what-makes-a-good-unit-test" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What makes a good unit test</h2><p>The web.dev article on what to test summarises the qualities of a good unit test:</p>
<ul>
<li>It tests one thing, so a failure tells you exactly what regressed.</li>
<li>It is fast, so the whole suite can run on every save and in CI.</li>
<li>It is deterministic — given the same inputs and the same code, it always produces the same result. No reliance on time, randomness, network, or shared state between tests.</li>
<li>It is independent of other tests; the order in which tests run must not change the outcome.</li>
<li>It uses clear, descriptive names so a failure in the test report reads almost like a sentence: &quot;Cart.applyDiscount — zero percent discount leaves total unchanged.&quot;</li>
</ul>
<p>What a good unit test does <em>not</em> do is assert against implementation details. A test should pin down behavior, not internal structure — so it survives a refactor that reorganises the code without changing what the code does. A test that breaks every time a private variable is renamed is coupling the test to the implementation, not the behavior.</p>
</section>
<section><h2 id="where-unit-testing-fits-with-the-rest-of-qa"><a href="#where-unit-testing-fits-with-the-rest-of-qa" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Where unit testing fits with the rest of QA</h2><p>Unit testing is one layer of a healthy quality strategy, not a replacement for the others. Static analysis tools catch a different class of problems (style, type errors, unused code) without running the program. Integration tests catch mismatches between components. End-to-end tests catch the rare cross-cutting failures that only show up when the whole stack is wired up. Manual exploratory testing catches the issues that no scripted check can anticipate.</p>
<p>The practical recommendation from web.dev is to start with unit tests, because they give the most signal for the least cost, then add integration and E2E tests as the system grows. The shape of any project&#39;s test suite should reflect the risks of that project: a payments API will skew heavily toward integration tests, a UI-heavy product will need more visual and end-to-end coverage, and a library will live almost entirely on its unit tests.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-is-the-difference-between-a-unit-test-and-an-integration-test">What is the difference between a unit test and an integration test?</h3><p>A unit test exercises a single function, method, or class in isolation, with any external collaborators replaced by test doubles. An integration test exercises several units together and lets them talk to each other — and often to real or realistic infrastructure such as a test database — to confirm that the combined behavior is correct. Unit tests are cheaper and faster; integration tests catch the bugs that only appear when the pieces meet.</p>
<h3 id="do-i-need-a-separate-testing-framework-or-can-i-write-my-own-assertions">Do I need a separate testing framework, or can I write my own assertions?</h3><p>You can write your own — a unit test is just code that calls other code and compares the result to an expected value. In practice everyone uses a framework because it gives you, for free, a runner that discovers and executes your tests, a reporter that shows what passed and failed, a way to group related cases, and helpers for common assertions. Common choices are Jest and Vitest for JavaScript, pytest for Python, JUnit for Java, and Go&#39;s built-in testing package.</p>
<h3 id="what-is-code-coverage-and-how-much-should-i-aim-for">What is code coverage and how much should I aim for?</h3><p>Code coverage is the percentage of lines, branches, or statements in your code that are exercised by your test suite. It is a useful way to spot code that has no test at all, but high coverage does not guarantee good tests. The web.dev article on testing strategy recommends aiming for meaningful coverage of the code that actually matters — algorithms, business rules, error paths — rather than a number like 100%, which can be reached with shallow assertions that would still pass on broken code.</p>
<h3 id="when-should-i-not-write-a-unit-test">When should I not write a unit test?</h3><p>Skip unit tests for code that is trivial, that you will throw away, that exists only to call a well-tested third-party API, or whose behavior is so tied to its environment (a complex UI animation, a hardware interaction) that a unit test would be a mock-heavy fiction with little value. For those cases integration or end-to-end tests are a better fit, and some exploratory work is best left to manual testing.</p>
</section>
<section><h2 id="summary"><a href="#summary" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Summary</h2><p>Unit testing is the practice of writing automated tests that exercise the smallest pieces of your code in isolation and report pass or fail. Good unit tests are fast, deterministic, named after the behavior they pin down, and structured around arrange-act-assert. They sit at the base of the testing pyramid, supported by integration and end-to-end tests, and they are the cheapest way to catch most regressions before they reach users. Start small, write tests for the code that matters, and let the test suite be the executable specification of what your program is meant to do.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>Open source licenses explained: MIT, Apache, and GPL compared</title>
      <link>https://debod.id/en/posts/open-source-licenses-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/open-source-licenses-explained/</guid>
      <pubDate>Wed, 02 Sep 2026 00:00:00 GMT</pubDate>
      <category>programming</category>
      <category>programming</category>
      <category>security</category>
      <category>devops</category>
      <category>developer-tools</category>
      <description>Open source licenses decide how others can use, modify, and distribute your code. This guide explains permissive licenses (MIT, Apache 2.0), copyleft licenses (GPL, AGPL), and how to choose the right one for your project.</description>
      <content:encoded><![CDATA[<section><p>An open source license tells other developers what they may and may not do with your code. It decides whether someone can use your library in a commercial product, modify it, and redistribute their changes — and under what conditions. This guide explains the two big families of open source licenses (permissive and copyleft), compares the most common ones (MIT, BSD, Apache 2.0, GPL, LGPL, AGPL), and gives practical guidance for picking a license for your own project.</p></section>
<section><h2 id="what-an-open-source-license-is-and-why-it-matters"><a href="#what-an-open-source-license-is-and-why-it-matters" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What an open source license is and why it matters</h2><p>Code you write is protected by copyright by default. Without a license, nobody has explicit permission to copy, modify, or distribute it — &quot;all rights reserved&quot; applies even to code posted publicly on GitHub. An open source license is a legal grant of rights that tells others exactly what they are allowed to do. The Open Source Definition, maintained by the Open Source Initiative, lists the criteria a license must meet to be called &quot;open source&quot;, including free redistribution, access to source code, and permission to create derivative works.</p>
<p>Choosing a license matters because it is difficult (and often impossible) to change after you have already published your code and others have relied on it. Once code is released under a license, you cannot retroactively revoke those rights, and you cannot later relicense code that others have contributed without getting their permission.</p>
</section>
<section><h2 id="permissive-licenses-mit-bsd-and-apache"><a href="#permissive-licenses-mit-bsd-and-apache" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Permissive licenses: MIT, BSD, and Apache</h2><p>Permissive licenses impose the fewest restrictions. They let anyone use, modify, copy, and distribute the code — even in proprietary, closed-source commercial products — as long as they keep the copyright notice. They are the most common licenses for libraries and open source components because they maximize adoption.</p>
<h3 id="mit-license">MIT license</h3><p>The MIT License is a short, permissive license originating at the Massachusetts Institute of Technology in the late 1980s. It allows reuse within proprietary software provided that all copies of the software, or its substantial portions, include the copyright notice and the license text. It has no copyleft requirement and no strong patent provisions, which makes it the simplest permissive option — and the single most popular open source license on GitHub. Many projects, including popular libraries and tools, use it because it imposes the fewest obligations.</p>
<h3 id="bsd-licenses">BSD licenses</h3><p>BSD licenses are permissive licenses closely related to MIT. The 2-clause BSD license is functionally similar to MIT. The 3-clause version adds a non-endorsement clause: it prevents your name from being used to promote derivative products without permission, and it also forbids implying endorsement by the original authors. BSD licenses have no copyleft and no explicit patent grant, which is the main practical difference from Apache 2.0.</p>
<h3 id="apache-license-2-0">Apache License 2.0</h3><p>The Apache License 2.0 is a permissive license written by the Apache Software Foundation and first released in January 2004. Like MIT, it allows use, modification, and distribution with few restrictions. Its distinguishing feature is an <strong>explicit patent grant</strong>: contributors grant patent rights to users for the code they contribute, and this grant terminates if a user sues someone over a patent related to the code (a patent retaliation clause). Apache 2.0 also requires preserving the copyright notice and a NOTICE file when one exists. This patent clause is why many companies prefer Apache 2.0 for infrastructure projects they want used broadly in industry.</p>
</section>
<section><h2 id="copyleft-licenses-gpl-lgpl-and-agpl"><a href="#copyleft-licenses-gpl-lgpl-and-agpl" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Copyleft licenses: GPL, LGPL, and AGPL</h2><p>Copyleft licenses go further than permissive ones. They require that derivative works — code derived from or incorporating the licensed code — be distributed under the same license, with source code made available. The idea, defended by the Free Software Foundation, is to keep software and its improvements free and open. The trigger is redistribution: if you distribute a derivative work, the copyleft applies. Purely internal or server-side use that is never distributed to others generally does not trigger it.</p>
<h3 id="gnu-general-public-license-gpl">GNU General Public License (GPL)</h3><p>The GPL is the canonical strong-copyleft license, written by Richard Stallman and the Free Software Foundation. Under the GPL, if you distribute a derivative work, you must offer the whole work under the GPL and make the source code available to recipients. The Linux kernel is a famous GPL project. Because the GPL propagates to the whole derivative, a project that links GPL code typically has to be released as GPL too.</p>
<p>GPL version 3 (2007) adds explicit patent provisions and anti-tivoization clauses that restrict hardware makers from blocking users from running modified versions of the software. GPL version 2 famously dropped the advertising clause that had made the original BSD license incompatible with GPL.</p>
<h3 id="gnu-lesser-general-public-license-lgpl">GNU Lesser General Public License (LGPL)</h3><p>The LGPL is a &quot;weak copyleft&quot; license designed mainly for libraries. It lets programs dynamically link against the library without forcing the program itself to be released under the LGPL. Modifications to the library itself must stay under the LGPL, but your own application code that merely uses the library can remain proprietary.</p>
<h3 id="gnu-affero-general-public-license-agpl">GNU Affero General Public License (AGPL)</h3><p>The AGPL extends the GPL&#39;s copyleft to network use. It closes what is called the &quot;service provider loophole&quot;: under the GPL, a company running modified software only as a network service (without distributing the binary) could avoid releasing its source. The AGPL requires that users who interact with the software over a network also be offered the source code, making it common for SaaS-related projects to avoid AGPL code.</p>
</section>
<section><h2 id="permissive-vs-copyleft-key-differences"><a href="#permissive-vs-copyleft-key-differences" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Permissive vs copyleft: key differences</h2><table>
<thead>
<tr>
<th>Aspect</th>
<th>MIT / BSD</th>
<th>Apache 2.0</th>
<th>GPL</th>
<th>AGPL</th>
</tr>
</thead>
<tbody><tr>
<td>Family</td>
<td>Permissive</td>
<td>Permissive</td>
<td>Strong copyleft</td>
<td>Strong copyleft (network)</td>
</tr>
<tr>
<td>Use in proprietary software</td>
<td>Yes</td>
<td>Yes</td>
<td>Only if whole work is GPL</td>
<td>Only if whole work is AGPL</td>
</tr>
<tr>
<td>Must keep copyright notice</td>
<td>Yes</td>
<td>Yes</td>
<td>Yes</td>
<td>Yes</td>
</tr>
<tr>
<td>Explicit patent grant</td>
<td>No</td>
<td>Yes</td>
<td>Yes (v3)</td>
<td>Yes (v3)</td>
</tr>
<tr>
<td>Must publish modified source</td>
<td>No</td>
<td>No</td>
<td>Yes, on distribution</td>
<td>Yes, on network use too</td>
</tr>
</tbody></table>
</section>
<section><h2 id="compatibility-and-mixing-licenses"><a href="#compatibility-and-mixing-licenses" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Compatibility and mixing licenses</h2><p>License compatibility decides whether code under two licenses can be combined. A general rule: <strong>more permissive code can flow into a more restrictive project, but not the reverse.</strong> For example, you can include MIT-licensed code inside a GPL project, but you cannot take GPL code and redistribute it under an MIT license, because that would remove the copyleft obligations that GPL recipients rely on.</p>
<p>Specific notes: the Apache License 2.0 is compatible with GPL version 3 — code under Apache 2.0 can be combined with GPLv3 code as long as the combined work is licensed under GPLv3. The Free Software Foundation considers Apache 2.0 incompatible with GPL versions 1 and 2.</p>
</section>
<section><h2 id="practical-guidance-for-choosing-a-license"><a href="#practical-guidance-for-choosing-a-license" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Practical guidance for choosing a license</h2><p><strong>Pick MIT</strong> if you want maximum adoption and simplicity, and you do not care whether companies build proprietary products on your code. It is the best default for libraries, npm packages, and small projects.</p>
<p><strong>Pick Apache 2.0</strong> if everything MIT offers matters to you, plus you want an explicit patent grant that protects both you and commercial users. It is a common choice for infrastructure, cloud, and large open source projects.</p>
<p><strong>Pick GPL</strong> if your goal is to ensure that derivative works stay open source, and you are comfortable that proprietary software will generally avoid linking your code.</p>
<p><strong>Pick LGPL</strong> for libraries where you want the library&#39;s improvements to stay open but you still want to be usable from proprietary applications.</p>
<p><strong>Pick AGPL</strong> if you want the strongest protection — closing the network loophole — and accept that most SaaS businesses will avoid your code.</p>
<p>If you are unsure, start with MIT: it is permissive, simple, widely understood, and easiest to change away from later if your needs evolve. Whatever you choose, add a LICENSE file at the root of the repository — without one, your code is effectively &quot;all rights reserved&quot;.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-is-the-difference-between-permissive-and-copyleft-licenses">What is the difference between permissive and copyleft licenses?</h3><p>Permissive licenses (MIT, BSD, Apache 2.0) let anyone use, modify, and distribute the code, including in proprietary products, as long as they keep the copyright notice. Copyleft licenses (GPL, AGPL) require that derivative works you distribute be released under the same license with source code made available, so the software and its improvements stay open.</p>
<h3 id="is-apache-2-0-more-permissive-than-mit">Is Apache 2.0 more permissive than MIT?</h3><p>Both are permissive and allow similar reuse, but Apache 2.0 adds an explicit patent grant (with a retaliation clause) and a NOTICE file requirement. MIT is shorter and imposes the fewest obligations. Neither has copyleft. In practice Apache 2.0 is chosen when patent clarity matters to commercial users.</p>
<h3 id="do-i-have-to-pay-to-use-open-source-software">Do I have to pay to use open source software?</h3><p>No. Open source licenses grant free rights to use, modify, and distribute code. Licenses sometimes add obligations — like keeping copyright notices or releasing modified source under the same license — but none require payment. Some companies sell commercial licenses or support separately, but that is on top of the open source license.</p>
<h3 id="can-i-put-gpl-code-in-my-proprietary-project">Can I put GPL code in my proprietary project?</h3><p>Generally no. If you distribute a project that incorporates GPL code, the whole derivative work must be offered under the GPL with source, so you would lose the ability to keep it proprietary. Purely internal use that is never distributed may not trigger the copyleft, but shipping it to customers or users will.</p>
<h3 id="what-happens-if-i-publish-code-without-a-license">What happens if I publish code without a license?</h3><p>Without a license, the default rule is &quot;all rights reserved&quot; — nobody has explicit permission to copy, modify, or distribute your code, even though it is public. If you want others to use it, you must add an explicit license file. If you accept contributions, you also need a way to ensure those contributors grant the same rights (for example a contributor license agreement or a clear inbound license).</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>What is observability? Logs, metrics, and traces explained</title>
      <link>https://debod.id/en/posts/observability-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/observability-explained/</guid>
      <pubDate>Wed, 02 Sep 2026 00:00:00 GMT</pubDate>
      <category>devops</category>
      <category>devops</category>
      <category>monitoring</category>
      <category>cloud-computing</category>
      <description>Observability is the ability to infer a system&apos;s internal state from its external outputs. Learn how the three pillars — logs, metrics, and traces — work together, the difference between monitoring and observability, and the four golden signals (latency, traffic, errors, saturation).</description>
      <content:encoded><![CDATA[<section><p>Observability is a measure of how well you can infer the internal state of a system from its external outputs. The term comes from control theory, where it was introduced by the engineer Rudolf E. Kálmán in 1960 for linear dynamical systems. In software, observability means you can answer open-ended questions about why a system behaves the way it does — not just &quot;is it up?&quot; but &quot;why is it slow for these users?&quot;, &quot;which service is the bottleneck?&quot;, and &quot;what happened right before the error rate spiked?&quot;</p>
<p>A system is <em>observable</em> if you can determine its behavior from the data it produces, without adding more instrumentation. If an application only tells you whether it is online or offline, that is monitoring, not observability. If it emits logs, metrics, and traces that let you reconstruct the full chain of events behind any incident, it is observable. The practical goal is the ability to debug unknown problems in a distributed system whose parts you cannot fully reason about ahead of time.</p></section>
<section><h2 id="the-three-pillars-logs-metrics-and-traces"><a href="#the-three-pillars-logs-metrics-and-traces" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The three pillars: logs, metrics, and traces</h2><p>Observability in modern practice rests on three complementary types of telemetry data, sometimes called the three pillars:</p>
<ul>
<li><strong>Metrics</strong> are numeric measurements collected over time, such as request rate, error rate, CPU usage, memory use, and latency percentiles. They are aggregated and cheap to store and query, which makes them ideal for dashboards and alerts. Metrics answer &quot;What is happening?&quot; in aggregate — they tell you a value went up or down, but not why or for which specific request.</li>
<li><strong>Logs</strong> are timestamped records of discrete events, written by applications, libraries, and the operating system. Each log line describes a single event: a request arrived, a user logged in, an error was thrown. Logs are rich in context but large in volume, so they are filtered and searched rather than continuously visualized. They answer &quot;Why did this happen?&quot; with the detail a human can inspect.</li>
<li><strong>Traces</strong> follow a single request&#39;s path through every service and component it touches, recording the duration of each step (each step is called a <em>span</em>). Because a single user request often fans out across many services — an API gateway, an authentication service, a database, a cache — traces are the only one of the three that reconstructs the end-to-end journey of one specific request. They answer &quot;Where exactly did this request slow down or fail?&quot;</li>
</ul>
<p>The three work together. Metrics alert you that something is wrong (&quot;error rate is up&quot;), traces show you the path and where the failure happened, and logs supply the surrounding context to identify the root cause. No single pillar is sufficient on its own.</p>
</section>
<section><h2 id="monitoring-vs-observability"><a href="#monitoring-vs-observability" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Monitoring vs. observability</h2><p>Monitoring and observability are related but not the same. Monitoring is the act of collecting, processing, and displaying real-time data about a system in order to detect known problems — it is built around known questions and predefined dashboards, alerts, and thresholds. Observability is the broader property that lets you investigate <em>unknown</em> problems: with rich telemetry in place, you can ask new questions and trace a problem to its cause even when you did not anticipate that specific failure mode.</p>
<p>One useful way to see the difference: monitoring tells you when something is wrong; observability helps you understand why. A monitoring system with thresholds can page you when latency spikes. It is observability — the ability to dig into logs and traces and correlate them — that lets you discover the spike was caused by a database connection pool exhaustion triggered by a slow third-party API. Good observability makes monitoring far more effective, because every alert can be followed by a fast, evidence-based investigation.</p>
</section>
<section><h2 id="the-four-golden-signals"><a href="#the-four-golden-signals" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The four golden signals</h2><p>In Google&#39;s Site Reliability Engineering book, Rob Ewaschuk defines four golden signals — the minimum set of metrics that give a good picture of a user-facing service&#39;s health. They were introduced by Google in 2014 and are widely reused by SRE and DevOps teams:</p>
<ul>
<li><strong>Latency</strong> — the time it takes to serve a request, ideally reported separately for successful and failed requests (a fast error is not a good response).</li>
<li><strong>Traffic</strong> — how much demand is being placed on the system, measured in requests per second, queries per second, or similar throughput units.</li>
<li><strong>Errors</strong> — the rate of requests that fail, whether explicitly (HTTP 500s) or implicitly (a 200 response with wrong content).</li>
<li><strong>Saturation</strong> — how close the system is to its capacity limits, for the resource that is most constrained (CPU, memory, database connections, queue depth, disk I/O).</li>
</ul>
<p>Tracking these four signals covers the essential dimensions of health; teams add more metrics as specific services require, but the golden signals are the recommended baseline.</p>
</section>
<section><h2 id="white-box-vs-black-box-monitoring"><a href="#white-box-vs-black-box-monitoring" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>White-box vs. black-box monitoring</h2><p>Another useful distinction from the Google SRE book is between two flavors of monitoring:</p>
<ul>
<li><strong>White-box monitoring</strong> relies on internal signals and metrics that the system exposes about itself, through logs, interfaces that emit internal statistics, or instrumentation you add to your own code. It tells you what is happening <em>inside</em> the system and is essential for debugging.</li>
<li><strong>Black-box monitoring</strong> tests externally visible behavior the way an actual user would experience it, for example by periodically requesting a public endpoint and checking that it responds correctly within a time limit. It proves that the <em>user-visible</em> service works, regardless of how its internals are implemented.</li>
</ul>
<p>Production environments need both: black-box checks catch genuine user-facing outages, while white-box metrics and traces explain why an outage happened and let you respond before it worsens.</p>
</section>
<section><h2 id="putting-observability-into-practice"><a href="#putting-observability-into-practice" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Putting observability into practice</h2><p>Bridging the theory to implementation is where observability tools come in. Open-source stacks like Prometheus (metrics) and Grafana (visualization, dashboards, and alerting) handle the metric pillar; your application emits numeric counters, gauges, and histograms that a metrics server scrapes and stores as time series. Log aggregation and search platforms collect log lines from many hosts into one searchable store. Tracing engines record spans and reconstruct the request path across services. Managed platforms such as Datadog, New Relic, and Grafana Cloud bundle logs, metrics, and traces into a single tool so you can correlate all three without running several systems.</p>
<p>Whatever stack you choose, the principles are the same: instrument your code so it emits useful telemetry, keep metrics cheap and log volume manageable, and use the three pillars together rather than expecting any one of them to answer every question.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-is-the-difference-between-monitoring-and-observability">What is the difference between monitoring and observability?</h3><p>Monitoring is the practice of collecting and displaying data about a system to detect known problems, using predefined dashboards, alerts, and thresholds. Observability is the property of a system that lets you ask new questions and investigate unknown problems from the telemetry it emits. Monitoring tells you something is wrong; observability helps you understand why.</p>
<h3 id="which-is-better-for-my-app-metrics-logs-or-traces">Which is better for my app: metrics, logs, or traces?</h3><p>You need all three working together. Metrics are cheap and good for alerting and dashboards, logs provide the detailed context of individual events, and traces show the end-to-end path of a single request across services. Trying to debug a distributed system with only one pillar leaves important questions unanswered.</p>
<h3 id="what-are-the-four-golden-signals">What are the four golden signals?</h3><p>The four golden signals, defined in Google&#39;s Site Reliability Engineering book, are latency (how long requests take), traffic (how much demand the system is handling), errors (the rate of failed requests), and saturation (how close the system is to capacity). Together they give a baseline picture of a user-facing service&#39;s health.</p>
<h3 id="do-i-need-a-full-observability-platform-or-can-i-start-small">Do I need a full observability platform, or can I start small?</h3><p>Start small and grow. Many teams begin with an open-source metrics stack (Prometheus plus Grafana) for the metric pillar, then add structured logging to a searchable store, and add traces only when they need to debug cross-service latency. The three-pillar principles apply at any scale, and you can adopt a managed platform later to bundle them together.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>What is Machine Learning? Supervised, Unsupervised, and Reinforcement Learning Explained</title>
      <link>https://debod.id/en/posts/machine-learning-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/machine-learning-explained/</guid>
      <pubDate>Wed, 02 Sep 2026 00:00:00 GMT</pubDate>
      <category>programming</category>
      <category>programming</category>
      <description>Machine learning is a branch of artificial intelligence where systems learn from data to make predictions without being explicitly programmed. Learn the three main paradigms — supervised, unsupervised, and reinforcement learning — with practical examples and real-world applications.</description>
      <content:encoded><![CDATA[<section><p>In traditional programming, a developer writes explicit rules that a computer follows to produce an output. If you want a program to detect spam, you write rules such as &quot;flag emails that contain the word &#39;winner&#39; and come from an unknown sender.&quot; Machine learning takes a different approach: instead of writing the rules, you give the computer examples of inputs and their correct outputs, and it learns the rules from the data.</p>
<p>Machine learning (ML) is a field of study within artificial intelligence concerned with the development and study of statistical algorithms that can learn from data and generalize to unseen data, and thus perform tasks without being explicitly programmed. The term was coined in 1959 by Arthur Samuel, an IBM employee and pioneer in computer gaming and AI, who built one of the earliest ML programs — a checkers-playing system that improved its play through self-practice.</p>
<p>The field draws deeply from statistics and mathematical optimisation. The core insight is simple: instead of hand-coding a solution, you let the algorithm discover patterns. Advances in deep learning — a class of algorithms called neural networks — have pushed ML past many previous benchmarks in image recognition, natural language processing, and game playing.</p></section>
<section><h2 id="how-machine-learning-differs-from-classical-programming"><a href="#how-machine-learning-differs-from-classical-programming" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How machine learning differs from classical programming</h2><p>The difference comes down to where the rules live:</p>
<ul>
<li><strong>Classical programming</strong>: you write the rules; the computer applies them to data and produces output.</li>
<li><strong>Machine learning</strong>: you give the computer examples of input-output pairs (the training data); the algorithm learns the rules that map inputs to outputs.</li>
</ul>
<p>This flip is what makes ML powerful for problems where writing explicit rules is impractical — face recognition, language translation, fraud detection, and recommendation systems all have rules too complex or too implicit to hand-code.</p>
</section>
<section><h2 id="the-three-main-paradigms"><a href="#the-three-main-paradigms" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The three main paradigms</h2><p>Most machine learning problems fall into one of three categories.</p>
<h3 id="supervised-learning">Supervised learning</h3><p>In supervised learning, the training data consists of labeled examples — each input comes paired with the correct output. The algorithm learns to map inputs to outputs by comparing its predictions to the known correct answers and adjusting accordingly.</p>
<p>Wikipedia describes supervised learning as learning from example input-output pairs, where the &quot;supervisor&quot; is the labeled training data. It covers two main problem types:</p>
<ul>
<li><strong>Classification</strong> — predicting a category: spam or not spam, a digit from 0 to 9, whether a tumor is benign or malignant.</li>
<li><strong>Regression</strong> — predicting a continuous value: the price of a house, tomorrow&#39;s temperature, a user&#39;s expected lifetime value.</li>
</ul>
<p>Practical examples include email spam filters (trained on thousands of emails labeled as spam or not spam), medical diagnosis systems (trained on patient records with known outcomes), and credit scoring models (trained on past loan applicants with known repayment histories).</p>
<pre><code class="language-python">from sklearn.ensemble import RandomForestClassifier

# Supervised learning: classification
# Each row has features (email words, length, links) and a label (spam / not spam)
X_train = [[1, 0, 3], [0, 1, 1], [1, 1, 0]]  # example feature vectors
y_train = [&quot;spam&quot;, &quot;not_spam&quot;, &quot;spam&quot;]

model = RandomForestClassifier()
model.fit(X_train, y_train)
prediction = model.predict([[1, 0, 2]])  # → [&quot;spam&quot;]
</code></pre>
<h3 id="unsupervised-learning">Unsupervised learning</h3><p>In unsupervised learning, the data has no labels. The algorithm&#39;s job is to find structure — groups, patterns, or anomalies — without being told what to look for. Wikipedia defines it as finding hidden patterns in unlabeled data.</p>
<p>Common techniques include:</p>
<ul>
<li><strong>Clustering</strong> — grouping similar items together. Customer segmentation groups shoppers by purchasing behavior so a business can tailor marketing to each group.</li>
<li><strong>Dimensionality reduction</strong> — simplifying high-dimensional data (say, 1,000 features from an image) into fewer dimensions while preserving key structure, used to speed up ML pipelines or visualise data.</li>
<li><strong>Anomaly detection</strong> — flagging unusual patterns, such as fraudulent transactions that differ from a customer&#39;s normal behavior.</li>
</ul>
<p>Unsupervised learning is harder to evaluate because there is no ground truth to compare against, but it is invaluable when labels are expensive or impossible to obtain.</p>
<pre><code class="language-python">from sklearn.cluster import KMeans

# Unsupervised learning: clustering
# The algorithm discovers 3 groups without any labels provided
X = [[1, 2], [1, 4], [1, 0], [10, 2], [10, 4], [10, 0]]
model = KMeans(n_clusters=3, random_state=42)
labels = model.fit_predict(X)
# → [0, 0, 0, 1, 1, 1] (two natural clusters found)
</code></pre>
<h3 id="reinforcement-learning">Reinforcement learning</h3><p>Reinforcement learning (RL) is the third major paradigm. As Wikipedia describes it, RL involves an agent that takes actions in an environment to maximise a cumulative reward signal. Unlike supervised learning — where the correct answer is provided for each input — the agent only receives a reward signal (positive or negative) that tells it how well it did, but not what the right action was.</p>
<p>RL solves sequential decision problems where actions have long-term consequences. The agent must balance exploration (trying new actions to discover better strategies) with exploitation (using the best-known strategy). This is called the exploration-exploitation dilemma.</p>
<p>RL has powered landmark achievements: AlphaGo defeated a professional human Go player in 2016 using deep neural networks combined with tree search. RL also drives robotic control systems, autonomous vehicle navigation, and adaptive recommendation engines.</p>
</section>
<section><h2 id="how-to-get-started"><a href="#how-to-get-started" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How to get started</h2><p>You can start with practical tools that require little background:</p>
<ul>
<li><strong>Python with scikit-learn or TensorFlow</strong> — the most common ML stack. scikit-learn covers classical algorithms (decision trees, random forests, clustering, regression) in a clean, beginner-friendly API. TensorFlow and its Keras interface are the standard for deep learning.</li>
<li><strong>Google Colab</strong> — a free browser-based Jupyter notebook environment with GPUs provided at no cost. You can run scikit-learn, TensorFlow, or PyTorch code immediately without installing anything.</li>
<li><strong>No-code tools</strong> — platforms such as Google AutoML, Azure Machine Learning, and DataRobot let you upload data and train models through a GUI, abstracting away the code entirely.</li>
</ul>
<p>A typical beginner workflow: load a dataset, split it into training and test sets, train a model on the training set, evaluate its accuracy on the held-out test set, and iterate on model choice or feature engineering until performance is acceptable.</p>
</section>
<section><h2 id="the-relationship-between-ai-and-machine-learning"><a href="#the-relationship-between-ai-and-machine-learning" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The relationship between AI and machine learning</h2><p>It helps to keep the scope clear. Artificial intelligence is the broad goal of building machines that can perform tasks that would otherwise require human intelligence. Machine learning is one of the primary approaches to achieving that goal — specifically, systems that learn from data rather than following hard-coded rules.</p>
<p>Other AI approaches include rule-based expert systems, symbolic reasoning, and search algorithms. ML has become the dominant approach because of its empirical strength: on problems with abundant data, ML systems consistently outperform systems built on hand-crafted rules.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-is-the-difference-between-supervised-and-unsupervised-learning">What is the difference between supervised and unsupervised learning?</h3><p>Supervised learning uses labeled data — every training example has a known correct answer — and the algorithm minimises the gap between its predictions and those known answers. Unsupervised learning works on unlabeled data and finds structure without any reference answers. If you have labels, supervised methods almost always produce better predictions; if you do not have labels, unsupervised methods are the only option.</p>
<h3 id="can-reinforcement-learning-be-used-without-a-simulation-of-the-environment">Can reinforcement learning be used without a simulation of the environment?</h3><p>In many cases, yes. You can train RL agents directly in the real world (called online or live RL), though this is slower and riskier — a wrong action might cause physical damage. A common middle ground is model-based RL, where the agent first builds a model of the environment from collected experience, then simulates further training within that model before acting in the real world. Pure model-free RL (such as Q-learning) typically needs many environment interactions, which makes it impractical for expensive real-world settings.</p>
<h3 id="how-is-deep-learning-different-from-classical-machine-learning">How is deep learning different from classical machine learning?</h3><p>Classical ML algorithms — decision trees, random forests, logistic regression, support vector machines — require careful feature engineering: the practitioner decides which aspects of the input to extract and feed to the model. Deep learning uses neural networks with many layers that automatically learn hierarchical feature representations from raw data (pixels, audio waveforms, text). For problems with rich, unstructured inputs — images, audio, natural language — deep learning dramatically outperforms classical ML. For structured tabular data, classical ML often matches or exceeds deep learning with less complexity.</p>
<h3 id="what-are-the-main-limitations-of-machine-learning">What are the main limitations of machine learning?</h3><p>ML systems fail when data is scarce, biased, or unrepresentative of the real world. A model trained only on data from one region or demographic will perform poorly for others. ML can also be a black box — even the model&#39;s creators may not be able to explain why a specific prediction was made. This raises concerns in high-stakes domains such as healthcare, criminal justice, and finance. Other challenges include overfitting (a model that memorises the training data but fails on new examples), data privacy, and the computational cost of training large models.</p>
</section>
<section><h2 id="summary"><a href="#summary" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Summary</h2><p>Machine learning flips the classical programming model: instead of writing explicit rules, you give the algorithm data and let it discover the rules. The three main paradigms serve different problems. Supervised learning maps labeled inputs to outputs — the right choice when you have examples of the right answer. Unsupervised learning finds hidden structure in unlabeled data — useful for exploration, clustering, and anomaly detection. Reinforcement learning trains agents through reward signals in an environment — the approach behind game-playing systems and robotics. Together they power everything from your email spam filter to large language models.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>How to optimize images for the web: formats, resizing, lazy loading, and CLS</title>
      <link>https://debod.id/en/posts/image-optimization-guide/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/image-optimization-guide/</guid>
      <pubDate>Wed, 02 Sep 2026 00:00:00 GMT</pubDate>
      <category>web</category>
      <category>performance</category>
      <category>html</category>
      <description>Images are usually the heaviest assets on a page. Here&apos;s how to pick the right format, serve the right size, lazy-load what&apos;s offscreen, and stop images from shifting your layout.</description>
      <content:encoded><![CDATA[<section><p>Images are usually the heaviest things a web page asks the browser to download. Over time they got bigger: MDN reports that between 2011 and 2019 the median image size grew from roughly 250 KB to 900 KB on desktop and from 100 KB to 850 KB on mobile, while total page weight roughly quadrupled. Get images wrong and pages load slowly; get them right and you cut bytes, raise your Core Web Vitals, and keep layout stable. This guide covers the four levers that matter most: format, size, lazy loading, and layout stability.</p></section>
<section><h2 id="pick-the-right-format"><a href="#pick-the-right-format" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Pick the right format</h2><p>The format you choose trades quality against file size. The most common web formats, per MDN, are AVIF, GIF, JPEG, PNG, SVG, and WebP. A good rule of thumb is: <strong>JPEG</strong> or <strong>WebP</strong>/ <strong>AVIF</strong> for photographs and still images, <strong>PNG</strong> for lossless images or when you need transparency, and <strong>SVG</strong> for anything that must scale — icons, logos, and diagrams. SVG is a vector format stored as text, so it can be drawn accurately at any size without quality loss, which is why MDN calls it the format for images that must be drawn accurately at different sizes. GIF, which compresses with the LZW algorithm, is fine for simple animations, but for animation sequences MDN recommends modern formats such as WebP or APNG instead.</p>
</section>
<section><h2 id="why-webp-and-avif-win-on-file-size"><a href="#why-webp-and-avif-win-on-file-size" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Why WebP and AVIF win on file size</h2><p>The older formats predate better compression. According to MDN&#39;s image format guide, <strong>lossy WebP images are on average 25–35% smaller than JPEG</strong> images of visually similar compression levels, and <strong>lossless WebP images are typically 26% smaller</strong> than the same image in PNG. <strong>AVIF goes further: lossy AVIF images are around 50% smaller than JPEG</strong>, and AVIF generally compresses better than WebP (MDN cites a median of 50% vs 30% compression on the same JPEG set). Both WebP and AVIF are royalty-free and supported in all current major browsers. Because formats like AVIF do not yet go back as far in browsers, MDN recommends serving them with a fallback using the <code>&lt;picture&gt;</code> element.</p>
</section>
<section><h2 id="serve-the-right-size-not-just-the-right-format"><a href="#serve-the-right-size-not-just-the-right-format" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Serve the right size, not just the right format</h2><p>Format fixes bytes per pixel, but size fixes how many pixels you send. A 4000-pixel-wide photo displayed in a 600-pixel card is mostly wasted bandwidth. MDN describes the <code>&lt;img&gt;</code> element&#39;s responsive-hint attributes for exactly this: <strong><code>srcset</code></strong> lists a set of candidate images at different sizes (plus widths, for example <code>800w</code>), and <strong><code>sizes</code></strong> tells the browser how wide the image will be displayed, so the browser can pick the best candidate for the user&#39;s viewport and device. This is the difference between sending one oversized image to everyone and letting each device download only what it needs.</p>
</section>
<section><h2 id="keep-layout-stable-declare-dimensions"><a href="#keep-layout-stable-declare-dimensions" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Keep layout stable: declare dimensions</h2><p>When an image loads and the space around it was not reserved, the content below jumps — that is a layout shift, which hurts your Cumulative Layout Shift (CLS) score and frustrates readers. MDN&#39;s fix is explicit: <strong>use both <code>width</code> and <code>height</code> to set the intrinsic size of the image, allowing it to take up space before it loads, to mitigate content layout shifts</strong>. With dimensions declared, the browser reserves the box ahead of time and the image drops in without pushing anything. Using <code>width: auto</code> in CSS keeps the image responsive while honoring those declared intrinsic dimensions.</p>
</section>
<section><h2 id="lazy-load-what-is-offscreen"><a href="#lazy-load-what-is-offscreen" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Lazy-load what is offscreen</h2><p>Lazy loading is a strategy to identify resources as non-critical and load them only when needed, shortening the critical rendering path. It applies to offscreen content — images below the fold need no download before the user scrolls to them. The browser-native way is the <strong><code>loading=&quot;lazy&quot;</code></strong> attribute on <code>&lt;img&gt;</code> (and on iframes); the <code>load</code> event then fires when the eagerly-loaded content is done, even if lazily-loaded images in the viewport are still arriving. For custom logic, MDN points to the <strong>Intersection Observer API</strong>, which notifies your code when an observed element enters or exits the viewport — the standard building block for viewport-triggered loading. One important caution: your above-the-fold image is often the page&#39;s largest contentful paint (LCP) element, so that image should stay eager and be optimized carefully rather than deferred.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-image-format-should-i-use">What image format should I use?</h3><p>For photographs and still images, use JPEG or a modern format like WebP or AVIF. For lossless images or transparency, use PNG. For icons, logos, and diagrams that must scale, use SVG. GIF remains fine for simple animations, but WebP, APNG, or AVIF compress animations far better.</p>
<h3 id="how-much-can-webp-and-avif-actually-save">How much can WebP and AVIF actually save?</h3><p>MDN reports lossy WebP images are on average 25–35% smaller than JPEG at similar visual quality, and lossless WebP about 26% smaller than PNG. AVIF goes further — around 50% smaller than JPEG — and generally compresses better than WebP. Since modern formats lack deep historical support, serve them with a JPEG or PNG fallback via <code>&lt;picture&gt;</code>.</p>
<h3 id="why-does-an-image-shift-my-layout">Why does an image shift my layout?</h3><p>If the browser has not reserved space for an image before it loads, the content below it jumps when the image arrives — a layout shift. Setting explicit <code>width</code> and <code>height</code> attributes tells the browser the intrinsic size so it can reserve the box up front, which mitigates layout shifts.</p>
<h3 id="does-lazy-loading-hurt-my-lcp-or-seo">Does lazy loading hurt my LCP or SEO?</h3><p>Native lazy loading defers offscreen images, which is usually a win. But the largest contentful paint (LCP) image — typically the hero at the top of the page — should be loaded eagerly and optimized, because delaying it can slow your LCP. Defer what is below the fold; prioritize what is visible first.</p>
</section>
<section><h2 id="summary"><a href="#summary" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Summary</h2><p>Optimizing images is a sequence of simple, high-impact decisions: choose a modern format (WebP or AVIF) for photos, keep PNG for transparency and SVG for anything that scales, send the right dimensions with <code>srcset</code> and <code>sizes</code>, declare <code>width</code> and <code>height</code> so nothing shifts, and lazy-load only what is offscreen. Together these cut hundreds of kilobytes from typical pages, improve your Core Web Vitals, and give you a faster, more stable site.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>What is Event-Driven Architecture? How EDA works with events, CQRS, and event sourcing</title>
      <link>https://debod.id/en/posts/event-driven-architecture-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/event-driven-architecture-explained/</guid>
      <pubDate>Wed, 02 Sep 2026 00:00:00 GMT</pubDate>
      <category>architecture</category>
      <category>programming</category>
      <category>devops</category>
      <category>async</category>
      <description>Event-driven architecture (EDA) is a software design pattern where components communicate by producing and detecting events. Learn how EDA works, its topologies, event sourcing, CQRS, and when to use it.</description>
      <content:encoded><![CDATA[<section><p>In a traditional web application, one service calls another directly: the caller sends a request and waits for a reply. That works fine until your system grows, your services multiply, and a single user action needs to trigger ten different downstream things — email notifications, audit logs, analytics pipelines, inventory updates. Chaining all those calls synchronously turns your architecture into a spider web of dependencies where one slow service drags everything else down.</p>
<p>Event-driven architecture (EDA) takes a different approach. Instead of services calling each other, they broadcast signals called events. Any service that cares about a particular event can subscribe and react. The producer of the event does not know or care how many consumers are listening — it just announces &quot;something happened&quot; and moves on.</p>
<p>This guide explains what EDA is, how events are structured, what topologies exist, and how it connects to related patterns like event sourcing and CQRS.</p></section>
<section><h2 id="what-is-an-event"><a href="#what-is-an-event" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What is an event?</h2><p>An event is a record of something that happened. Wikipedia defines it as &quot;a significant change in state.&quot; For example, when a customer places an order, the event might be <code>OrderPlaced</code> with a payload like <code>{ orderId: &quot;1234&quot;, customerId: &quot;user_99&quot;, total: 49.99, items: [...] }</code>. The event does not carry instructions — it simply reports a fact.</p>
<p>This distinction matters: the producer says &quot;an order was placed&quot; without knowing whether anyone is listening. Consumers decide for themselves what to do with that information — send a confirmation email, update inventory, trigger a fraud check, or all three at once.</p>
</section>
<section><h2 id="core-components-emitters-channels-consumers"><a href="#core-components-emitters-channels-consumers" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Core components: emitters, channels, consumers</h2><p>Every event-driven system has three building blocks:</p>
<p><strong>Event emitters (producers)</strong> detect facts and publish them. An emitter has no knowledge of consumers — it does not know whether anything is listening, or what will happen next. This loose coupling is the core advantage of EDA.</p>
<p><strong>Event channels</strong> are the conduits that carry events from emitters to consumers. Channels can be simple queues (first-in, first-out), publish/subscribe topics (one event broadcast to many consumers), or complex event streams. The channel decides where each event goes; producers and consumers do not need to know about each other.</p>
<p><strong>Event consumers (sinks)</strong> subscribe to channels and react to events. A consumer might update a database, call an external API, send a notification, or simply log the occurrence. Consumers are independent of each other — one consumer failing does not block another.</p>
</section>
<section><h2 id="event-topologies"><a href="#event-topologies" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Event topologies</h2><p>There are two primary EDA topologies.</p>
<p><strong>Broker topology</strong> — components broadcast events directly to a central broker (such as Apache Kafka, RabbitMQ, or Cloudflare Queues) without a central orchestrator. Consumers subscribe to topics and process events as they arrive. This topology offers high throughput and scalability because there is no single point of control. It works well when consumers are independent and can handle events at their own pace.</p>
<p><strong>Mediator topology</strong> — a central orchestrator (a workflow engine or event mediator) coordinates event flows. When an initial event arrives, the mediator triggers a sequence of downstream steps. This topology gives you better control over complex, multi-step workflows where order matters. However, the mediator becomes a single point of coordination, which adds latency and a potential bottleneck.</p>
<p>Many real systems use a hybrid: broker topology for most events, mediator topology for complex workflows that require sequencing.</p>
</section>
<section><h2 id="event-types"><a href="#event-types" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Event types</h2><p>Not all events are the same. According to event-driven architecture literature (Yann Cui, Microsoft Azure patterns), events fall into two key categories:</p>
<p><strong>Domain events</strong> represent important occurrences within a specific bounded context. They are typically lightweight — containing only the data consumers need — because they are consumed by services within the same domain. An example is <code>ItemAddedToCart</code> in an e-commerce domain.</p>
<p><strong>Integration events</strong> communicate changes across bounded contexts or to external systems. They carry richer payloads because consumers are outside your direct control and may need more context. An example is <code>OrderShipped</code>, which an external logistics partner needs to arrange pickup.</p>
</section>
<section><h2 id="event-sourcing-storing-state-as-a-sequence-of-events"><a href="#event-sourcing-storing-state-as-a-sequence-of-events" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Event sourcing: storing state as a sequence of events</h2><p>Traditional systems store the current state of data — you save the row, you overwrite it on updates. Event sourcing flips this: instead of storing <em>what</em> the data is, you store <em>every change</em> that was made. Each change is an immutable event appended to an event log.</p>
<p>Martin Fowler&#39;s original definition: &quot;Event Sourcing ensures that all changes to application state are stored as a sequence of events. Not only can we query these events, we can also use the event log to reconstruct past states, and as a foundation to automatically adjust the state to cope with retroactive changes.&quot;</p>
<p>Concretely, an e-commerce system using event sourcing would not store <code>order.status = &quot;shipped&quot;</code>. Instead it stores the events: <code>OrderCreated</code>, <code>PaymentReceived</code>, <code>OrderPacked</code>, <code>OrderShipped</code>. The current state is <em>derived</em> by replaying the event log. This gives you three superpowers that traditional CRUD cannot match:</p>
<ol>
<li><strong>Complete audit trail</strong> — every state change is captured with its timestamp and context. You can replay exactly what happened and when.</li>
<li><strong>Temporal queries</strong> — you can ask &quot;what was the order total at 3 PM yesterday?&quot; by replaying events up to that timestamp. No separate snapshot tables needed.</li>
<li><strong>Easy integration</strong> — new services can subscribe to the event stream and build their own read models without touching the write side.</li>
</ol>
<p>The downside is complexity. Reconstructing state by replaying thousands of events is slow. For this reason, event-sourced systems typically maintain <strong>materialized views</strong> — pre-computed read models that are updated as events arrive. This is where CQRS fits in.</p>
</section>
<section><h2 id="cqrs-separating-reads-from-writes"><a href="#cqrs-separating-reads-from-writes" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>CQRS: separating reads from writes</h2><p>CQRS (Command Query Responsibility Segregation) is a pattern that separates the code that writes data from the code that reads it. In a traditional CRUD system, the same model handles both. As Microsoft Azure&#39;s pattern documentation explains, CQRS is useful when &quot;read and write operations have different performance and scaling requirements.&quot;</p>
<p>The write side (the <strong>command side</strong>) handles operations that change state — creating an order, cancelling a subscription, updating a profile. These are called commands, not queries, because they represent intent, not just data retrieval.</p>
<p>The read side (the <strong>query side</strong>) handles data retrieval. In an event-sourced system, the read side is built from materialized views of the event log. The same event <code>OrderPlaced</code> might update an order summary view for the UI, a revenue report view for finance, and a shipping queue view for logistics — all from one event.</p>
<p>CQRS pairs naturally with event sourcing. Event sourcing gives you an immutable event log as your source of truth; CQRS lets you project that log into as many read models as you need, each optimized for a specific query pattern.</p>
</section>
<section><h2 id="how-eda-connects-to-other-patterns"><a href="#how-eda-connects-to-other-patterns" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How EDA connects to other patterns</h2><p>EDA is not isolated — it builds on and connects to several other architectural patterns:</p>
<ul>
<li><strong>Message queues</strong> (RabbitMQ, Kafka, Cloudflare Queues) are the infrastructure layer that EDA uses to transport events. A message queue <em>is</em> an event channel. The message-queues-explained pillar covers this in detail.</li>
<li><strong>Webhooks</strong> are a lightweight, HTTP-based form of event notification. A webhook is essentially an event sent over HTTP to a specific endpoint. Webhooks-explained covers this pattern.</li>
<li><strong>Microservices</strong> and EDA are natural partners: loosely coupled services that react to events scale independently and deploy without coordination. Monolith vs microservices covers the trade-offs.</li>
<li><strong>Serverless functions</strong> (Lambda, Cloudflare Workers) respond to events — an HTTP request, a timer, a queue message — making them a natural fit for event consumers in an EDA.</li>
</ul>
</section>
<section><h2 id="when-to-use-event-driven-architecture"><a href="#when-to-use-event-driven-architecture" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>When to use event-driven architecture</h2><p>EDA shines in four scenarios:</p>
<ol>
<li><strong>Systems with multiple independent consumers</strong> — when one action needs to trigger many downstream reactions (notifications, logging, analytics, integrations), EDA eliminates the need for the producer to know about all of them.</li>
<li><strong>Microservices that need loose coupling</strong> — services that communicate only through events can be deployed, scaled, and updated independently. No service calls another; they just publish and subscribe.</li>
<li><strong>Audit-critical applications</strong> — financial systems, compliance tools, healthcare records. Event sourcing gives you a tamper-evident log of every state change.</li>
<li><strong>Temporal querying and historical reconstruction</strong> — when you need to know not just the current state but how you got there, or what the state was at a point in time.</li>
</ol>
<p>EDA is overkill for simple request-response applications where one service calls another and waits. The additional infrastructure (brokers, event stores, materialized views) adds operational complexity. If your system has no need for multiple consumers, loose coupling, or audit trails, a synchronous architecture is simpler and faster to build.</p>
</section>
<section><h2 id="common-challenges"><a href="#common-challenges" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Common challenges</h2><p><strong>Eventual consistency</strong> — in an EDA, consumers process events asynchronously. The read side may lag behind the write side by milliseconds or seconds. If your user needs to see the result of a write immediately (read-your-writes consistency), you need to account for this — either by reading from the write model directly after a command, or by designing your UI to show the expected outcome optimistically.</p>
<p><strong>At-least-once delivery</strong> — message brokers typically guarantee that an event will be delivered at least once, not exactly once. A consumer might receive the same event twice (for example, after a failed processing attempt that retried). Consumers must be <strong>idempotent</strong> — processing the same event twice must produce the same result as processing it once. Microsoft Azure&#39;s pattern documentation gives the example of a seat-reservation system: a duplicate reservation event must result in only one seat being decremented from availability.</p>
<p><strong>Event schema evolution</strong> — as your system changes, event shapes change too. A <code>OrderPlaced</code> event from two years ago might have different fields than one created today. Strategies include semantic versioning of events, upcasters (adapters that translate old event schemas to new ones), and storing only IDs in events and fetching the full payload on demand.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-is-the-difference-between-eda-and-message-queues">What is the difference between EDA and message queues?</h3><p>Event-driven architecture is a design <em>pattern</em> — it describes how systems communicate via events. Message queues (RabbitMQ, Kafka, Cloudflare Queues) are the <em>infrastructure</em> that implements the pattern. You can have a message queue without EDA (for example, a simple task queue with one producer and one consumer). You can have EDA without a traditional message queue if you use HTTP webhooks or database triggers. But in practice, most EDA systems use message queues as their event channels.</p>
<h3 id="what-is-the-difference-between-event-sourcing-and-cqrs">What is the difference between event sourcing and CQRS?</h3><p>Event sourcing is a storage pattern: you store every state change as an immutable event. CQRS is a architectural pattern: you separate the code that writes data from the code that reads data. They are complementary — event sourcing provides a natural event stream to feed CQRS read models. But you can use CQRS without event sourcing (using a traditional database for writes and a separate read-optimized store for queries), and you can use event sourcing without CQRS (replaying events into a single, traditional read model).</p>
<h3 id="when-should-you-not-use-event-driven-architecture">When should you not use event-driven architecture?</h3><p>Avoid EDA when your system is simple and has no need for multiple independent consumers, audit trails, or temporal queries. The benefits of loose coupling and scalability come with significant operational complexity: you need to run and monitor message brokers or event stores, handle eventual consistency, and write idempotent consumers. For a straightforward CRUD application with one service and one database, a synchronous request-response model is faster to build and easier to operate.</p>
<h3 id="what-are-domain-events-and-integration-events">What are domain events and integration events?</h3><p>Domain events represent important occurrences within a specific bounded context. They have lightweight payloads because consumers are within the same service domain. Integration events communicate changes across bounded contexts or to external systems. They carry richer payloads because consumers outside your direct control may need more context. Both types are first-class citizens in EDA; the distinction helps you design appropriately sized event payloads.</p>
</section>
<section><h2 id="sources"><a href="#sources" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Sources</h2><ul>
<li><a href="https://en.wikipedia.org/wiki/Event-driven_architecture">Wikipedia — Event-driven architecture (CC BY-SA 4.0)</a></li>
<li><a href="https://en.wikipedia.org/wiki/Event_sourcing">Wikipedia — Event sourcing (CC BY-SA 4.0)</a></li>
<li><a href="https://learn.microsoft.com/en-us/azure/architecture/patterns/cqrs">Microsoft Learn — CQRS Pattern</a></li>
<li><a href="https://martinfowler.com/eaaDev/EventSourcing.html">Martin Fowler — Event Sourcing</a></li>
<li><a href="https://microservices.io/patterns/data/event-sourcing.html">Chris Richardson — Event sourcing pattern</a></li>
</ul>
</section>]]></content:encoded>
    </item>
    <item>
      <title>What is a Content Security Policy (CSP)? How CSP works and how to deploy it</title>
      <link>https://debod.id/en/posts/csp-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/csp-explained/</guid>
      <pubDate>Wed, 02 Sep 2026 00:00:00 GMT</pubDate>
      <category>security</category>
      <category>security</category>
      <category>web</category>
      <description>A Content Security Policy (CSP) is an HTTP security header that tells a browser which resources a page is allowed to load. Learn how CSP works, what directives are available, and how to deploy it without breaking your site.</description>
      <content:encoded><![CDATA[<section><p>The browser&#39;s same-origin policy keeps code from <code>https://mybank.com</code> isolated from <code>https://evil.example.com</code> — in theory. In practice, cross-site scripting (XSS) attacks bypass this by tricking a site into serving malicious code alongside its legitimate content. The browser has no way to distinguish injected script from the real thing, so it runs everything. A Content Security Policy (CSP) is the web&#39;s primary defence against this class of attack: it is an HTTP response header that acts as an explicit allowlist, telling the browser exactly which scripts, styles, images and other resources are permitted to run on a given page. This guide explains how CSP works, what each directive does, and how to roll one out without taking your site offline.</p></section>
<section><h2 id="how-csp-works"><a href="#how-csp-works" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How CSP works</h2><p>A CSP is delivered to the browser as the <code>Content-Security-Policy</code> HTTP response header, set by your web server or CDN on every response — not just the HTML document. The policy is written as a series of directives separated by semicolons, each controlling a different category of resource:</p>
<pre><code>Content-Security-Policy: default-src &#39;self&#39;; script-src &#39;self&#39; https://trusted-cdn.com; object-src &#39;none&#39;
</code></pre>
<p>Each directive has a name and a value. The directives together form an <strong>allowlist</strong>: the browser loads only the resources that match one of the values you specified, and blocks everything else. If an attacker manages to inject a <code>&lt;script src=&quot;https://evil.example.com/xss.js&quot;&gt;</code> tag, the browser refuses to load it because <code>https://evil.example.com</code> is not in the <code>script-src</code> allowlist.</p>
<p>The <code>default-src</code> directive acts as a fallback for any resource type that does not have its own more specific directive. In the example above, images, fonts and frames inherit from <code>default-src &#39;self&#39;</code>, while scripts are additionally allowed from <code>https://trusted-cdn.com</code>. Any plugin content (<code>&lt;object&gt;</code>, <code>&lt;embed&gt;</code>) is blocked outright by <code>object-src &#39;none&#39;</code>.</p>
<p>You can also deliver a CSP via a <code>&lt;meta http-equiv&gt;</code> tag inside <code>&lt;head&gt;</code>. This is useful for purely static, client-side applications where you do not have server-level control of headers. Note, however, that certain directives — <code>frame-ancestors</code>, the <code>report-uri</code>/<code>report-to</code> reporting mechanism, and <code>sandbox</code> — only take effect when delivered as a real HTTP response header.</p>
</section>
<section><h2 id="key-directives-and-keywords"><a href="#key-directives-and-keywords" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Key directives and keywords</h2><p>CSP defines several categories of directives. The most important ones are:</p>
<p><strong>Fetch directives</strong> control which resources can be loaded. The main ones are:</p>
<ul>
<li><code>default-src</code> — fallback for unspecified resource types</li>
<li><code>script-src</code> — where JavaScript is allowed to load from (the most critical directive for XSS protection)</li>
<li><code>style-src</code> — where CSS stylesheets may be loaded from</li>
<li><code>img-src</code> — where images may be loaded from</li>
<li><code>font-src</code> — where web fonts may be loaded from</li>
<li><code>object-src</code> — where plugins such as <code>&lt;object&gt;</code>, <code>&lt;embed&gt;</code>, <code>&lt;applet&gt;</code> may be loaded from; almost always set to <code>&#39;none&#39;</code></li>
<li><code>frame-src</code> — where <code>&lt;iframe&gt;</code> and <code>&lt;frame&gt;</code> elements are allowed to load from</li>
</ul>
<p><strong>Document directives</strong> include <code>base-uri</code>, which restricts what can appear in the <code>&lt;base&gt;</code> element&#39;s <code>href</code> attribute (preventing base-tag injection attacks).</p>
<p><strong>Navigation directives</strong> include <code>form-action</code>, controlling which pages a <code>&lt;form&gt;</code> may submit to.</p>
<p><strong>Reporting directives</strong> control violation reporting (see the Testing section below).</p>
<p>The values a directive accepts include:</p>
<ul>
<li><code>&#39;self&#39;</code> — resources from the same origin as the document</li>
<li><code>&#39;none&#39;</code> — block this resource type entirely</li>
<li><code>&#39;unsafe-inline&#39;</code> — allow inline <code>&lt;script&gt;</code> blocks and event handlers; <strong>this weakens XSS protection significantly</strong> and should be avoided where possible</li>
<li><code>&#39;unsafe-eval&#39;</code> — allow <code>eval()</code> and similar dynamic code execution; almost never needed</li>
<li><code>&#39;strict-dynamic&#39;</code> — allows a trusted script to load its own dependencies, propagating trust through the script&#39;s own loading chain rather than through a static allowlist</li>
<li><code>&#39;nonce-NUMBER&#39;</code> — a per-request random token; only scripts with a matching <code>nonce</code> attribute are allowed</li>
<li><code>&#39;sha256-HASH&#39;</code> — a SHA-256, SHA-384 or SHA-512 hash of the exact script content; only scripts whose content produces that hash are allowed</li>
<li>Host patterns such as <code>https://cdn.example.com</code> or <code>*.example.org</code></li>
</ul>
</section>
<section><h2 id="clickjacking-and-https-upgrades"><a href="#clickjacking-and-https-upgrades" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Clickjacking and HTTPS upgrades</h2><p>CSP does more than block XSS. The <code>frame-ancestors</code> directive controls which sites are allowed to embed your page in an <code>&lt;iframe&gt;</code>. Setting <code>frame-ancestors &#39;none&#39;</code> prevents your site from being framed entirely, which defends against clickjacking — where an attacker overlays your page with a transparent iframe to trick users into clicking the attacker&#39;s buttons instead of yours. <code>frame-ancestors</code> is a more flexible replacement for the older <code>X-Frame-Options</code> header.</p>
<p>The <code>upgrade-insecure-requests</code> directive tells the browser to rewrite any HTTP resource requests on the page to HTTPS before making them. This is useful when migrating a site from HTTP to HTTPS: include it in the policy and any mixed-content request (an image loaded over HTTP, a script loaded over HTTP) is automatically upgraded rather than blocked.</p>
</section>
<section><h2 id="strict-csp-nonces-and-hashes"><a href="#strict-csp-nonces-and-hashes" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Strict CSP: nonces and hashes</h2><p>A CSP that relies on <code>&#39;unsafe-inline&#39;</code> offers weak XSS protection, because an attacker who can inject an attribute like <code>onclick=</code> can bypass it. A <strong>strict CSP</strong> avoids <code>&#39;unsafe-inline&#39;</code> entirely and instead uses one of two mechanisms for allowing legitimate inline scripts:</p>
<p><strong>Nonces</strong> are a random token generated server-side for each HTTP response and included both in the CSP header and in the <code>nonce</code> attribute of the <code>&lt;script&gt;</code> tags you intend to run:</p>
<pre><code>Content-Security-Policy: script-src &#39;nonce-416d1177-4d12-4e3b-b7c9-f6c409789fb8&#39;
</code></pre>
<pre><code>&lt;script nonce=&quot;416d1177-4d12-4e3b-b7c9-f6c409789fb8&quot;&gt;
  // this script is allowed to run
&lt;/script&gt;
</code></pre>
<p>The nonce changes on every request, so an attacker cannot predict it. Any script injected without the correct nonce is blocked.</p>
<p><strong>Hashes</strong> are a permanent, deterministic alternative: you compute the SHA-256 (or SHA-384 or SHA-512) hash of the exact script content, then add it to the policy:</p>
<pre><code>Content-Security-Policy: script-src &#39;sha256-cd9827ad...=&#39;
</code></pre>
<p>The hash never changes, so this works well for static sites. You can combine <code>&#39;strict-dynamic&#39;</code> with either nonces or hashes: the trusted root scripts are verified by the nonce or hash, and those scripts are then permitted to load their own dependencies without the nonce/hash requirement propagating to every consumer.</p>
</section>
<section><h2 id="testing-and-monitoring-your-policy"><a href="#testing-and-monitoring-your-policy" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Testing and monitoring your policy</h2><p>Deploying a restrictive CSP on a live site without testing first risks breaking your own functionality. Two tools help you do this safely.</p>
<p><strong><code>Content-Security-Policy-Report-Only</code></strong> sends the browser a header that applies the policy in monitoring mode — violations are reported but nothing is blocked. This lets you see what your policy would catch without the risk of taking your site offline:</p>
<pre><code>Content-Security-Policy-Report-Only: default-src &#39;self&#39;; report-uri /csp-reports
</code></pre>
<p><strong>Violation reports</strong> are sent as an HTTP POST to an endpoint you control. Modern browsers use the Reporting API, where you first declare an endpoint:</p>
<pre><code>Reporting-Endpoints: csp-endpoint=&quot;https://example.com/csp-reports&quot;
Content-Security-Policy: default-src &#39;self&#39;; report-to csp-endpoint
</code></pre>
<p>The browser sends a JSON body identifying the blocked resource, the directive that blocked it, and the page that triggered it. Review these reports before enforcing the policy to find any legitimate resources your allowlist is missing.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-does-a-content-security-policy-protect-against">What does a Content Security Policy protect against?</h3><p>CSP primarily defends against cross-site scripting (XSS) attacks by controlling which scripts a page is allowed to execute. It also helps protect against clickjacking (via <code>frame-ancestors</code>), man-in-the-middle attacks on resources (via <code>upgrade-insecure-requests</code>), and injection of malicious iframes or plugins (via <code>object-src</code>). CSP is a defence-in-depth measure — the first line of defence against content injection remains input validation and output encoding on the server side.</p>
<h3 id="what-are-the-most-important-csp-directives-to-know">What are the most important CSP directives to know?</h3><p><code>script-src</code> is the most critical for XSS protection, because it controls where JavaScript may be loaded from. <code>default-src</code> sets the baseline for all resource types, so a conservative <code>default-src &#39;self&#39;</code> combined with explicit overrides for each resource type you actually use is a good starting point. <code>object-src &#39;none&#39;</code> should almost always be set, since plugin content (Flash is long dead, but PDFs and Java applets still exist) is a high-risk attack surface. <code>frame-ancestors &#39;none&#39;</code> prevents your site from being embedded in iframes on other domains.</p>
<h3 id="is-a-csp-hard-to-deploy-on-an-existing-site">Is a CSP hard to deploy on an existing site?</h3><p>It can be, if the site uses a lot of inline scripts, third-party widgets or CDN-hosted resources without a strict allowlist. The safest rollout path is: (1) enable <code>Content-Security-Policy-Report-Only</code> with a permissive policy to collect baseline violation reports; (2) add one or two directives per week while monitoring reports; (3) once reports are clean, switch from <code>Report-Only</code> to <code>Content-Security-Policy</code> to enforce the policy. Using a nonce or hash-based strict CSP is the most secure approach, but it requires server-side generation of per-request nonces, which is harder to implement than a static hash-based policy.</p>
<h3 id="what-does-unsafe-inline-do-and-why-should-it-be-avoided">What does <code>&#39;unsafe-inline&#39;</code> do and why should it be avoided?</h3><p><code>&#39;unsafe-inline&#39;</code> tells the browser to allow any <code>&lt;script&gt;</code> block written directly in the HTML and any event handler attribute such as <code>onclick=</code> or <code>onerror=</code>. This makes CSP easy to deploy on existing sites because it does not require changing how scripts are included. However, it also means that an XSS payload written as an inline script or an event handler is not blocked — an attacker who can inject <code>&lt;script&gt;alert(1)&lt;/script&gt;</code> or <code>&lt;img onerror=alert(1)&gt;</code> will have their code execute. Strict CSP avoids <code>&#39;unsafe-inline&#39;</code> by using nonces or hashes to whitelist only specific, intentional inline scripts.</p>
</section>
<section><h2 id="summary"><a href="#summary" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Summary</h2><p>A Content Security Policy is an HTTP response header that acts as an explicit resource allowlist for the browser. By specifying which domains scripts, styles, images and other resource types may be loaded from, CSP blocks content-injection attacks — particularly XSS — even if an attacker manages to inject malicious markup into your page. The key directives to understand are <code>script-src</code> (for JavaScript), <code>object-src &#39;none&#39;</code> (to block plugins), and <code>frame-ancestors &#39;none&#39;</code> (to prevent clickjacking). A strict CSP uses per-request nonces or pre-computed hashes instead of <code>&#39;unsafe-inline&#39;</code>, which gives the strongest XSS protection. Deploy incrementally using <code>Content-Security-Policy-Report-Only</code> before switching to enforcement mode. CSP is not a substitute for input validation, but it is one of the most effective browser-native defences against the most common class of web attacks.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>What are Core Web Vitals? LCP, INP, and CLS explained</title>
      <link>https://debod.id/en/posts/core-web-vitals-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/core-web-vitals-explained/</guid>
      <pubDate>Wed, 02 Sep 2026 00:00:00 GMT</pubDate>
      <category>performance</category>
      <category>performance</category>
      <category>web</category>
      <description>Core Web Vitals are a set of Google metrics that measure the real-user experience of a web page: loading, interactivity, and visual stability. Learn what LCP, INP, and CLS measure, their target thresholds, and how to improve them.</description>
      <content:encoded><![CDATA[<section><p>If you have ever opened Chrome DevTools and seen a &quot;performance&quot; score, you have touched on the idea of Web Vitals. Core Web Vitals are a set of metrics, defined by Google, that quantify the real user experience of a web page in three dimensions: <strong>loading</strong>, <strong>interactivity</strong>, and <strong>visual stability</strong>. They are the subset of Web Vitals that apply to all web pages, are measured by all site owners, and are surfaced across Google&#39;s tools (PageSpeed Insights, Search Console, Chrome User Experience Report). Each of the three Core Web Vitals is measurable in the field, reflects a user-centric outcome, and has a clearly defined good/poor threshold. This guide explains what each metric measures, its target, and the practical changes that move the needle.</p></section>
<section><h2 id="the-three-core-web-vitals"><a href="#the-three-core-web-vitals" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The three Core Web Vitals</h2><p>The current set of Core Web Vitals focuses on three aspects of the user experience and includes the following metrics:</p>
<table>
<thead>
<tr>
<th>Metric</th>
<th>What it measures</th>
<th>Good threshold</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Largest Contentful Paint (LCP)</strong></td>
<td>Loading performance — when the page&#39;s main content is likely rendered</td>
<td>2.5 seconds or less</td>
</tr>
<tr>
<td><strong>Interaction to Next Paint (INP)</strong></td>
<td>Interactivity — how quickly the page responds to interactions</td>
<td>200 milliseconds or less</td>
</tr>
<tr>
<td><strong>Cumulative Layout Shift (CLS)</strong></td>
<td>Visual stability — how much the layout unexpectedly moves</td>
<td>0.1 or less</td>
</tr>
</tbody></table>
<p>To ensure you are hitting the targets for most of your users, a good threshold to measure is the <strong>75th percentile</strong> of page loads, segmented across mobile and desktop devices.</p>
</section>
<section><h2 id="largest-contentful-paint-lcp-measuring-loading"><a href="#largest-contentful-paint-lcp-measuring-loading" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Largest Contentful Paint (LCP): measuring loading</h2><p>LCP reports the render time of the largest <strong>image, text block, or video</strong> visible in the viewport, relative to when the user first navigated to the page. It reflects perceived load speed: a fast LCP reassures users that the page is useful. Older metrics like <code>load</code> or <code>DOMContentLoaded</code> do not map to what the user actually sees, and First Contentful Paint (FCP) only captures the very beginning of the loading experience — which is why LCP tracks the largest element instead.</p>
<p>LCP considers a limited set of elements: <code>&lt;img&gt;</code> elements, <code>&lt;image&gt;</code> inside <code>&lt;svg&gt;</code>, <code>&lt;video&gt;</code> elements, elements with a background image loaded via <code>url()</code>, and block-level elements containing text. Chromium-based browsers use heuristics to exclude elements users see as &quot;non-contentful&quot;, such as elements with opacity 0, elements that cover the full viewport, or placeholder images with low entropy.</p>
<p>A good LCP is <strong>2.5 seconds or less</strong>; greater than 4.0 seconds is poor.</p>
</section>
<section><h2 id="interaction-to-next-paint-inp-measuring-interactivity"><a href="#interaction-to-next-paint-inp-measuring-interactivity" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Interaction to Next Paint (INP): measuring interactivity</h2><p>INP assesses a page&#39;s overall responsiveness to user interactions by observing the latency of every <strong>click, tap, and keyboard interaction</strong> that occurs throughout the visit. An interaction is a group of event handlers that fire during the same logical user gesture (for example, a tap triggers <code>pointerdown</code>, <code>pointerup</code>, and <code>click</code> together). The reported INP value is the longest interaction observed, ignoring outliers — for pages with many interactions, one highest interaction is ignored for every 50 interactions.</p>
<p>INP replaced <strong>First Input Delay (FID)</strong>. FID only measured the input delay of the first interaction, whereas INP considers the full duration of all interactions across the whole visit. 90% of a user&#39;s time on a page is spent after it loads, so responsiveness throughout the page lifecycle matters.</p>
<p>An INP at or below <strong>200 milliseconds</strong> means good responsiveness; above 500 milliseconds is poor.</p>
</section>
<section><h2 id="cumulative-layout-shift-cls-measuring-visual-stability"><a href="#cumulative-layout-shift-cls-measuring-visual-stability" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Cumulative Layout Shift (CLS): measuring visual stability</h2><p>Unexpected layout shifts can cause users to lose their place while reading or click the wrong button. A layout shift occurs whenever a visible element changes its position from one rendered frame to the next. CLS is the largest burst of layout-shift scores, where a burst (a &quot;session window&quot;) is one or more shifts in rapid succession with less than one second between each and a maximum of five seconds total.</p>
<p>The layout shift score is the product of two measures: the <strong>impact fraction</strong> (how much the unstable elements affect the viewport area) multiplied by the <strong>distance fraction</strong> (how far the elements moved relative to the viewport&#39;s largest dimension).</p>
<p>A good CLS is <strong>0.1 or less</strong>; greater than 0.25 is poor.</p>
</section>
<section><h2 id="field-vs-lab-measurement"><a href="#field-vs-lab-measurement" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Field vs lab measurement</h2><p>Core Web Vitals are, first and foremost, <strong>field metrics</strong> — measured from real users. Two kinds of tools read them:</p>
<ul>
<li><strong>Field tools</strong> use the Chrome User Experience Report (CrUX), which collects anonymized, real user measurement data. PageSpeed Insights, Chrome DevTools, and Search Console&#39;s Core Web Vitals report all surface this data.</li>
<li><strong>Lab tools</strong> load a page in a controlled environment (like Lighthouse) to diagnose issues, using metrics such as Total Blocking Time (TBT) as a proxy for INP.</li>
</ul>
<p>To measure the metrics in JavaScript, Google provides the <code>web-vitals</code> library, which wraps the underlying web APIs (<code>onLCP()</code>, <code>onINP()</code>, <code>onCLS()</code>) into simple calls.</p>
</section>
<section><h2 id="how-to-improve-core-web-vitals"><a href="#how-to-improve-core-web-vitals" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How to improve Core Web Vitals</h2><p>Because each metric measures a different facet, improving it takes different work:</p>
<ul>
<li><strong>Improve LCP:</strong> eliminate render-blocking resources, optimize the server response time, preload and properly size the largest element, avoid slow third-party scripts.</li>
<li><strong>Improve INP:</strong> break up long tasks on the main thread, defer non-essential JavaScript, prefer simpler event handlers, and give immediate visual feedback on interaction.</li>
<li><strong>Improve CLS:</strong> reserve space (dimensions) for images and video, avoid injecting content above existing content, size custom fonts to avoid layout shift when they load, and avoid placing ads/widgets that dynamically resize.</li>
</ul>
<p>On a real site, the highest-impact wins come from the low-hanging fruit — dimensions on media, self-hosted fonts with fallback sizing, and trimming render-blocking resources.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-is-the-difference-between-field-and-lab-core-web-vitals">What is the difference between field and lab Core Web Vitals?</h3><p>Field metrics are measured from real users via the Chrome User Experience Report; lab metrics are measured in a controlled environment (for example with Lighthouse). Core Web Vitals prioritize field data because it reflects the real-world experience, while lab data helps diagnose specific issues.</p>
<h3 id="which-metric-measures-loading-speed">Which metric measures loading speed?</h3><p>Largest Contentful Paint (LCP) measures loading performance — the render time of the largest image, text block, or video visible in the viewport. A good LCP is 2.5 seconds or less.</p>
<h3 id="why-did-inp-replace-first-input-delay-fid">Why did INP replace First Input Delay (FID)?</h3><p>FID only measured the input delay of the first interaction, while INP measures the full duration of all interactions across the entire visit. Because 90% of a user&#39;s time is spent after a page loads, INP gives a more complete picture of responsiveness.</p>
<h3 id="what-causes-cumulative-layout-shift-cls">What causes Cumulative Layout Shift (CLS)?</h3><p>Layout shifts usually happen when resources load asynchronously, images or videos load without reserved dimensions, fonts render differently from their fallback, or third-party ads/widgets resize themselves. CLS is the largest burst of these unexpected layout shifts, and a good score is 0.1 or less.</p>
</section>
<section><h2 id="summary"><a href="#summary" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Summary</h2><p>Core Web Vitals are the essential health metrics for any web page: LCP for loading, INP for interactivity, and CLS for visual stability, each with a concrete good threshold. They are measured from real users and surfaced across Google&#39;s tools, so optimizing them improves both user experience and search ranking. Start with the easy wins — dimensions on media, self-hosted fonts, and fewer render-blocking scripts — then use field data to find the next bottleneck.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>Containers vs Virtual Machines: which should you choose?</title>
      <link>https://debod.id/en/posts/containers-vs-vms/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/containers-vs-vms/</guid>
      <pubDate>Wed, 02 Sep 2026 00:00:00 GMT</pubDate>
      <category>devops</category>
      <category>containers</category>
      <category>docker</category>
      <category>devops</category>
      <description>Containers and virtual machines both isolate workloads, but they do it in fundamentally different ways. Compare architecture, performance, isolation, security, and use cases to pick the right one for your application.</description>
      <content:encoded><![CDATA[<section><p>When you deploy a modern application, two questions come up almost immediately: what runs the code, and what isolates one workload from another? The two dominant answers are containers (most commonly Docker) and virtual machines (VMs). Both package an application and its dependencies, both isolate it from the host, and both are the foundation of modern cloud computing — but they do their work at very different layers of the stack.</p>
<p>The short version: containers share the host operating system&#39;s kernel and package only the application and its user-space libraries. Virtual machines run a complete guest operating system on top of a hypervisor, with their own kernel, their own userspace, and virtualized hardware. The trade-off is between density and speed on one side, and isolation strength and OS flexibility on the other.</p></section>
<section><h2 id="what-a-virtual-machine-actually-is"><a href="#what-a-virtual-machine-actually-is" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What a virtual machine actually is</h2><p>A virtual machine is the virtualization or emulation of an entire computer system. It provides the functionality of a physical machine — BIOS, CPU, memory, disk, network — to a guest operating system, while the real hardware is shared and managed by a piece of software called a hypervisor.</p>
<p>The term was first formalized by Popek and Goldberg in 1974, who defined a virtual machine as &quot;an efficient, isolated duplicate of a real computer machine.&quot; IBM&#39;s CP-40 and CP-67, predecessors of the VM family, were the first widely available virtual machine architectures; the CP-40 was created in 1966 and used full virtualization to allow a single mainframe to run multiple isolated operating systems concurrently.</p>
<p>There are two broad classes. System virtual machines (also called full virtualization VMs) provide a substitute for a real machine and can execute an entire operating system; a hypervisor sits directly on the hardware (Type 1, or &quot;bare-metal&quot;, such as VMware ESXi, Hyper-V, or KVM) or on top of a host OS (Type 2, such as VirtualBox or VMware Workstation). Process virtual machines are different — they run as a normal application and abstract a programming environment, like the Java virtual machine or the .NET Common Language Runtime. When people say &quot;VM&quot; in a deployment context, they almost always mean the system virtual machine.</p>
<p>A hypervisor manages several guest operating systems on a single physical host. Each guest sees virtualized hardware, runs its own kernel, and believes it has the machine to itself. Modern hypervisors use hardware-assisted virtualization — features in the host CPU such as Intel VT-x or AMD-V — to make the emulation efficient.</p>
</section>
<section><h2 id="what-a-container-actually-is"><a href="#what-a-container-actually-is" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What a container actually is</h2><p>A container is a standardized, encapsulated environment that runs an application, isolated from other containers on the same host. Containers are built on operating-system-level virtualization, a paradigm in which the operating system kernel allows multiple isolated user-space instances to exist on a single machine. On Linux, the isolation is implemented with two kernel features: namespaces, which give each container its own view of process trees, network, user IDs, and mounted file systems, and cgroups, which limit the resources a container may use, such as memory and CPU.</p>
<p>Because all containers on a machine share the services of a single operating system kernel, they use far fewer resources than virtual machines. They do not need to boot a guest OS; they start as fast as a regular process. Docker, the most common container engine, was first released as open source in March 2013 and remains under active development by Docker, Inc. The engine itself is licensed under the Apache License 2.0.</p>
<p>Containers are not the only form of OS-level virtualization. The same family includes FreeBSD jails, Solaris Containers, AIX Workload Partitions, Linux-VServer, and OpenVZ, all of which share the host kernel and partition user-space. Docker&#39;s contribution was largely ergonomic: a standard image format, a simple build pipeline, and a daemon that ties the whole thing together.</p>
</section>
<section><h2 id="how-they-differ-architecturally"><a href="#how-they-differ-architecturally" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How they differ architecturally</h2><p>The architectural difference is the heart of the matter. A virtual machine runs a full guest operating system, with its own kernel, on top of a hypervisor that virtualizes the hardware. A container runs the application directly on the host&#39;s kernel, with isolation enforced by namespaces and cgroups.</p>
<table>
<thead>
<tr>
<th></th>
<th>Containers (Docker)</th>
<th>Virtual machines</th>
</tr>
</thead>
<tbody><tr>
<td>What is replicated</td>
<td>Application + user-space libraries, sharing the host kernel</td>
<td>Full guest operating system with its own kernel</td>
</tr>
<tr>
<td>Isolation mechanism</td>
<td>Linux namespaces + cgroups (kernel features)</td>
<td>Hypervisor (Type 1: VMware ESXi, Hyper-V, KVM; Type 2: VirtualBox)</td>
</tr>
<tr>
<td>Boot time</td>
<td>Seconds, often under one second</td>
<td>Tens of seconds to minutes</td>
</tr>
<tr>
<td>Image size</td>
<td>Megabytes — application + libraries</td>
<td>Gigabytes — full OS plus application</td>
</tr>
<tr>
<td>Density on a host</td>
<td>Tens to hundreds of containers per host</td>
<td>Tens of VMs per host, typically fewer</td>
</tr>
<tr>
<td>Performance overhead</td>
<td>Near-native — normal system calls, no emulation</td>
<td>Noticeable — full OS boot, virtualized I/O, hypervisor scheduling</td>
</tr>
<tr>
<td>OS flexibility</td>
<td>Shares the host kernel; cannot run a different kernel (Linux container on Windows still needs a Linux VM under WSL 2)</td>
<td>Can run any guest OS the hypervisor supports (e.g. Windows on Linux host)</td>
</tr>
<tr>
<td>Hardware-assisted virtualization</td>
<td>Not required</td>
<td>Effectively required for performance on x86</td>
</tr>
</tbody></table>
<p>The same physical machine can typically run many more containers than VMs, simply because each container is just a process with some isolation. A 2018 analysis of Docker use found that a typical organization runs around eight containers per host, with a quarter of organizations running 18 or more per host — a density that is hard to match with full VMs.</p>
</section>
<section><h2 id="performance-and-density"><a href="#performance-and-density" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Performance and density</h2><p>Containers are lighter in every meaningful sense. They start as fast as a process, because there is no operating system to boot. They use less memory, because the kernel is loaded once for the host and shared by every container. They use less disk, because images only need to package the application and its libraries, not a complete operating system. A typical container image is in the tens to hundreds of megabytes; a typical VM image is in the gigabytes.</p>
<p>The performance overhead of containers is small. Because the application inside a container uses the host&#39;s normal system call interface, there is no emulation layer. The cost is the namespace and cgroup bookkeeping done by the kernel, which is well under 5% in most workloads.</p>
<p>Virtual machines pay a higher cost. Each VM runs a full kernel and a full userspace, which means memory overhead for the OS itself, CPU overhead for the hypervisor, and I/O overhead for virtualized disk and network. The gap narrows with hardware-assisted virtualization and modern paravirtualized drivers, but the structural cost remains: a VM is a more isolated, heavier, slower-to-start unit than a container.</p>
</section>
<section><h2 id="isolation-and-security"><a href="#isolation-and-security" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Isolation and security</h2><p>This is where the trade-off becomes real. A virtual machine is, by design, more strongly isolated than a container. The hypervisor draws a sharp boundary around the guest: a bug or compromise inside the guest cannot directly touch the host kernel or other guests, because every guest has its own kernel. A container escape — a vulnerability that lets a process break out of its container — is more serious, because the escaped process is now running on the host kernel with the ability to affect other containers.</p>
<p>The practical implication is that virtual machines are the default choice when you need to run untrusted workloads, when you have hard multi-tenant requirements, or when the regulatory environment demands strong isolation between workloads. Containers are the default for trusted, application-tier workloads where the deployment density, fast startup, and image portability matter more than the strongest possible isolation boundary.</p>
<p>In practice, the two are often combined. A common pattern is to run containers inside VMs — a VM gives you isolation between tenants, and containers inside the VM give you density and a clean deployment story for each application. Cloud providers such as AWS, Azure, and Google Cloud run their container services (ECS, AKS, GKE) on top of virtual machines at the infrastructure layer; you do not see the VMs, but they are there.</p>
</section>
<section><h2 id="operating-system-flexibility"><a href="#operating-system-flexibility" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Operating system flexibility</h2><p>Virtual machines win this category decisively. A VM can run any operating system that the hypervisor supports, including different kernels — a Linux VM on a Windows host, a Windows VM on a Linux host, a FreeBSD VM on any of the above. This is invaluable for cross-platform development, for running legacy software, and for kernels that need to be different from the host.</p>
<p>Containers are tied to the host kernel. A Linux container runs the Linux kernel of the host. On Windows or macOS, Docker Desktop runs a Linux virtual machine to host the containers, so the practical story is &quot;container on a Linux VM on Windows&quot; — a model that works, but does add a layer. Windows containers exist, but require Windows Server as the host; a Linux container cannot run natively on a Windows kernel.</p>
<p>The flexibility of VMs also shows up in kernel features. If you need a specific kernel module, a specific kernel version, or a kernel hardened with a specific security profile, a VM lets you install any kernel you want. A container cannot do that — it has to use what the host provides.</p>
</section>
<section><h2 id="when-to-use-which"><a href="#when-to-use-which" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>When to use which</h2><p>The two technologies are not competitors so much as answers to different questions. Containers are best when:</p>
<ul>
<li>You want to package and ship an application as a single artifact that runs the same way everywhere.</li>
<li>You need fast startup, high density, and small image sizes.</li>
<li>The application is trusted (your own code, or vetted third-party code).</li>
<li>You are deploying microservices, where each service is a separate process and can be scaled independently.</li>
<li>You are using an orchestrator such as Kubernetes, which expects container images as its packaging format.</li>
</ul>
<p>Virtual machines are best when:</p>
<ul>
<li>You need to run a different operating system or a different kernel from the host.</li>
<li>You have hard multi-tenant isolation requirements, or you are running untrusted code.</li>
<li>You need to install kernel modules, custom kernels, or kernel-level security tooling.</li>
<li>The workload is monolithic and benefits from running an entire operating system.</li>
<li>The provider or regulatory environment requires VM-level isolation.</li>
</ul>
<p>Most modern production systems use both. A cloud VM hosts Kubernetes; Kubernetes hosts containers; each container hosts a microservice. The VM is the security and isolation boundary between tenants; the container is the deployment unit for individual applications.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="can-a-container-run-a-different-operating-system-than-the-host">Can a container run a different operating system than the host?</h3><p>No, not natively. A container shares the host kernel, so it can only run user-space software that is compatible with that kernel. A Linux container on a Linux host works; a Linux container on a Windows kernel does not, and Docker Desktop on Windows works around this by running a Linux virtual machine to host the containers. To run a different kernel, you need a virtual machine.</p>
<h3 id="which-is-more-secure-a-container-or-a-virtual-machine">Which is more secure, a container or a virtual machine?</h3><p>Virtual machines are more strongly isolated, because each VM has its own kernel and is separated from the host and other VMs by the hypervisor. A container shares the host kernel, so a container escape vulnerability is more serious. In practice, both can be secured; the difference is the size of the security boundary. For untrusted code, VMs are the safer default; for trusted code, containers are usually considered acceptable.</p>
<h3 id="why-are-containers-faster-to-start-than-vms">Why are containers faster to start than VMs?</h3><p>A container is essentially a process with isolation applied by the kernel. It does not need to boot an operating system, load a kernel, or initialize system services. A VM must run through the full boot sequence of a guest operating system, which takes tens of seconds at minimum.</p>
<h3 id="can-a-vm-run-containers-inside-it">Can a VM run containers inside it?</h3><p>Yes, and this is the standard pattern in the cloud. A VM hosts a container runtime such as Docker or containerd, and the container runtime hosts individual containers. Cloud container services such as AWS ECS, Azure AKS, and Google Cloud GKE all run containers on top of virtual machines under the hood; the user sees only the container interface.</p>
<h3 id="should-i-move-my-existing-vms-to-containers">Should I move my existing VMs to containers?</h3><p>Not directly. Containers and VMs solve different problems. If you have a monolithic application that runs on a specific operating system, keeping it in a VM is often the right choice. If you are refactoring the application into microservices, or you want faster deployments and easier scaling, the new components are usually a better fit for containers. A common migration path is to keep the existing VMs and add containers for new services, gradually decomposing the monolith.</p>
<h3 id="do-containers-replace-vms">Do containers replace VMs?</h3><p>No. They occupy different layers of the stack. A container is a packaging and runtime format for an individual process; a VM is a hardware-level isolation boundary that can host any operating system. Modern cloud platforms use both: VMs at the infrastructure layer for isolation and multi-tenancy, containers at the application layer for density and fast deployment.</p>
</section>
<section><h2 id="summary"><a href="#summary" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Summary</h2><p>Containers and virtual machines both isolate workloads, but they do it at different layers. Containers share the host kernel and use namespaces and cgroups to isolate processes; virtual machines run a complete guest operating system on a hypervisor and provide a much stronger isolation boundary. Containers are lighter, faster, denser, and better suited to trusted, application-tier workloads that benefit from fast startup and portable images. Virtual machines are heavier and slower but offer OS flexibility, kernel-level isolation, and the strongest boundary between untrusted workloads. Most modern systems use both: VMs at the infrastructure layer, containers at the application layer.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>What is XSS (Cross-Site Scripting)? How it works and how to prevent it</title>
      <link>https://debod.id/en/posts/xss-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/xss-explained/</guid>
      <pubDate>Tue, 01 Sep 2026 00:00:00 GMT</pubDate>
      <category>security</category>
      <category>security</category>
      <category>web</category>
      <category>javascript</category>
      <description>Cross-Site Scripting (XSS) lets an attacker run their own JavaScript in another user&apos;s browser. This guide explains how the three types work and the defenses that stop them.</description>
      <content:encoded><![CDATA[<section><p>Cross-Site Scripting (XSS) is a type of injection attack in which an attacker injects a malicious script into an otherwise trusted website. When the page runs, the script executes in the victim&#39;s browser with the trust and privileges of that site — the victim&#39;s origin, cookies, session, and ability to read and modify the page. Because the browser thinks the script came from a trusted source, it grants it the same access as legitimate code.</p>
<p>XSS is one of the oldest and most persistent web vulnerabilities. It sits in the injection category of the OWASP Top 10, and it can range from an annoyance to complete account compromise. This guide explains how the three main types work, what an attacker can do with them, and the defenses that reliably stop them.</p></section>
<section><h2 id="how-xss-works"><a href="#how-xss-works" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How XSS works</h2><p>XSS happens when an application takes input from an untrusted source and includes it in the HTML or JavaScript it sends to a browser without safely encoding or validating it. The browser cannot tell that the injected fragment is not part of the site&#39;s own code, so it executes it with the same origin and privileges.</p>
<p>For example, consider a search page that echoes the query back, like this:</p>
<pre><code>&lt;h2&gt;Results for: &lt;user input&gt;&lt;/h2&gt;
</code></pre>
<p>A normal request produces <code>Results for: node</code>. But an attacker who crafts a request such as <code>?q=&lt;script&gt;…&lt;/script&gt;</code> makes the application write an actual <code>&lt;script&gt;</code> tag into the page, which the browser then executes.</p>
<p>Because the injected script runs inside the trusted origin, it can access the site&#39;s cookies and session tokens, read and modify the DOM, make authenticated requests to the site&#39;s APIs, or rewrite the page to phish for credentials. The browser&#39;s Same-Origin Policy offers no protection here — the code is running <em>on the inside</em> with the site&#39;s own privileges.</p>
</section>
<section><h2 id="the-three-types-of-xss"><a href="#the-three-types-of-xss" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The three types of XSS</h2><p>XSS is usually grouped into three types, separated by how the payload reaches the victim.</p>
<h3 id="reflected-xss">Reflected XSS</h3><p>Reflected (also called non-persistent or Type-I) XSS happens when user input is immediately echoed back by the server in a response — for example in a search result, an error message, or another response that includes part of the request — without being made safe to render. The payload lives in the request itself and never touches the server&#39;s storage.</p>
<p>Reflected XSS needs the victim to do something: click a crafted link, submit a specially crafted form, or visit a malicious page that triggers the request. The attacker typically delivers the URL through email, social media, or another site.</p>
<h3 id="stored-xss">Stored XSS</h3><p>Stored (also called persistent or Type-II) XSS happens when the injected script is permanently stored on the target server — in a database, a comment field, a message forum, a visitor log, or a user profile — and is later served to every user who views that data. Because no extra interaction with the victim is needed beyond visiting the page, stored XSS is generally considered the more dangerous of the three.</p>
<h3 id="dom-based-xss">DOM-based XSS</h3><p>DOM-based XSS is a client-side problem. The server&#39;s response is the same for every user, but client-side JavaScript copies an attacker-controlled source — such as <code>location.hash</code>, <code>location.search</code>, or <code>document.referrer</code> — into a dangerous sink such as <code>innerHTML</code> or <code>document.write</code>. The payload never leaves the browser, so the server never sees it and cannot sanitize it at the response level. Frameworks can open the same hole when code uses raw-HTML insertion such as <code>dangerouslySetInnerHTML</code> (React) or <code>v-html</code> (Vue).</p>
</section>
<section><h2 id="what-an-attacker-can-do"><a href="#what-an-attacker-can-do" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What an attacker can do</h2><p>The consequences of XSS are similar regardless of which type it is — the difference is only in how the payload arrives. A successful exploit can:</p>
<ul>
<li><strong>Steal session tokens and cookies</strong>, letting the attacker impersonate the victim.</li>
<li><strong>Read sensitive data</strong> held in the page or sent to the site&#39;s APIs.</li>
<li><strong>Rewrite the page</strong> to show fake login forms or phishing content.</li>
<li><strong>Make authenticated requests</strong> on the victim&#39;s behalf (which also bypasses CSRF protections).</li>
<li><strong>Install a keylogger</strong> or other script that records what the victim types.</li>
</ul>
<p>A &quot;read-only&quot; or brochure-style site is not immune — reflected XSS can still appear in search and error responses.</p>
</section>
<section><h2 id="how-to-prevent-xss"><a href="#how-to-prevent-xss" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How to prevent XSS</h2><p>Prevention follows a defense-in-depth approach, because no single control is sufficient on its own.</p>
<h3 id="1-contextually-aware-output-encoding">1. Contextually aware output encoding</h3><p>The primary defense is to encode untrusted data in the correct way for the context where it is rendered — HTML, an HTML attribute, JavaScript, CSS, or a URL all need different escaping rules. A framework that auto-escapes by default, used consistently, removes most of the risk. The OWASP XSS Prevention Cheat Sheet documents the correct encoding for each context.</p>
<h3 id="2-avoid-raw-html-and-unsafe-sinks-in-client-code">2. Avoid raw-HTML and unsafe sinks in client code</h3><p>On the client side, prefer <code>textContent</code> and safe DOM methods over <code>innerHTML</code>, <code>outerHTML</code>, <code>document.write</code>, and <code>eval</code>. If you must insert HTML, sanitize it with a tested allow-list sanitizer such as DOMPurify, and never funnel location or referrer data into a sink. Treat framework escape-hatches like <code>dangerouslySetInnerHTML</code> and <code>v-html</code> as the security-sensitive paths they are.</p>
<h3 id="3-context-sensitive-input-validation">3. Context-sensitive input validation</h3><p>Validate input against an allow list of what is expected (for example, only alphanumeric characters for a username). Validation alone does not prevent XSS — it must be combined with encoding — but it reduces the chance of dangerous characters reaching a render path.</p>
<h3 id="4-use-the-http-security-headers-and-frameworks">4. Use the HTTP security headers and frameworks</h3><p>A modern Content-Security-Policy (CSP) that restricts where scripts can load from is a strong second layer: even if a payload slips through, the browser refuses to run it. Frameworks with built-in auto-escaping and template systems also prevent most injection by default, provided developers do not bypass them with raw-HTML APIs.</p>
<h3 id="5-test-for-xss">5. Test for XSS</h3><p>Treat XSS as a class of bug to hunt for, not a one-time fix. Automated scanners, manual testing, and source review of every place user input reaches output should be part of the development workflow.</p>
</section>
<section><h2 id="security-in-a-larger-picture"><a href="#security-in-a-larger-picture" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Security in a larger picture</h2><p>XSS is one member of the broader injection family, which also includes SQL injection and command injection. They share the same root cause — untrusted data being treated as executable code — and the same first line of defense: keep data separate from the instructions that process it, and encode or validate at every boundary. For related concepts, see our guides on <a href="/en/posts/sql-injection-explained/">SQL injection</a> and <a href="/en/posts/https-explained/">HTTPS</a>.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-is-xss-cross-site-scripting">What is XSS (Cross-Site Scripting)?</h3><p>XSS is an injection attack in which malicious script is injected into a trusted website and executed in another user&#39;s browser with the site&#39;s origin and privileges. It can steal cookies and session tokens, read page data, modify the page, or make authenticated requests on the victim&#39;s behalf.</p>
<h3 id="what-are-the-three-types-of-xss">What are the three types of XSS?</h3><p>Reflected XSS echoes request input straight back in a response without safe encoding; stored XSS persists the payload on the server and serves it to every visitor; DOM-based XSS happens entirely client-side when JavaScript copies an attacker-controlled source into a dangerous sink like <code>innerHTML</code>.</p>
<h3 id="can-xss-steal-passwords">Can XSS steal passwords?</h3><p>XSS can steal anything the page can access, including submitted form values and entered credentials. An attacker can rewrite a page to show a fake login form, or install a script that records keystrokes, then capture the victim&#39;s password when it is typed.</p>
<h3 id="how-do-i-check-if-my-site-is-vulnerable-to-xss">How do I check if my site is vulnerable to XSS?</h3><p>Look for every place where user input reaches the HTML, an attribute, a script, or a URL, and confirm it is encoded for that context. Common targets are search and error responses (reflected), comments and profiles (stored), and client-side code that writes to <code>innerHTML</code> or similar sinks (DOM-based). Automated scanners and manual testing help surface these paths.</p>
</section>
<section><h2 id="summary"><a href="#summary" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Summary</h2><p>Cross-Site Scripting is an injection vulnerability where attacker-controlled script runs in a victim&#39;s browser with the trust of a legitimate site. It is classified into reflected, stored, and DOM-based types, which differ only in how the payload reaches the victim. In every case it can steal sessions, read sensitive data, and act as the victim. The reliable defenses are contextually aware output encoding, avoiding raw-HTML and unsafe client-side sinks, allow-list input validation, strong Content-Security-Policy headers, and regular testing. Because XSS is easy to trigger and its impact can be severe, any application that renders user input should treat prevention as mandatory, not optional.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>What is WebAssembly (WASM)? How it works and when to use it</title>
      <link>https://debod.id/en/posts/webassembly-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/webassembly-explained/</guid>
      <pubDate>Tue, 01 Sep 2026 00:00:00 GMT</pubDate>
      <category>programming</category>
      <category>web</category>
      <category>programming</category>
      <category>performance</category>
      <category>javascript</category>
      <description>WebAssembly is a binary instruction format for a stack-based virtual machine — a portable compilation target that lets you run C, C++, Rust, and other compiled languages in the browser and beyond. Here&apos;s how it works and when it&apos;s worth using.</description>
      <content:encoded><![CDATA[<section><p>WebAssembly (abbreviated Wasm) is a binary instruction format for a stack-based virtual machine, designed as a portable compilation target for programming languages. It enables high-performance applications to run in the browser, on servers, and at the edge — anywhere a Wasm runtime exists.</p>
<p>The WebAssembly specification is maintained by the W3C WebAssembly Working Group. The first W3C Recommendation (version 1.0) shipped in December 2019. Version 2.0 became a W3C standard in December 2024, and version 3.0 arrived in September 2025 with 64-bit address space, garbage-collected types, and improved exception handling.</p></section>
<section><h2 id="why-webassembly-exists"><a href="#why-webassembly-exists" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Why WebAssembly exists</h2><p>JavaScript was designed for interactivity, not for compute-intensive tasks. As web applications grew in complexity — video editors, 3D games, audio workstations, scientific simulations — JavaScript alone hit performance ceilings.</p>
<p>WebAssembly solves this by providing a compact binary format that runs at near-native speed in the browser&#39;s virtual machine. Any language that can compile to Wasm — C, C++, Rust, Go, Python, and dozens more — can run on the web without being rewritten in JavaScript.</p>
<p>WebAssembly is not a replacement for JavaScript. It is a complement: JavaScript handles the DOM and the web ecosystem, while Wasm handles the heavy computation underneath.</p>
</section>
<section><h2 id="how-webassembly-works"><a href="#how-webassembly-works" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How WebAssembly works</h2><h3 id="the-binary-format">The binary format</h3><p>WebAssembly code is distributed as <code>.wasm</code> files — binary files designed for machines, not humans. A companion human-readable text format <code>.wat</code> exists, using S-expressions, with a 1:1 correspondence to the binary format.</p>
<p>The binary format has a simple structure: a magic number (<code>\0asm</code>, bytes <code>0x00 0x61 0x73 0x6d</code>), a 4-byte version field, and a sequence of sections — type definitions, function declarations, exports, code, memory, tables, and data.</p>
<p>Instructions use LEB128 variable-length encoding, keeping the format compact.</p>
<h3 id="the-virtual-machine">The virtual machine</h3><p>WebAssembly runs inside a virtual machine embedded in browsers (or standalone runtimes). The VM is a stack machine: instructions pop arguments from an implicit operand stack and push results back. The stack layout is fixed, so Wasm engines can compile the stack away and produce efficient machine code for registers at runtime.</p>
<p>Four key concepts define a Wasm module:</p>
<ul>
<li><strong>Module</strong> — the compiled Wasm binary: stateless, shareable.</li>
<li><strong>Memory</strong> — a resizable ArrayBuffer (a linear array of bytes) that Wasm code reads and writes with <code>load</code> and <code>store</code> instructions at byte-level granularity.</li>
<li><strong>Table</strong> — a resizable typed array of references (such as function references) that cannot be stored directly in Memory for safety and portability reasons.</li>
<li><strong>Instance</strong> — a Module paired with its runtime state (Memory, Table, imported values).</li>
</ul>
<h3 id="sandbox-security">Sandbox security</h3><p>Wasm runs in a sandboxed environment. It cannot access the filesystem, network, or system APIs unless the host explicitly grants those capabilities through imports. This makes Wasm safe to run even on untrusted code — it cannot corrupt memory or escalate privileges.</p>
<h3 id="wasi-webassembly-system-interface">WASI: WebAssembly System Interface</h3><p>WASI (WebAssembly System Interface) is a set of standardized APIs that lets Wasm modules access system resources — file I/O, environment variables, clocks, random numbers, sockets — in a portable, secure way. WASI uses capability-based security: a Wasm module starts with no ambient authority; it can only do what the host explicitly grants.</p>
<p>WASI 0.1 (Preview 1) launched the ecosystem. WASI 0.2 (Preview 2) introduced the Component Model, enabling composable multi-language Wasm applications. WASI 0.3 (Preview 3) added native async support via streams and futures.</p>
<p>Runtimes supporting WASI include Wasmtime, Wasmer, WasmEdge, WAMR, and wazero.</p>
</section>
<section><h2 id="languages-that-compile-to-webassembly"><a href="#languages-that-compile-to-webassembly" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Languages that compile to WebAssembly</h2><p>As of 2021, around 40 programming languages support WebAssembly as a compilation target. The most mature options:</p>
<ul>
<li><strong>C/C++</strong> — via Emscripten (LLVM/Clang-based, default output changed to Wasm in 2019) or standalone Clang 8+. Used to port Unreal Engine 3, SQLite, AutoCAD, and Qt to the browser.</li>
<li><strong>Rust</strong> — first-class support via <code>rustc</code> with <code>wasm32-wasi</code> or <code>wasm32-unknown-unknown</code> targets. The Rust WebAssembly Working Group maintains the tooling.</li>
<li><strong>Go</strong> — native support introduced in Go 1.11.</li>
<li><strong>AssemblyScript</strong> — a TypeScript variant that compiles to Wasm. Developers familiar with TypeScript can target Wasm without learning a new language.</li>
<li><strong>Python</strong> (Pyodide), <strong>.NET</strong> (Blazor), <strong>Java/Kotlin</strong> (CheerpJ), <strong>Ruby</strong> (MRuby), <strong>Dart</strong> (Flutter web builds), and others.</li>
</ul>
</section>
<section><h2 id="use-cases"><a href="#use-cases" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Use cases</h2><h3 id="high-performance-web-applications">High-performance web applications</h3><p>WebAssembly shines for compute-intensive tasks where JavaScript hits performance limits: 3D games, Virtual/Augmented Reality, computer vision, image and video editing, audio processing, scientific simulations. Figma, the collaborative design tool, uses C++ compiled to Wasm to process complex design files in the browser — a task that would be sluggish in JavaScript alone.</p>
<h3 id="porting-existing-codebases">Porting existing codebases</h3><p>Emscripten has been used to port SQLite, AutoCAD, and game engines (Unity, Godot, Unreal Engine 3) to the browser. Companies get browser versions of their native applications without rewriting them in JavaScript.</p>
<h3 id="server-side-and-edge-computing">Server-side and edge computing</h3><p>Outside the browser, Wasm runtimes let you run Wasm modules as server-side plugins or at the edge. Cloudflare Workers uses Wasm via V8 isolates to execute code at edge locations with fast cold starts. AWS Lambda and similar platforms are exploring Wasm for lower latency and better isolation than traditional containers.</p>
<h3 id="plugin-systems">Plugin systems</h3><p>Proxy-Wasm (the WebAssembly-based ABI used by Envoy and other proxy servers) allows plugins to extend infrastructure safely without native code risks. Software platforms distribute modular pieces as Wasm components for sandboxed extensibility.</p>
</section>
<section><h2 id="browser-support"><a href="#browser-support" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Browser support</h2><p>WebAssembly reached broad browser support in March 2017:</p>
<ul>
<li><strong>Chrome</strong> version 57 (March 2017)</li>
<li><strong>Firefox</strong> version 52 (March 2017)</li>
<li><strong>Safari</strong> version 11 (September 2017)</li>
<li><strong>Microsoft Edge</strong> version 16 (October 2017)</li>
</ul>
<p>All major browsers today support WebAssembly. Feature availability varies:</p>
<ul>
<li><strong>SIMD</strong> — Chrome 91+, Firefox 79+, Safari 15.2+</li>
<li><strong>Threads</strong> — Chrome 75+, Firefox 79+, Safari 14.1+</li>
<li><strong>Memory64</strong> — Chrome 133+, Firefox 134+, Safari (not yet)</li>
<li><strong>WasmGC</strong> (garbage-collected types) — shipped in browsers in early 2025</li>
</ul>
</section>
<section><h2 id="webassembly-vs-javascript"><a href="#webassembly-vs-javascript" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>WebAssembly vs JavaScript</h2><h3 id="when-to-use-webassembly">When to use WebAssembly</h3><ul>
<li>Compute-intensive tasks requiring near-native speed: image/video processing, cryptography, physics simulations, 3D rendering.</li>
<li>Running existing C/C++, Rust, or other compiled code in the browser without a full port to JavaScript.</li>
<li>Predictable, deterministic performance (Wasm avoids JavaScript engine JIT warm-up variability).</li>
<li>Large codebases where the binary format is significantly smaller than the equivalent JavaScript bundle.</li>
</ul>
<h3 id="when-to-use-javascript">When to use JavaScript</h3><ul>
<li>DOM manipulation and direct Web API access (Wasm cannot directly access the DOM; it calls JavaScript, which calls Web APIs).</li>
<li>Rapid development, prototyping, flexible dynamic typing.</li>
<li>Small to medium-sized applications where the JavaScript ecosystem and npm library landscape are a bigger advantage.</li>
<li>Tasks where garbage collection pauses are acceptable.</li>
</ul>
<h3 id="key-limitation-the-dom">Key limitation: the DOM</h3><p>Wasm cannot directly access the browser&#39;s Document Object Model (DOM). It can only pass primitive data types (integers and floating-point numbers) to JavaScript functions. JavaScript then handles the DOM and Web API calls. WasmGC (garbage-collected types) plans to allow Wasm to call Web APIs directly, but full DOM access is still in the future.</p>
<h3 id="performance-characteristics">Performance characteristics</h3><ul>
<li><strong>Wasm vs native code</strong>: Early benchmarks showed Wasm running at about 91% of comparable native speed. Later benchmarks show a range of 33%–200% depending on workload.</li>
<li><strong>Wasm vs JavaScript</strong>: A 2022 study found Wasm runs approximately 20% faster than comparable JavaScript on compute tasks.</li>
<li><strong>Cryptography</strong>: The fastest Wasm runtime runs cryptographic operations roughly 2.3x slower than native code. Some operations can be up to 80x slower when native CPU instructions are unavailable in Wasm.</li>
<li><strong>Cold start</strong>: Wasm modules can have faster cold-start times than JavaScript Virtual Machines for large applications, because parsing and compilation are simpler.</li>
</ul>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-is-the-difference-between-webassembly-and-javascript">What is the difference between WebAssembly and JavaScript?</h3><p>JavaScript is an interpreted, dynamically typed language that runs in the browser&#39;s JavaScript engine and can directly manipulate the DOM. WebAssembly is a binary compilation target that runs in a sandboxed virtual machine — it is faster for compute-intensive tasks but cannot directly access the DOM. They are complementary: JavaScript handles the web ecosystem, while Wasm handles heavy computation. A typical architecture runs JavaScript to call Wasm functions for the heavy lifting.</p>
<h3 id="can-webassembly-replace-javascript">Can WebAssembly replace JavaScript?</h3><p>No. WebAssembly is not a replacement for JavaScript — it is a complement. WebAssembly cannot access the DOM directly, cannot load modules from the web the way ES modules work, and does not have the same developer experience as JavaScript. The standard pattern is to use JavaScript to orchestrate the application and call Wasm functions for compute-intensive parts.</p>
<h3 id="is-webassembly-only-for-browsers">Is WebAssembly only for browsers?</h3><p>No. The WebAssembly Component Model and WASI (WebAssembly System Interface) enable WebAssembly to run on servers, at the edge, in plugins, and in embedded systems. Cloudflare Workers, AWS Lambda, and many other platforms run Wasm outside the browser. The specification is platform-independent by design.</p>
<h3 id="what-does-wasi-mean">What does WASI mean?</h3><p>WASI stands for WebAssembly System Interface. It is a standardized set of APIs that gives Wasm modules portable, secure access to system resources — files, clocks, random numbers, networking — without requiring browser-specific JavaScript APIs. WASI makes it practical to compile the same Wasm module to run on a browser, a server, or an edge function.</p>
<h3 id="is-webassembly-secure">Is WebAssembly secure?</h3><p>Yes. WebAssembly runs in a sandbox that prevents memory corruption and unauthorized system access. A Wasm module cannot read or write memory outside its linear memory region, and it cannot access the filesystem, network, or other system resources unless the host explicitly grants those capabilities. WASI extends this with capability-based security: modules start with zero authority and receive only the specific capabilities they need.</p>
</section>
<section><h2 id="summary"><a href="#summary" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Summary</h2><p>WebAssembly is a binary instruction format for a stack-based virtual machine — a compilation target that runs at near-native speed in browsers, on servers, and at the edge. It solves JavaScript&#39;s performance ceiling for compute-intensive tasks by letting you run C, C++, Rust, Go, and other compiled languages on the web without rewriting them.</p>
<p>Wasm is not a JavaScript replacement. It cannot access the DOM, and it works best alongside JavaScript: JavaScript handles the web ecosystem, Wasm handles the heavy computation. The technology has matured from an MVP in 2017 to version 3.0 in 2025, with WASI enabling serious server-side and edge computing use cases.</p>
<p>For developers building performance-critical web applications, porting existing native codebases to the browser, or running sandboxed server-side plugins, WebAssembly is worth understanding — and increasingly worth using.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>What is SSRF (Server-Side Request Forgery)? How it works and how to prevent it</title>
      <link>https://debod.id/en/posts/ssrf-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/ssrf-explained/</guid>
      <pubDate>Tue, 01 Sep 2026 00:00:00 GMT</pubDate>
      <category>security</category>
      <category>security</category>
      <category>web</category>
      <category>networking</category>
      <description>Server-Side Request Forgery (SSRF) lets an attacker abuse a server&apos;s own URL-fetching feature to reach internal systems and cloud metadata. This guide explains the attack and the defenses that stop it.</description>
      <content:encoded><![CDATA[<section><p>Server-Side Request Forgery (SSRF) is a security vulnerability that lets an attacker make a vulnerable server send requests to internal or external systems, or to the server itself. The attack works when a server has functionality that fetches a URL supplied or modified by the user — such as importing from a URL, downloading an avatar image, or sending a webhook. By carefully choosing that URL, the attacker can make the server read resources that are not exposed to the public internet.</p>
<p>SSRF is one of the most serious web vulnerabilities. OWASP lists it among the most critical API security risks, and it is recognized as one of the most serious software weaknesses. In severe cases an SSRF can compromise a cloud environment, exploit internal hosts, leak sensitive information, or turn the server into a proxy for further attacks.</p>
<p>This guide explains how SSRF attacks work, what an attacker can reach, and the defenses that reliably prevent them.</p></section>
<section><h2 id="how-ssrf-works"><a href="#how-ssrf-works" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How SSRF works</h2><p>The vulnerable application exposes a feature that reads data from a URL — for example a URL the user submits and the code on the server fetches. Instead of a public website, the attacker supplies a URL pointing at internal resources. The server-side code then picks up the crafted URL and tries to read from it.</p>
<p>Common examples of features attackers abuse:</p>
<ul>
<li><strong>Image download</strong> — a user enters the URL of an avatar image and the application downloads and uses it.</li>
<li><strong>Custom webhooks</strong> — a user specifies a webhook handler or callback URL that the application calls.</li>
<li><strong>PDF or document generators</strong> — a feature that renders a page from a user-supplied URL.</li>
<li><strong>URL importers</strong> — tools that fetch a link&#39;s content to embed or preview it.</li>
</ul>
<p>An attacker chooses target URLs such as:</p>
<pre><code>http://169.254.169.254/        AWS, GCP and Azure instance metadata service
http://localhost/              services running on the server itself
http://10.0.0.1/               RFC 1918 private network
file:///etc/passwd             read local files if the fetcher supports file://
</code></pre>
<p>Because the request originates from the server, it carries the server&#39;s network privileges. The server can reach internal services — databases, administration interfaces, or other backends — that are blocked from the public internet. The security boundary that would normally protect these resources no longer protects them, because the trusted server is the one making the request.</p>
</section>
<section><h2 id="types-of-ssrf"><a href="#types-of-ssrf" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Types of SSRF</h2><p>SSRF is categorized by whether the attacker can observe the response:</p>
<p><strong>Basic (non-blind) SSRF</strong> — the response is returned to the attacker. The server fetches the URL the attacker requested and sends the result back. This is the easiest form to exploit: the attacker directly reads the data from an internal service.</p>
<p><strong>Blind SSRF</strong> — the response is not sent back to the attacker. The request still triggers actions on the target, but the attacker cannot directly read the output. In this case the attacker confirms the vulnerability indirectly, for example by observing timing differences, sending a request to a server they control, or triggering a side effect on the target.</p>
</section>
<section><h2 id="what-an-attacker-can-reach-with-ssrf"><a href="#what-an-attacker-can-reach-with-ssrf" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What an attacker can reach with SSRF</h2><p>The impact depends on what the server can access and whether any response is observable:</p>
<ul>
<li><strong>Cloud instance metadata</strong> — the link-local address <code>169.254.169.254</code> is used by AWS, Google Cloud and Azure to expose instance metadata. In older AWS setups this included IAM temporary credentials. An SSRF that reaches this endpoint can leak credentials and compromise the cloud account.</li>
<li><strong>Internal services</strong> — a server often can connect to databases, admin panels, monitoring tools, or microservices on the private network. SSRF turns these reachable.</li>
<li><strong>The server itself</strong> — <code>localhost</code> services that run on the same machine become reachable through the server&#39;s own network stack.</li>
<li><strong>Local files</strong> — if the fetcher supports non-HTTP schemes such as <code>file://</code>, the attacker can read files on the server&#39;s filesystem (for example <code>/etc/passwd</code>).</li>
<li><strong>Server as a proxy</strong> — because requests come from the vulnerable server, SSRF can scan the internal network and conceal further attacks behind the compromised server&#39;s identity.</li>
</ul>
<p>SSRF is not limited to HTTP. When the application performs the follow-up request, it may use other protocols and schemes such as <code>ftp://</code>, <code>smb://</code>, <code>gopher://</code>, <code>data://</code>, or <code>dict://</code>. In addition, an XML External Entity (XXE) vulnerability in a parser can be chained to perform SSRF by making the parser fetch an external address.</p>
</section>
<section><h2 id="defenses-that-prevent-ssrf"><a href="#defenses-that-prevent-ssrf" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Defenses that prevent SSRF</h2><p>OWASP recommends different defenses depending on whether the application only needs to reach a fixed set of trusted services, or must fetch arbitrary external URLs.</p>
<h3 id="case-1-the-application-only-talks-to-identified-trusted-services">Case 1 — The application only talks to identified, trusted services</h3><p>When the application&#39;s calls are limited to a well-known set of internal services, use an <strong>allowlist</strong>. The allowlist approach is stronger than a blocklist because it rejects everything by default and only permits known-good destinations.</p>
<ul>
<li><strong>Validate the input</strong> — ensure the supplied value matches the expected format. If the input is a domain name or IP address, validate it is a valid IPv4 or IPv6 address before comparing it against the allowlist. Use a battle-tested library rather than hand-rolled parsing, because IP addresses can be encoded in many ways (hex, octal, dotted decimal, decimal).</li>
<li><strong>Compare the validated output, not the raw input</strong> — convert the input to its canonical IP form, then check that against the allowlist with strict, case-sensitive comparison. This closes bypasses that encode the address differently.</li>
<li><strong>Disable redirects</strong> in the HTTP client. A validating proxy can follow a redirect to an internal address, bypassing the check that passed on the original URL.</li>
</ul>
<h3 id="case-2-the-application-must-fetch-arbitrary-external-urls">Case 2 — The application must fetch arbitrary external URLs</h3><p>When a blocklist is the only option, block the destinations that are most dangerous:</p>
<table>
<thead>
<tr>
<th>Service</th>
<th>Addresses to block</th>
</tr>
</thead>
<tbody><tr>
<td>AWS IMDS</td>
<td><code>169.254.169.254</code>, <code>metadata.amazonaws.com</code></td>
</tr>
<tr>
<td>GCP Metadata</td>
<td><code>metadata.google.internal</code>, <code>169.254.169.254</code></td>
</tr>
<tr>
<td>Azure IMDS</td>
<td><code>169.254.169.254</code></td>
</tr>
<tr>
<td>Localhost</td>
<td><code>127.0.0.0/8</code>, <code>0.0.0.0/8</code>, <code>::1/128</code></td>
</tr>
<tr>
<td>RFC 1918 private</td>
<td><code>10.0.0.0/8</code>, <code>172.16.0.0/12</code>, <code>192.168.0.0/16</code></td>
</tr>
<tr>
<td>Multicast</td>
<td><code>224.0.0.0/4</code>, <code>ff00::/8</code></td>
</tr>
</tbody></table>
<p>These follow the IANA special-purpose registries. Because DNS can resolve a hostname to a blocked address, apply the blocklist to the <em>resolved IP</em>, not just the submitted hostname. Address canonicalization and network segregation add further defense in depth.</p>
<h3 id="network-layer-defenses">Network-layer defenses</h3><p>When the server must reach arbitrary external hosts, <strong>network segregation</strong> prevents SSRF from reaching internal systems. Place the outward-facing application in its own network segment, and use firewall rules to block traffic to internal RFC 1918 ranges, link-local metadata addresses, and localhost. This way, even if the application-level validation is bypassed, there is no route from the application to the internal resources it should never reach.</p>
<p>Other hardening measures:</p>
<ul>
<li><strong>Restrict non-HTTP schemes</strong> — if the application only needs HTTP(S), reject <code>file://</code>, <code>gopher://</code>, <code>ftp://</code> and other schemes.</li>
<li><strong>Pin DNS resolution</strong> — resolve the hostname once server-side and only allow the connection to that resolved address, mitigating DNS rebinding where a hostname alternates between a public and a private address.</li>
<li><strong>Static analysis</strong> — SAST tooling can flag code paths where a URL built partly from user input reaches an outbound request, helping catch SSRF before it ships.</li>
</ul>
</section>
<section><h2 id="ssrf-vs-csrf"><a href="#ssrf-vs-csrf" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>SSRF vs CSRF</h2><p>SSRF is often confused with Cross-Site Request Forgery (CSRF), but they are different. CSRF uses a <em>web client</em> — a victim&#39;s browser — as the proxy to perform unwanted actions on a site the victim is logged into. SSRF instead uses a <em>vulnerable server</em> as the proxy to make requests the attacker controls, reaching internal systems the attacker could not contact directly. Both are abusing a trusted component to perform unauthorized requests, but on opposite sides of the request path.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-is-the-difference-between-ssrf-and-csrf">What is the difference between SSRF and CSRF?</h3><p>CSRF tricks a user&#39;s browser into sending an unwanted request to a site where the user is logged in. SSRF makes a vulnerable server send a request to an internal or external target of the attacker&#39;s choosing. CSRF abuses the web client as a proxy; SSRF abuses the server as a proxy and reaches resources the attacker cannot normally contact.</p>
<h3 id="why-is-the-cloud-metadata-address-169-254-169-254-important-for-ssrf">Why is the cloud metadata address 169.254.169.254 important for SSRF?</h3><p>The link-local address <code>169.254.169.254</code> is the instance metadata service for AWS, Google Cloud and Azure. On a server in the cloud, an SSRF that can reach this address may read instance metadata, which can include IAM temporary credentials. This is why cloud providers&#39; metadata addresses are prime SSRF targets and are specifically blocked by SSRF defenses.</p>
<h3 id="what-is-blind-ssrf">What is blind SSRF?</h3><p>In blind SSRF the response from the target is not returned to the attacker, so they cannot directly read the output. The attacker confirms the vulnerability indirectly — via timing differences, a callback to a server they control, or a side effect triggered on the target — and exploits it based on that evidence.</p>
<h3 id="can-ssrf-be-used-to-read-local-files">Can SSRF be used to read local files?</h3><p>Yes, if the URL-fetching feature supports non-HTTP schemes. A fetcher that accepts <code>file://</code> can be made to read local files on the server, such as <code>/etc/passwd</code>. SSRF is not limited to HTTP — schemes like <code>ftp://</code>, <code>gopher://</code>, <code>data://</code> and <code>dict://</code> can also be abused, and an XXE vulnerability can be chained to perform SSRF.</p>
</section>
<section><h2 id="summary"><a href="#summary" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Summary</h2><p>SSRF lets an attacker control a request that a vulnerable server makes, turning the server into a proxy to internal systems, the local host, or cloud metadata. The impact ranges from reading a local file to compromising a whole cloud account.</p>
<p>The strongest defense is an allowlist when the application only needs to reach trusted services — validating input and comparing the canonical resolved address. When the application must fetch arbitrary URLs, block dangerous destinations such as metadata services, localhost and private ranges, restrict non-HTTP schemes, and use network segregation so internal systems are unreachable even if application checks are bypassed.</p>
</section>
<section><h2 id="sources"><a href="#sources" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Sources</h2><ul>
<li><a href="https://owasp.org/www-community/attacks/Server_Side_Request_Forgery">OWASP — Server Side Request Forgery</a> (CC BY-SA 4.0)</li>
<li><a href="https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html">OWASP — Server-Side Request Forgery Prevention Cheat Sheet</a> (CC BY-SA 4.0)</li>
<li><a href="https://en.wikipedia.org/wiki/Server-side_request_forgery">Wikipedia — Server-side request forgery</a> (CC BY-SA 4.0)</li>
</ul>
</section>]]></content:encoded>
    </item>
    <item>
      <title>What is SQL injection? How it works and how to prevent it</title>
      <link>https://debod.id/en/posts/sql-injection-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/sql-injection-explained/</guid>
      <pubDate>Tue, 01 Sep 2026 00:00:00 GMT</pubDate>
      <category>security</category>
      <category>security</category>
      <category>database</category>
      <category>web</category>
      <description>SQL injection is one of the most common web vulnerabilities — this guide explains how it works, the main attack types, and the secure coding practices that prevent it.</description>
      <content:encoded><![CDATA[<section><p>SQL injection is a code injection technique used to attack data-driven applications. It works by inserting malicious SQL statements into an entry field and having the database execute them as if they were part of the application&#39;s legitimate query. It is one of the oldest and most common web security vulnerabilities, and it can lead to stolen data, tampered records, and even full server compromise.</p></section>
<section><h2 id="how-sql-injection-works"><a href="#how-sql-injection-works" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How SQL injection works</h2><p>SQL injection happens when attacker-supplied data becomes SQL code. This occurs when a program builds a SQL query by string interpolation or by concatenating user-supplied data directly into the query string, instead of keeping data separate from the SQL instructions.</p>
<p>Consider a login form that builds a query like this:</p>
<pre><code>SELECT * FROM users WHERE name = &#39;&lt;user input&gt;&#39; AND password = &#39;&lt;user input&gt;&#39;
</code></pre>
<p>If the application inserts the username directly into the string, then entering the username <code>name&#39; OR &#39;a&#39;=&#39;a</code> turns the query into:</p>
<pre><code>SELECT * FROM users WHERE name = &#39;name&#39; OR &#39;a&#39;=&#39;a&#39; AND password = &#39;&lt;user input&gt;&#39;
</code></pre>
<p>Because <code>OR &#39;a&#39;=&#39;a&#39;</code> always evaluates to true, the <code>WHERE</code> clause matches every row. The attacker logs in as the first user in the table — which is often the administrator. The root problem is that SQL makes no real distinction between the control plane (the query structure) and the data plane (the input values), so input can alter the structure of the query itself.</p>
</section>
<section><h2 id="common-attack-types"><a href="#common-attack-types" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Common attack types</h2><h3 id="classic-in-band-injection">Classic (in-band) injection</h3><p>The attacker injects SQL directly through an input field or URL parameter, and the database response appears directly in the application. This is the simplest form and often the first thing an attacker tries.</p>
<h3 id="blind-sql-injection">Blind SQL injection</h3><p>The application does not return the database&#39;s error messages or query results directly, so the attacker cannot read the output. Instead, the attacker sends queries that produce a <em>true</em> or <em>false</em> result and observes how the page behaves in response. For example, sending <code>1=1</code> and <code>1=2</code> in a URL parameter and watching for a different page can reveal whether the site is vulnerable.</p>
<h3 id="second-order-sql-injection">Second-order SQL injection</h3><p>An application guards its SQL against immediate user input, but stores the data and later builds another query from the stored value without the same care. The first request is safe, but the stored data becomes dangerous when it is reused later.</p>
<h3 id="union-based-injection">UNION-based injection</h3><p>The attacker uses the <code>UNION</code> SQL operator to append an extra <code>SELECT</code> to the original query, retrieving data from other tables — such as usernames, password hashes, or credit card numbers.</p>
</section>
<section><h2 id="potential-damage"><a href="#potential-damage" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Potential damage</h2><p>A successful SQL injection exploit can:</p>
<ul>
<li>Read sensitive data from the database, such as personal information, credentials, or payment details.</li>
<li>Modify or delete data (Insert, Update, Delete), tampering with records or destroying data.</li>
<li>Execute administrative operations on the database, such as shutting it down.</li>
<li>Recover the contents of files present on the database server&#39;s filesystem.</li>
<li>In some cases, issue commands to the operating system.</li>
</ul>
<p>Shortly put, SQL injection can let an attacker spoof identity, tamper with existing data, void transactions, change balances, disclose all data on the system, or become an administrator of the database server.</p>
</section>
<section><h2 id="how-to-prevent-sql-injection"><a href="#how-to-prevent-sql-injection" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How to prevent SQL injection</h2><p>The reliable defenses follow a defense-in-depth approach. OWASP consistently warns against relying on string escaping or denylists as a primary defense because they are &quot;frail compared to other defenses&quot; and full of loopholes.</p>
<h3 id="1-parameterized-queries-prepared-statements">1. Parameterized queries (prepared statements)</h3><p>This is the primary defense. Placeholders (also called bind variables) hold only values of a defined type, preventing input from altering the query structure. The application code stays separate from the data, and each parameter is passed as a distinct value. Most development platforms and database drivers support this directly — for example, Java&#39;s <code>PreparedStatement</code>, PHP&#39;s PDO, and the <code>.NET</code> <code>SqlCommand</code> with parameters.</p>
<pre><code>SELECT * FROM users WHERE name = ? AND password = ?
</code></pre>
<p>The <code>?</code> marks are placeholders. Even if an attacker types SQL into the <code>name</code> field, it is treated as a value, not as part of the query.</p>
<h3 id="2-allow-list-input-validation">2. Allow-list input validation</h3><p>Validate that input matches an expected pattern (an allow list) so that only explicitly allowed values are accepted. For identifiers, user names, sort keys, and other structured inputs, this blocks unexpected characters before they reach a query. Input validation alone does not prevent SQL injection, but it is an effective additional safeguard.</p>
<h3 id="3-least-privilege">3. Least privilege</h3><p>Give database accounts only the minimum permissions they need. A read-only account should not be able to modify data, and application accounts should never have administrative access. Limiting access to system tables and using separate accounts for different functions — such as authentication versus data modification — reduces the damage a single successful injection can cause.</p>
<h3 id="4-object-relational-mappers-orms">4. Object-relational mappers (ORMs)</h3><p>ORMs typically offer built-in protections against SQL injection because they parameterize queries for you. However, they are not a guarantee: raw SQL executed through an ORM can still be vulnerable if built by concatenating input. Treat ORM raw-query paths with the same care as hand-written SQL.</p>
</section>
<section><h2 id="what-does-not-reliably-work"><a href="#what-does-not-reliably-work" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What does not reliably work</h2><ul>
<li><strong>String escaping</strong> — manually escaping special characters in input is widely discouraged. Not every piece of data can be safely added to SQL as a quoted string (for example, <code>LIMIT</code> clause arguments or table and column names), so escaping leaves other injection paths open.</li>
<li><strong>Denylists / blacklists</strong> — blocking a list of known-bad characters or keywords is riddled with loopholes and can be bypassed with different encodings.</li>
</ul>
</section>
<section><h2 id="security-in-a-larger-picture"><a href="#security-in-a-larger-picture" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Security in a larger picture</h2><p>SQL injection is one of many threats that fall under the general category of injection attacks. A layered approach — parameterized queries, strict input validation, least-privilege database accounts, and regular security testing — is what hardens an application in practice. For related concepts, see our guides on <a href="/en/posts/http-status-codes/">HTTP status codes</a> and the <a href="/en/posts/http-methods-explained/">HTTP method basics</a>.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-is-sql-injection">What is SQL injection?</h3><p>SQL injection is a code injection attack in which malicious SQL statements are inserted into an entry field so that a database executes them as part of a legitimate query. It happens when an application builds SQL by concatenating user input into the query string instead of keeping data separate from the SQL instructions.</p>
<h3 id="can-parameterized-queries-always-prevent-sql-injection">Can parameterized queries always prevent SQL injection?</h3><p>For the data values in a query, yes — parameterized queries keep input from altering the query structure, which is the primary defense. But they do not cover every use of SQL: database identifiers (table names, column names) and <code>LIMIT</code> clause arguments cannot always be parameterized, so those paths still need allow-list validation and careful handling.</p>
<h3 id="what-is-blind-sql-injection">What is blind SQL injection?</h3><p>Blind SQL injection occurs when an application does not return query results or database errors directly. The attacker sends queries that evaluate to true or false and observes how the page responds, inferring information one bit at a time. It is slower than classic injection but equally exploitable.</p>
<h3 id="how-common-is-sql-injection-in-2026">How common is SQL injection in 2026?</h3><p>SQL injection remains a consistently top-ranked vulnerability in web application security reports (OWASP and industry sources list injection as a long-standing high-impact class). It persists because it is easy to detect and easy to exploit, and any site with a database and dynamic queries can be a target regardless of size.</p>
</section>
<section><h2 id="summary"><a href="#summary" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Summary</h2><p>SQL injection is a code injection attack that occurs when user-supplied data is concatenated into a SQL query and executed as if it were part of the query structure. It can read, modify, or delete database data and, in severe cases, compromise the database server or operating system. The reliable defenses are parameterized queries, allow-list input validation, and least-privilege database accounts — applied together rather than relying on escaping or denylists. Because the flaw is easy to detect and exploit, any application that builds SQL queries with user input should treat prevention as mandatory, not optional.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>What is Server-Side Rendering (SSR)? How it works and when to use it</title>
      <link>https://debod.id/en/posts/server-side-rendering-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/server-side-rendering-explained/</guid>
      <pubDate>Tue, 01 Sep 2026 00:00:00 GMT</pubDate>
      <category>web</category>
      <category>web</category>
      <category>performance</category>
      <category>javascript</category>
      <description>Server-side rendering generates the full HTML for a page on the server in response to a request — delivering a complete, crawlable page to the browser. Here&apos;s how SSR works, how it compares to client-side rendering, and when it makes sense for your project.</description>
      <content:encoded><![CDATA[<section><p>When you type a URL into a browser and hit Enter, something has to build the page you see. That &quot;something&quot; can happen in two main places: on a server before the page is sent, or in your browser after it arrives. Server-side rendering is the first approach — it is the practice of generating HTML content on the server and sending the fully built page to the client.</p></section>
<section><h2 id="how-server-side-rendering-works"><a href="#how-server-side-rendering-works" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How server-side rendering works</h2><p>Here is what happens step by step when a server renders a page:</p>
<ol>
<li>A user types a URL or clicks a link, and the browser sends a request to the server.</li>
<li>The server processes the request — identifying which page is needed and running any backend logic.</li>
<li>The server fetches the data required for that page — pulling from a database, an API, or other sources.</li>
<li>The server inserts that data into an HTML template, building the complete HTML document.</li>
<li>The server sends the finished HTML to the browser as an HTTP response.</li>
<li>The browser receives the HTML and renders it immediately, showing content without waiting for JavaScript to load.</li>
</ol>
<p>This is in contrast to client-side rendering, where the server sends a near-empty HTML shell and the browser must download, parse, and execute JavaScript before any content appears.</p>
</section>
<section><h2 id="server-side-rendering-vs-client-side-rendering-vs-static-rendering"><a href="#server-side-rendering-vs-client-side-rendering-vs-static-rendering" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Server-side rendering vs. client-side rendering vs. static rendering</h2><p>Understanding SSR means understanding the alternatives:</p>
<h3 id="client-side-rendering-csr">Client-side rendering (CSR)</h3><p>With client-side rendering, the server sends minimal HTML (usually just an empty <code>&lt;div&gt;</code> and a JavaScript bundle). The browser downloads the JavaScript, runs it, and uses it to build the page content inside the DOM. The browser has to do all the work of fetching data and rendering after the initial JavaScript download is complete.</p>
<p>This approach enables rich, interactive single-page applications (SPAs). But it creates a <strong>waterfall of requests</strong>: the browser renders the initial HTML, then discovers it needs data, then fetches the data, then renders it. Users see a blank or loading screen during this process.</p>
<h3 id="static-site-generation-ssg">Static site generation (SSG)</h3><p>Static rendering happens at build time, not at request time. The server (or build tool) pre-generates HTML files for every URL ahead of time, and those files are served directly from a CDN on each request. There is no server-side computation per request, so TTFB is consistently fast.</p>
<p>SSG works best when the content does not change frequently — blogs, documentation, marketing sites. For sites with millions of dynamic pages or frequently changing content, pre-generating every possible page is infeasible.</p>
<h3 id="ssr-in-context">SSR in context</h3><p>SSR fills the gap between static sites and fully client-rendered apps. It generates HTML on demand per request, so it can serve fresh, personalized, or dynamic content while still delivering a complete HTML page to the browser. The trade-off is that every request requires server-side computation, which adds latency compared to serving a pre-built static file.</p>
<p>Modern frameworks handle SSR in different ways. React supports SSR via <code>renderToString()</code> or the newer streaming server DOM APIs (<code>renderToPipeableStream</code>, <code>renderToReadableStream</code>). Vue has a server-side rendering guide and the Nuxt framework. Angular offers Angular Universal. Next.js is built around SSR as a core feature.</p>
</section>
<section><h2 id="benefits-of-server-side-rendering"><a href="#benefits-of-server-side-rendering" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Benefits of server-side rendering</h2><h3 id="improved-seo-and-crawlability">Improved SEO and crawlability</h3><p>Search engines can crawl and index server-rendered pages more easily. Because the full HTML is present in the initial response, crawlers do not need to execute JavaScript to read the content. Social media previews (Facebook, Twitter, LinkedIn) similarly rely on HTML being present in the initial response — they often cannot execute JavaScript at all. MDN notes that while major search engines can run JavaScript, social media crawlers typically cannot, making SSR valuable for link previews.</p>
<h3 id="faster-first-contentful-paint">Faster first contentful paint</h3><p>Because the browser receives complete HTML, it can render content immediately upon receiving the first bytes. There is no wait for a large JavaScript bundle to download, parse, and execute before content appears. This leads to a faster First Contentful Paint (FCP), which is one of Google&#39;s Core Web Vitals.</p>
<h3 id="better-performance-on-low-powered-devices">Better performance on low-powered devices</h3><p>Server-rendered pages send text and links, not JavaScript and its execution engine. For users on slow devices, old hardware, or in environments where JavaScript is unavailable or disabled, an SSR page is more accessible. MDN&#39;s SSR glossary describes this as &quot;the page is somewhat usable without JavaScript&quot; — interactive features relying on JavaScript will not work, but the content is readable.</p>
<h3 id="reduced-client-side-javascript">Reduced client-side JavaScript</h3><p>Server-side rendering lets you avoid sending large JavaScript bundles to the client. With less JavaScript to parse and execute, the main thread is less busy, which means a lower Total Blocking Time (TBT) and a lower Interaction to Next Paint (INP) — both Core Web Vitals.</p>
</section>
<section><h2 id="disadvantages-of-server-side-rendering"><a href="#disadvantages-of-server-side-rendering" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Disadvantages of server-side rendering</h2><h3 id="increased-time-to-first-byte">Increased time to first byte</h3><p>Generating HTML on the server for every request takes compute time. If the server is under load or the page is complex, TTFB can suffer compared to serving a pre-built static file. Solutions like edge caching, CDN-level caching, and streaming HTML can reduce this gap.</p>
<h3 id="higher-server-load">Higher server load</h3><p>Every page request requires server-side computation — routing, data fetching, template rendering, serialization. For high-traffic sites, this can mean significant server costs. Caching rendered HTML at the CDN or application level is the standard mitigation.</p>
<h3 id="complexity-of-caching">Complexity of caching</h3><p>Dynamic content is harder to cache effectively. A page that varies per user or changes frequently requires careful cache-invalidation logic. Getting caching &quot;right&quot; with SSR involves managing stale-while-revalidate patterns, cache tags, and CDN-level purge strategies.</p>
<h3 id="hydration-overhead">Hydration overhead</h3><p>Many SSR frameworks send a fully rendered HTML page but then also send a JavaScript bundle that &quot;takes over&quot; the existing DOM — a process called <strong>hydration</strong>. This means the same content is effectively rendered twice: once on the server and once on the client. Hydration adds JavaScript parsing and execution cost even on pages that do not need heavy interactivity. Modern approaches like React Server Components and resumability aim to reduce or eliminate this overhead.</p>
</section>
<section><h2 id="how-frameworks-implement-ssr"><a href="#how-frameworks-implement-ssr" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How frameworks implement SSR</h2><h3 id="next-js-react">Next.js (React)</h3><p>Next.js was built around SSR as a core concept. By default, pages in Next.js are server-rendered. You can opt into static generation with <code>getStaticProps</code>, or incremental static regeneration (ISR) to rebuild pages in the background on a schedule. Next.js App Router introduced React Server Components, which execute only on the server and send zero JavaScript to the client for those components.</p>
<h3 id="nuxt-vue">Nuxt (Vue)</h3><p>Nuxt is the SSR framework for Vue. It handles routing, data fetching, and rendering on the server, and it sends a Vue app to the client for hydration. Nuxt 3 added hybrid rendering, letting you mix SSR, SSG, and ISR on different routes within the same application.</p>
<h3 id="angular-universal">Angular Universal</h3><p>Angular Universal runs Angular applications on the server using Node.js or .NET backends. It renders the initial view on the server, sends it to the client, and then &quot;bootstraps&quot; the Angular app in the browser to take over.</p>
<h3 id="streaming-ssr">Streaming SSR</h3><p>Traditional SSR sends the HTML document in one piece after the entire page is ready. Streaming SSR sends the document in chunks as they become available, letting the browser start parsing and rendering before the entire response is complete. React&#39;s <code>renderToPipeableStream</code> and <code>renderToReadableStream</code> support streaming. This improves perceived performance for pages where some content (like a navigation bar or hero section) is ready before data for a slow section is fetched.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-is-the-difference-between-server-side-rendering-and-client-side-rendering">What is the difference between server-side rendering and client-side rendering?</h3><p>Server-side rendering generates the complete HTML on the server and sends it to the browser in the HTTP response. Client-side rendering sends minimal HTML and relies on JavaScript running in the browser to build the page content. SSR produces a faster first paint and better SEO; CSR allows richer client-side interactivity without page reloads. Both approaches can be used together in the same application.</p>
<h3 id="does-server-side-rendering-improve-seo">Does server-side rendering improve SEO?</h3><p>Yes. Search engines can read SSR pages without executing JavaScript, which makes indexing more reliable. Social media platforms that cannot run JavaScript also benefit from the complete HTML in the initial response. However, modern search engine crawlers (Googlebot) can execute JavaScript, so CSR pages can also be indexed — though with potential delays and more complexity.</p>
<h3 id="what-is-hydration-in-server-side-rendering">What is hydration in server-side rendering?</h3><p>Hydration is the process of attaching client-side JavaScript to a server-rendered HTML page so that the page becomes interactive. The browser renders the HTML from the server, then downloads and runs the JavaScript bundle, which attaches event listeners and initializes the application state. This &quot;re-renders&quot; the page on the client in a process called rehydration. The term rehydration emphasizes that the server-rendered content is being &quot;filled in&quot; with client-side interactivity after the initial HTML arrives.</p>
<h3 id="is-server-side-rendering-faster-than-client-side-rendering">Is server-side rendering faster than client-side rendering?</h3><p>For the initial page load, SSR is generally faster because the browser renders HTML immediately without waiting for JavaScript. For subsequent interactions after the page loads, CSR applications can feel faster because navigating between pages does not require a full server round trip. The best choice depends on your use case — content-heavy sites benefit from SSR, while highly interactive applications benefit from CSR.</p>
<h3 id="can-i-mix-ssr-and-csr-in-the-same-application">Can I mix SSR and CSR in the same application?</h3><p>Yes. Many modern frameworks support a hybrid approach. For example, Netflix server-renders its landing pages for fast initial loads, then prefetches the JavaScript for interactive pages. This gives users the best of both: fast first paint from SSR, and rich interactivity from CSR after the page loads.</p>
</section>
<section><h2 id="summary"><a href="#summary" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Summary</h2><p>Server-side rendering generates the complete HTML for a page on the server in response to each request, delivering a fully populated page to the browser. It improves SEO and crawlability, produces faster first contentful paint, and reduces the JavaScript burden on the client — but it increases server-side compute costs and TTFB compared to serving static files.</p>
<p>The choice between SSR, SSG, and CSR is not binary. Modern frameworks let you mix approaches per route, choosing SSR for pages that need fresh dynamic content, SSG for evergreen content, and CSR for highly interactive application areas. Understanding what each rendering strategy costs and delivers helps you make the right call for your specific project.</p>
<p>For content-heavy sites, blogs, e-commerce product pages, and marketing sites — where crawlability and first-paint speed matter most — SSR is a strong default. For dashboard-style applications, real-time collaborative tools, and pages with heavy user interaction, CSR or hybrid rendering may serve users better.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>REST vs gRPC: which API style should you choose?</title>
      <link>https://debod.id/en/posts/rest-vs-grpc/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/rest-vs-grpc/</guid>
      <pubDate>Tue, 01 Sep 2026 00:00:00 GMT</pubDate>
      <category>api</category>
      <category>rest</category>
      <category>api</category>
      <category>http</category>
      <category>backend</category>
      <category>web-development</category>
      <description>REST and gRPC are two ways to build APIs. REST uses HTTP and JSON with universal browser support; gRPC uses HTTP/2 and Protocol Buffers for binary efficiency and code-generated stubs. Compare the trade-offs and learn when to choose each.</description>
      <content:encoded><![CDATA[<section><p>REST and gRPC are two answers to the same question: how should a client call code running on another machine? REST wraps HTTP and calls it an API style. gRPC wraps HTTP/2 and Protocol Buffers and calls it a remote procedure call framework. Both move data between systems, but they make opposite trade-offs — and those trade-offs determine which works better for your clients, your team, and your infrastructure. This guide lays out those trade-offs clearly.</p></section>
<section><h2 id="what-rest-is"><a href="#what-rest-is" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What REST is</h2><p>REST (Representational State Transfer) is an architectural style Roy Fielding introduced in his doctoral dissertation in 2000, built around the constraints that shaped the World Wide Web. In practice, when developers say &quot;REST API,&quot; they usually mean an HTTP-based API organized around <strong>resources</strong>: each entity (users, orders, products) gets one or more URLs, and you act on it with HTTP methods.</p>
<p>REST uses HTTP/1.1 or HTTP/2 and communicates in human-readable formats — typically JSON, sometimes XML. The URL tells the server which resource you want; the HTTP method (GET, POST, PUT, DELETE) tells it what to do. Responses are plain text that any client can read with no special tooling. This simplicity is REST&#39;s deepest strength: every browser, every HTTP library, every proxy, and every debugging tool works with REST without configuration.</p>
<p>HTTP methods in REST carry well-defined semantics. GET is <strong>safe</strong> (it does not change server state) and <strong>cacheable</strong>. PUT and DELETE are <strong>idempotent</strong> (calling them twice has the same effect as calling them once). These properties let you cache REST responses at the CDN, the browser, and any intermediate proxy — which is powerful for read-heavy, public-facing APIs.</p>
</section>
<section><h2 id="what-grpc-is"><a href="#what-grpc-is" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What gRPC is</h2><p>gRPC is an RPC (Remote Procedure Call) framework Google open-sourced in August 2016, building on over a decade of internal experience with a system called Stubby that connected Google&#39;s microservices. It uses HTTP/2 as its transport and <strong>Protocol Buffers</strong> (protobuf) as its data serialization format.</p>
<p>Instead of URLs and HTTP verbs, gRPC defines a <strong>service</strong> with named <strong>remote procedures</strong>. You write a <code>.proto</code> file describing your service and the messages it sends, then run the <code>protoc</code> compiler to generate client and server stubs in your language of choice. The client calls a local-looking method, and the stub handles the network call, serialization, and deserialization automatically.</p>
<p>Protocol Buffers use a binary encoding that is smaller and faster to parse than JSON. A protobuf message encodes field numbers and wire types rather than human-readable key names, so a <code>string name = 1;</code> field takes a few bytes instead of the characters <code>&quot;name&quot;:&quot;...&quot;</code>. This matters at scale, where payload size and parsing speed directly affect latency and bandwidth costs.</p>
<p>gRPC also supports <strong>bi-directional streaming</strong> over a single TCP connection — the client, the server, or both can send a stream of messages. This enables patterns like chat, real-time dashboards, and telemetry pipelines that are awkward to express in request-response REST.</p>
</section>
<section><h2 id="the-key-differences"><a href="#the-key-differences" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The key differences</h2><table>
<thead>
<tr>
<th>Aspect</th>
<th>REST</th>
<th>gRPC</th>
</tr>
</thead>
<tbody><tr>
<td>Transport</td>
<td>HTTP/1.1 or HTTP/2</td>
<td>HTTP/2 only</td>
</tr>
<tr>
<td>Data format</td>
<td>JSON, XML (human-readable)</td>
<td>Protocol Buffers (binary)</td>
</tr>
<tr>
<td>Browser support</td>
<td>Universal — works without a proxy</td>
<td>Requires a proxy or gRPC-Web; server streaming limited</td>
</tr>
<tr>
<td>API style</td>
<td>Resource-oriented (URLs + HTTP verbs)</td>
<td>Procedure-oriented (method calls)</td>
</tr>
<tr>
<td>Code generation</td>
<td>Optional; JSON is human-readable without it</td>
<td>Core feature; <code>.proto</code> → stubs in your language</td>
</tr>
<tr>
<td>Streaming</td>
<td>One request, one response (or polling)</td>
<td>Bi-directional streaming on a single connection</td>
</tr>
<tr>
<td>Caching</td>
<td>Excellent — HTTP caching works at every layer</td>
<td>Limited; no URL-based cache keys</td>
</tr>
<tr>
<td>Human-readable</td>
<td>Yes (JSON)</td>
<td>No — need a decoder to read payloads</td>
</tr>
<tr>
<td>Tooling</td>
<td>Mature: Postman, Swagger, Insomnia</td>
<td>Growing: BloomRPC, gRPCurl, Postman (partial)</td>
</tr>
</tbody></table>
</section>
<section><h2 id="when-rest-is-the-better-choice"><a href="#when-rest-is-the-better-choice" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>When REST is the better choice</h2><p>REST shines where its simplicity maps cleanly to the problem:</p>
<ul>
<li><strong>Browser and mobile clients with no build step</strong> — any HTTP client can call a REST endpoint without generated stubs or a proxy. This is why REST dominates public APIs and web frontends.</li>
<li><strong>Public APIs with caching requirements</strong> — when responses are cacheable at the CDN or browser level, REST eliminates server round-trips entirely for repeated reads. This is difficult to replicate in gRPC.</li>
<li><strong>Debugging and observability</strong> — JSON logs and proxy logs are human-readable. When something goes wrong in production, a developer can copy a JSON payload, paste it into a terminal, and understand it instantly.</li>
<li><strong>Environments with limited HTTP/2 support</strong> — legacy proxies, certain corporate firewalls, and older load balancers may not handle HTTP/2 cleanly. REST over HTTP/1.1 avoids this friction.</li>
<li><strong>Simple integrations and webhooks</strong> — webhooks deliver data as an HTTP POST with a JSON body. REST is the natural fit; gRPC requires converting the payload to protobuf on both sides.</li>
</ul>
<p>REST is also the right choice when your API serves many unknown clients you do not control. The combination of universal tooling, human-readable payloads, and no code-generation dependency is a significant practical advantage.</p>
</section>
<section><h2 id="when-grpc-is-the-better-choice"><a href="#when-grpc-is-the-better-choice" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>When gRPC is the better choice</h2><p>gRPC earns its complexity in specific, well-defined situations:</p>
<ul>
<li><strong>Internal microservices with controlled clients</strong> — when you control both the server and all clients, and they share a <code>.proto</code> contract, gRPC eliminates an entire class of serialization bugs. The generated stubs ensure client and server stay in sync at compile time.</li>
<li><strong>Low-latency, high-throughput services</strong> — binary protobuf encoding is significantly smaller than JSON and faster to serialize and deserialize. HTTP/2&#39;s multiplexing lets multiple calls share a single TCP connection. Together these cut latency and increase throughput at scale.</li>
<li><strong>Bi-directional streaming</strong> — if you need the server to push data to the client after the initial request, or both sides to send a stream of messages, gRPC&#39;s native streaming is far cleaner than polling a REST endpoint or bolting on WebSockets.</li>
<li><strong>Strongly typed contracts across polyglot services</strong> — a <code>.proto</code> file generates compatible code in all supported languages (Go, Java, Python, C#, Ruby, and more). For teams with services in multiple languages, this is a meaningful productivity gain.</li>
<li><strong>Mobile clients with bandwidth constraints</strong> — protobuf payloads are smaller than equivalent JSON, which directly benefits mobile users on spotty connections.</li>
</ul>
</section>
<section><h2 id="the-honest-downsides"><a href="#the-honest-downsides" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The honest downsides</h2><p>REST&#39;s simplicity has real costs. JSON payloads are large compared to binary encoding, which matters when you are moving millions of messages per day. Nested data often requires multiple round-trips or a bespoke aggregation endpoint. Different client shapes can slowly turn a clean resource-based API into a graveyard of special-purpose endpoints.</p>
<p>gRPC&#39;s biggest practical problem is <strong>browser incompatibility</strong>. The HTTP/2 trailers that gRPC uses for metadata cannot be sent directly from a browser — you need either a proxy that translates between gRPC and HTTP/1.1, or the gRPC-Web protocol with its own proxy. This adds deployment complexity and rules out pure browser clients without significant work. gRPC is also harder to observe: payloads are binary, so a developer cannot read a request by looking at a log or a proxy dump.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="does-grpc-use-http">Does gRPC use HTTP?</h3><p>gRPC runs over HTTP/2 exclusively. It uses HTTP/2 features — multiplexing, header compression, bi-directional streams — that are not available in HTTP/1.1. REST can use HTTP/1.1 or HTTP/2, which means it is compatible with a wider range of clients, proxies, and infrastructure.</p>
<h3 id="can-grpc-work-in-a-browser">Can gRPC work in a browser?</h3><p>Not directly. A browser cannot make a raw gRPC call because the protocol requires HTTP/2 trailers that browsers do not expose. gRPC-Web provides a subset of gRPC functionality in browsers, but it requires a proxy server to translate between gRPC-Web and gRPC, and it does not support bi-directional streaming. For browser clients, REST is the practical choice.</p>
<h3 id="how-does-grpc-handle-data-serialization">How does gRPC handle data serialization?</h3><p>gRPC uses Protocol Buffers (protobuf), Google&#39;s binary serialization format. You define your data structures and service methods in a <code>.proto</code> file, then run the <code>protoc</code> compiler with a gRPC plugin to generate code in your language. The generated code handles serialization and deserialization automatically. Compared to JSON, protobuf produces smaller payloads and faster parsing at the cost of human-unreadable wire format.</p>
<h3 id="is-grpc-faster-than-rest">Is gRPC faster than REST?</h3><p>gRPC is typically faster in two specific scenarios: when payloads are large (binary encoding is smaller), and when you need streaming (gRPC supports it natively without WebSockets or polling). For small, simple payloads with no streaming, the difference is negligible, and REST&#39;s tooling advantage often outweighs any speed difference. Latency depends more on your network and server than on the protocol style.</p>
</section>
<section><h2 id="verdict"><a href="#verdict" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Verdict</h2><p>Choose REST when you need broad client support, human-readable debugging, and easy caching — this covers most public APIs, web frontends, and simple integrations. Choose gRPC when you control both sides of the connection, need low latency at scale, or require bi-directional streaming in a microservices environment. If you are building an external or browser-facing API, start with REST; adopt gRPC internally when specific performance or streaming needs justify it.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>What is a Progressive Web App (PWA)? How it works and when to use it</title>
      <link>https://debod.id/en/posts/progressive-web-app-guide/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/progressive-web-app-guide/</guid>
      <pubDate>Tue, 01 Sep 2026 00:00:00 GMT</pubDate>
      <category>web</category>
      <category>web</category>
      <category>frontend</category>
      <description>A Progressive Web App (PWA) is a website built with web platform technologies that feels like a native installed app — it can be installed on a device, work offline, and run in the background. Here&apos;s how PWAs work, what they need, and when they make sense.</description>
      <content:encoded><![CDATA[<section><p>A progressive web app (PWA) is an app built using web platform technologies that provides a user experience like that of a platform-specific app. Like a website, a PWA runs on multiple platforms and devices from a single codebase. Like a platform-specific app, it can be installed on the device, operate while offline and in the background, and integrate with the device and other installed apps. In short, a PWA is a modern website that behaves like an installed app.</p></section>
<section><h2 id="how-a-progressive-web-app-works"><a href="#how-a-progressive-web-app-works" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How a progressive web app works</h2><p>PWAs are built on the ordinary web platform — HTML, CSS, and JavaScript — plus a small set of extra browser technologies that unlock app-like behavior. The core pieces are:</p>
<h3 id="served-over-https">Served over HTTPS</h3><p>A PWA must be served over a secure HTTPS connection. Service workers, the scripts that power offline behavior, are only available on secure origins. This also protects users: an installed app is associated with a permanent, trusted identity on the web.</p>
<h3 id="the-web-app-manifest">The web app manifest</h3><p>A web app manifest is a JSON file that describes the app to the browser: its name, icons, start URL, how it should be displayed, background and theme colors, and shortcut actions. The manifest is what lets the browser add the app to the user&#39;s home screen and launch it in its own standalone window rather than a browser tab.</p>
<h3 id="the-service-worker">The service worker</h3><p>A service worker is a script that the browser runs in the background, separate from the page itself. It sits between the app and the network, intercepting network requests. This is what makes caching and offline operation possible: the service worker can store assets and responses locally and serve them when the network is unavailable, and it can perform background operations even when the main app is not running.</p>
</section>
<section><h2 id="what-makes-a-pwa-installable"><a href="#what-makes-a-pwa-installable" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What makes a PWA installable</h2><p>Installability is one of the defining aspects of a PWA. When installed, the app appears to users as a permanent feature of their device, which they can launch directly from the operating system like any other app. For the browser to offer an install experience, the web app generally needs to meet a set of installability criteria — typically a valid web app manifest describing the app and its icons, a registered service worker, and a secure HTTPS origin.</p>
<p>Because a PWA is a website at its core, installation does not go through an app store. The same codebase installs across platforms and devices, and users can add it to their home screen with a single action. When the device and the operating system support it, an installed PWA can go further: launch in a standalone window, display a badge on its icon, expose shortcut actions, and share data with other installed apps.</p>
</section>
<section><h2 id="offline-and-background-operation"><a href="#offline-and-background-operation" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Offline and background operation</h2><p>A defining strength of PWAs is that they keep working when the network is intermittent or absent. The service worker caches resources and data locally so the app can serve cached content without a connection. When connectivity returns, the app can sync data and fetch fresh content. PWAs can also perform operations in the background — such as receiving push notifications — even when the main app is closed.</p>
<p>This reliable offline behavior is what web.dev highlights as &quot;reliability&quot;: PWAs use modern APIs to deliver enhanced capabilities, reliability, and installability while reaching anyone, anywhere, on any device, all with a single codebase.</p>
</section>
<section><h2 id="benefits-of-a-progressive-web-app"><a href="#benefits-of-a-progressive-web-app" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Benefits of a progressive web app</h2><h3 id="single-codebase-across-platforms">Single codebase across platforms</h3><p>Because a PWA is a website, one codebase runs on Android, iOS, desktop, and the web. There is no separate iOS and Android codebase to maintain, and no app-store review to ship an update — you deploy to the web and the change reaches every installed instance.</p>
<h3 id="no-app-store-requirement">No app store requirement</h3><p>PWAs install directly from the browser. Users avoid app-store sign-ups, and developers avoid app-store review processes, fees, and gatekeeping. Updates ship instantly on the web.</p>
<h3 id="works-offline-and-is-reliable">Works offline and is reliable</h3><p>The service worker caches the app shell and data, so users get content and functionality even with no signal. This matters for travel, commuters, low-bandwidth regions, and any use case where connectivity is unreliable.</p>
<h3 id="engagement-features">Engagement features</h3><p>Installed PWAs can send push notifications and display badges, helping re-engage users the way platform-specific apps do. Because they launch from the home screen like native apps, they stay top-of-mind rather than being lost among browser tabs.</p>
</section>
<section><h2 id="when-to-use-a-progressive-web-app"><a href="#when-to-use-a-progressive-web-app" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>When to use a progressive web app</h2><p>PWAs are a strong fit when you want the reach and low friction of the web together with app-like engagement — for example content apps, e-commerce, news, tools, and utility apps where offline access and installability add real value.</p>
<p>PWAs are a weaker fit when you need deep hardware integration, heavy graphics or AR that outpaces web platform capabilities, or full background processing that the web does not yet expose at native speed. In those cases a platform-specific app may still be the better choice, and many teams ship both.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-is-the-difference-between-a-pwa-and-a-regular-website">What is the difference between a PWA and a regular website?</h3><p>A PWA is a regular website built with the same web platform technologies, but it adds three extra layers: a web app manifest that describes how the app looks when installed, a service worker that enables offline caching and background behavior, and a secure HTTPS connection required to make those work. A regular website runs only in a browser tab; a PWA can be installed, launch from the home screen, and work offline.</p>
<h3 id="can-a-pwa-work-without-internet">Can a PWA work without internet?</h3><p>Yes. The service worker caches the app&#39;s assets and data, so a PWA can serve cached content when the network is offline or unreliable, and can sync fresh data when connectivity returns. This is one of the core differences between a PWA and a traditional website, which usually needs a live connection to load.</p>
<h3 id="do-i-need-an-app-store-to-install-a-pwa">Do I need an app store to install a PWA?</h3><p>No. PWAs install directly from the browser onto the device — there is no app store, review process, or fee. When the device and operating system support it, the browser offers an install option, and the app appears on the home screen like a platform-specific app.</p>
<h3 id="are-pwas-supported-on-both-android-and-ios">Are PWAs supported on both Android and iOS?</h3><p>Yes. The core of a PWA is a website, so one codebase runs on any browser across platforms. However, feature support varies: some capabilities, such as the full set of install and background features, are more mature in Chromium-based browsers and on Android than they are in Safari on iOS, where some PWA features are limited. A good PWA works everywhere but progressively enables more capability where the platform allows.</p>
</section>
<section><h2 id="summary"><a href="#summary" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Summary</h2><p>A progressive web app is a website enhanced with web platform technologies — HTTPS, a web app manifest, and a service worker — to deliver the experience of a platform-specific app. It can be installed on a device, launch from the home screen, work offline, and run in the background, all from a single codebase that reaches every platform without an app store.</p>
<p>PWAs shine where the reach and low friction of the web meet the engagement and reliability users expect from installed apps — content, commerce, news, and utility apps. They are the right choice when installability, offline access, and push re-engagement add real value, and when you want to ship one codebase instead of maintaining separate native apps.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>What is Object-Oriented Programming (OOP)? A complete guide</title>
      <link>https://debod.id/en/posts/object-oriented-programming-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/object-oriented-programming-explained/</guid>
      <pubDate>Tue, 01 Sep 2026 00:00:00 GMT</pubDate>
      <category>programming</category>
      <category>programming</category>
      <category>javascript</category>
      <description>Object-oriented programming is a paradigm based on objects that combine data and behavior. Learn the four pillars — encapsulation, inheritance, polymorphism, and abstraction — with practical examples and how they work in JavaScript, Java, and Python.</description>
      <content:encoded><![CDATA[<section><p>Object-oriented programming (OOP) is a programming paradigm based on <strong>objects</strong> — software entities that combine data and the functions that operate on that data. Instead of structuring a program as a list of instructions and separate data (the procedural style), OOP models a program as a collection of objects that interact with one another. Each object encapsulates state (its data) and behavior (its methods), and external code communicates with it through a public interface.</p>
<p>The short version: an OOP program is made of objects; an object bundles data and behavior; classes and prototypes provide blueprints for objects; and four core ideas — encapsulation, inheritance, polymorphism, and abstraction — give the paradigm its power. It is the dominant model in languages like Java, C++, C#, Python, and JavaScript, and it underpins most large software systems you use today.</p></section>
<section><h2 id="where-oop-came-from"><a href="#where-oop-came-from" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Where OOP came from</h2><p>The idea of &quot;objects&quot; in programming has deep roots. At MIT in the late 1950s and early 1960s, researchers used &quot;object&quot; to refer to LISP atoms with identified properties. Ivan Sutherland&#39;s Sketchpad (1960–1961) defined terms like &quot;object&quot; and &quot;instance&quot; in the context of graphical interaction. Meanwhile, in Norway, the Simula language was developed during 1961–1967 and introduced the essential object-oriented ideas: classes, inheritance, and dynamic binding. Simula is generally accepted as the first language with the primary features of an object-oriented language; it was used mainly for physical modeling, such as simulating the movement of ships through cargo ports.</p>
<p>Alan Kay, influenced by both MIT and Simula, began developing his own ideas in 1966 and went on to create Smalltalk at Xerox PARC in the 1970s — a language that treated objects as the fundamental building blocks of a dynamic system. Kay described objects as being &quot;like biological cells and/or individual computers on a network, only able to communicate with messages.&quot; OOP rose to prominence in the late 1970s and 1980s, and by the 1990s it had become the main way of programming, especially as graphical user interfaces — which use objects for buttons, menus, and other elements — became widespread.</p>
</section>
<section><h2 id="objects-classes-and-instances"><a href="#objects-classes-and-instances" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Objects, classes, and instances</h2><p>The central unit of OOP is the <strong>object</strong>. An object combines:</p>
<ul>
<li><strong>State</strong> — the data it holds, stored in fields (also called attributes or properties), often as variables.</li>
<li><strong>Behavior</strong> — the functions (called methods) that define what the object can do.</li>
</ul>
<p>In <strong>class-based</strong> programming, the most common form of OOP, an object is an <strong>instance</strong> of a <strong>class</strong>. The class is the blueprint: it defines the variables and methods that every instance will have. An object is created through the class&#39;s <strong>constructor</strong>. Every instance of a class has the same set of variables and methods, but each instance holds its own values for the instance variables.</p>
<p>In JavaScript, you declare a class with the <code>class</code> keyword and create instances with <code>new</code>:</p>
<pre><code class="language-js">class Person {
  name;

  constructor(name) {
    this.name = name;
  }

  introduceSelf() {
    console.log(`Hi! I&#39;m ${this.name}`);
  }
}

const giles = new Person(&quot;Giles&quot;);
giles.introduceSelf(); // Hi! I&#39;m Giles
</code></pre>
<p>Some languages — most notably JavaScript — also support <strong>prototype-based</strong> programming, where instead of a class concept, an object is linked to another object called its <strong>prototype</strong>. A prototype acts as a model for new objects: create one <code>fruit</code> object, then make <code>apple</code> and <code>orange</code> that share <code>fruit</code>&#39;s traits. In JavaScript each object has exactly one prototype link, up to a base object whose prototype is null. JavaScript&#39;s <code>class</code> syntax is a convenience layer that still works through prototypes under the hood.</p>
</section>
<section><h2 id="encapsulation-and-information-hiding"><a href="#encapsulation-and-information-hiding" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Encapsulation and information hiding</h2><p><strong>Encapsulation</strong> is the practice of bundling related data and methods together in an object, and <strong>information hiding</strong> is the related idea of keeping an object&#39;s internal details hidden from outside code. They work together through several mechanisms:</p>
<ul>
<li><strong>Cohesion</strong> — keeping related fields and methods together in one place.</li>
<li><strong>Decoupling</strong> — organizing code so that only certain parts of the data are used by related functions, making it easier to change how an object works internally without affecting other parts of the codebase.</li>
<li><strong>Data hiding</strong> — consuming code can only interact with an object through its public members, controlled by the language&#39;s access modifiers.</li>
</ul>
<p>Languages differ in how they expose visibility. Java provides <code>private</code> and <code>public</code> keywords; <code>protected</code> allows access from the same class and its subclasses. Python has no visibility keyword, but developers follow a convention such as starting a &quot;private&quot; member name with an underscore. In JavaScript, you can declare <strong>private fields</strong> whose names start with <code>#</code> — code outside the object that tries to access them throws an error.</p>
<pre><code class="language-js">class Student extends Person {
  #year; // private field — inaccessible outside the class

  constructor(name, year) {
    super(name);
    this.#year = year;
  }

  canStudyArchery() {
    return this.#year &gt; 1;
  }
}
</code></pre>
<p>Encapsulation lets you change an object&#39;s internals — the rules, the storage, the logic — without breaking the code that uses it, because that code only depends on the public interface.</p>
</section>
<section><h2 id="inheritance"><a href="#inheritance" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Inheritance</h2><p><strong>Inheritance</strong> is the mechanism of basing a class or object on another, so that a child retains the implementation of its parent. In class-based languages, a class may inherit from another class, creating a hierarchy: a subclass inherits from a super-class. For example, an <code>Employee</code> class might inherit from a <code>Person</code> class, endowing the <code>Employee</code> object with <code>Person</code>&#39;s variables. The subclass can add its own variables and methods without affecting the super-class, and most languages let it override super-class methods.</p>
<p>In JavaScript this is done with the <code>extends</code> keyword. A subclass constructor must first call the superclass constructor with <code>super()</code>:</p>
<pre><code class="language-js">class Professor extends Person {
  teaches;

  constructor(name, teaches) {
    super(name);
    this.teaches = teaches;
  }

  introduceSelf() {
    console.log(`My name is ${this.name}, and I will be your ${this.teaches} professor.`);
  }
}
</code></pre>
<p>Languages may also support <strong>multiple inheritance</strong> (inheriting from more than one class), or alternatives like <strong>mixins</strong> and <strong>traits</strong>. Some OOP languages — for example Go — deliberately do not support inheritance at all, and instead encourage <strong>composition over inheritance</strong>: build objects from smaller parts rather than parent–child relationships. An <code>Employee</code> class can simply contain a <code>Person</code> object instead of inheriting from it, which gives the class fine-grained control over how much of <code>Person</code> it exposes.</p>
<p>Opinions on inheritance vary widely. Bjarne Stroustrup, author of C++, has said it is possible to do OOP without inheritance, and Rob Pike has criticized inheritance for creating complex hierarchies instead of simpler solutions.</p>
</section>
<section><h2 id="polymorphism-and-dynamic-dispatch"><a href="#polymorphism-and-dynamic-dispatch" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Polymorphism and dynamic dispatch</h2><p><strong>Polymorphism</strong> (specifically subtype polymorphism) lets a function work with a specific interface and thus manipulate entities of different classes in a uniform way. Imagine a program with two shapes, a circle and a square, both inheriting from a common &quot;Shape&quot; class. Each shape draws itself. With subtype polymorphism, the program doesn&#39;t need to know each shape&#39;s concrete type — it simply calls the &quot;Draw&quot; method on each, and the runtime ensures the correct version runs. Because each shape&#39;s details are handled inside its own class, this yields strong separation of concerns.</p>
<p>Related to this is <strong>dynamic dispatch</strong>: a method may be selected at runtime rather than compile time. Dynamic dispatch works together with inheritance — if an object doesn&#39;t have the requested method, it looks up its parent class and continues up the chain to find a match. A method call can also be thought of as <strong>message passing</strong>: the method name and its inputs are like a message sent to the object for it to act on.</p>
</section>
<section><h2 id="abstraction-and-open-recursion"><a href="#abstraction-and-open-recursion" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Abstraction and open recursion</h2><p><strong>Abstraction</strong> is the general OOP idea of exposing only what&#39;s necessary while hiding complexity — treating an object as a black box with a known interface. Some languages support <strong>abstract classes</strong>, which cannot be directly instantiated and exist only to be subclassed.</p>
<p>Most OOP languages also provide a special value, <code>this</code> or <code>self</code>, that refers to the current object inside its methods. In languages that support <strong>open recursion</strong>, a method in an object can call other methods on the same object — including itself — using <code>this</code>. This permits a method defined in one class to invoke another method that is only defined later in a subclass, a feature known as late binding.</p>
</section>
<section><h2 id="oop-in-the-real-world"><a href="#oop-in-the-real-world" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>OOP in the real world</h2><p>Objects sometimes directly model real-world things: a graphics program has <code>circle</code>, <code>square</code>, and <code>menu</code> objects; an online shopping system has <code>shopping cart</code>, <code>customer</code>, and <code>product</code> objects. But more often, objects represent abstract entities, like an open file or a unit converter. Niklaus Wirth said that OOP &quot;closely reflects the structure of systems in the real world and is therefore well suited to model complex systems with complex behavior.&quot; Not everyone agrees — critics such as Joe Armstrong noted that OOP carries &quot;all this implicit environment,&quot; and some argue OOP doesn&#39;t always enhance readability or modularity.</p>
<p>Design patterns — common, reusable solutions to software design problems — are typically introduced in an OOP context. Common object patterns include the <strong>immutable object</strong> (which never changes state after creation), the <strong>factory object</strong> (which creates other objects), and the <strong>singleton</strong> (the only instance of its class for the program&#39;s lifetime). A well-known OOP anti-pattern is the <strong>God object</strong> — an object that knows or does too much.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-is-the-difference-between-a-class-and-an-object">What is the difference between a class and an object?</h3><p>A class is a blueprint or template that defines the variables and methods a set of objects will have. An object is a concrete instance of that class, created (usually) through a constructor. Every object of a class shares the same set of variables and methods, but each instance holds its own values for its instance variables.</p>
<h3 id="is-javascript-an-object-oriented-language">Is JavaScript an object-oriented language?</h3><p>Yes. JavaScript supports object-oriented programming — primarily <strong>prototype-based</strong> OOP, where objects are linked to a prototype that acts as a model. JavaScript also provides <code>class</code> syntax with <code>extends</code> inheritance and <code>#</code>-prefixed private fields, which map more closely to classical OOP concepts, even though they still work through prototypes under the hood.</p>
<h3 id="what-are-the-four-pillars-of-oop">What are the four pillars of OOP?</h3><p>The four core ideas are <strong>encapsulation</strong> (bundling data and methods together and hiding internals), <strong>abstraction</strong> (exposing only what&#39;s necessary), <strong>inheritance</strong> (basing a class on another to reuse its implementation), and <strong>polymorphism</strong> (manipulating objects of different classes through a common interface, backed by dynamic dispatch).</p>
<h3 id="is-composition-better-than-inheritance">Is composition better than inheritance?</h3><p>It depends on the problem. Composition means an object contains other objects (a &quot;has-a&quot; relationship) rather than inheriting from them (an &quot;is-a&quot; relationship). Languages like Go skip inheritance entirely and encourage composition over inheritance. Many developers prefer composition because it gives fine-grained control and avoids brittle, deep class hierarchies, but inheritance remains a powerful and widely used tool where a genuine parent–child relationship exists.</p>
</section>
<section><h2 id="summary"><a href="#summary" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Summary</h2><p>Object-oriented programming organizes a program around objects that combine state and behavior. Class-based languages use classes as blueprints and create instances through constructors; prototype-based languages like JavaScript link objects to a prototype. Four core ideas give OOP its shape: encapsulation hides internal details behind a public interface, inheritance reuses implementation through class hierarchies, polymorphism lets one interface work uniformly across different classes (with dynamic dispatch selecting the right method at runtime), and abstraction exposes only what&#39;s necessary. OOP underpins most modern languages and large software systems, and understanding its principles is a foundation skill for any developer.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>How to handle errors in Node.js: try/catch, error-first callbacks, promises, and event emitters</title>
      <link>https://debod.id/en/posts/nodejs-error-handling/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/nodejs-error-handling/</guid>
      <pubDate>Tue, 01 Sep 2026 00:00:00 GMT</pubDate>
      <category>nodejs</category>
      <category>nodejs</category>
      <category>express</category>
      <category>api</category>
      <category>backend</category>
      <category>async</category>
      <description>Node.js errors come in several asynchronous styles, and each has its own correct way to be caught. This guide explains try/catch for synchronous code, error-first callbacks, promise rejection handling, and the &apos;error&apos; event for streams and event emitters.</description>
      <content:encoded><![CDATA[<section><p>Node.js handles errors differently depending on the style of the API that produced them. Getting this right matters because an uncaught error in the wrong place will crash the whole process, taking down every other request being served. In this guide you will learn how to catch errors in each asynchronous style Node.js uses, so your server stays alive and your users get a clear, safe response instead of a crashed process.</p></section>
<section><h2 id="how-errors-propagate-in-node-js"><a href="#how-errors-propagate-in-node-js" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How errors propagate in Node.js</h2><p>All JavaScript errors are handled as exceptions that immediately throw using the standard <code>throw</code> mechanism, and synchronous code catches them with <code>try...catch</code>. But Node.js has few purely synchronous APIs: most of the standard library is asynchronous, and it reports failures in one of three ways depending on the API:</p>
<ul>
<li><strong>Callbacks</strong> that pass an error as the first argument (the &quot;error-first callback&quot; pattern).</li>
<li><strong>Promises</strong> that reject, which you handle with <code>catch</code>.</li>
<li><strong><code>EventEmitter</code></strong> objects that emit an <code>&#39;error&#39;</code> event, which you must listen for.</li>
</ul>
<p>Every style needs its own interception technique, because <code>try...catch</code> alone cannot catch an error thrown asynchronously after the calling code has already returned. If you ignore the way an API signals failure, the error escapes to the event loop and, with no handler, the Node.js process exits. Understanding which style an API uses is the first step to handling its errors correctly.</p>
</section>
<section><h2 id="handling-synchronous-errors-with-try-catch"><a href="#handling-synchronous-errors-with-try-catch" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Handling synchronous errors with try/catch</h2><p>For synchronous code, the <code>try...catch</code> block lets you catch an exception thrown at runtime and run recovery logic instead of letting it crash. A <code>try</code> block contains the code that may throw; the <code>catch</code> block receives the error object. Older JavaScript also supports a <code>finally</code> block, which runs whether or not an error was thrown, typically for cleanup.</p>
<pre><code class="language-js">try {
  const config = JSON.parse(fs.readFileSync(&quot;config.json&quot;, &quot;utf8&quot;));
  console.log(config.port);
} catch (err) {
  console.error(&quot;Could not read config:&quot;, err.message);
}
</code></pre>
<p>Note that only <em>synchronous</em> functions can be handled this way. If <code>fs.readFileSync</code> becomes <code>fs.readFile</code> (its asynchronous version), the error will no longer be thrown inside <code>try</code>, because the read completes later, outside the block. Trying to wrap an asynchronous call in <code>try...catch</code> is one of the most common Node.js mistakes — it looks correct but silently lets errors escape.</p>
</section>
<section><h2 id="error-first-callbacks"><a href="#error-first-callbacks" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Error-first callbacks</h2><p>The most common pattern in the Node.js core modules is the error-first callback. When an asynchronous operation finishes or fails, the callback is called with an error object as its first argument, or <code>null</code> if there is no error. Data (if any) comes as later arguments.</p>
<pre><code class="language-js">const fs = require(&quot;node:fs&quot;);

fs.readFile(&quot;notes.txt&quot;, &quot;utf8&quot;, (err, data) =&gt; {
  if (err) {
    // Handle the failure here — never throw from an async callback.
    console.error(&quot;Read failed:&quot;, err.message);
    return;
  }
  console.log(data);
});
</code></pre>
<p>The critical rule is that you must <em>check</em> <code>err</code> at the top of every callback. If the callback ignores the error argument, a failed operation silently does nothing, which can corrupt state or mislead the rest of your program. Never throw inside a callback — the thrown error is not caught by any surrounding <code>try...catch</code> (the callback runs later, on the event loop), so it becomes an uncaught exception and crashes the process.</p>
</section>
<section><h2 id="using-promises-and-async-await"><a href="#using-promises-and-async-await" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Using promises and async/await</h2><p>Modern Node.js APIs, and the <code>node:fs/promises</code> module, return promises. A rejected promise is thrown for you automatically when you <code>await</code> it, which means <code>try...catch</code> works again — as long as you <em>await</em> inside the <code>try</code> block:</p>
<pre><code class="language-js">const fs = require(&quot;node:fs/promises&quot;);

async function readConfig() {
  try {
    const config = await fs.readFile(&quot;config.json&quot;, &quot;utf8&quot;);
    return JSON.parse(config);
  } catch (err) {
    console.error(&quot;Failed to load config:&quot;, err.message);
    throw err; // re-throw so the caller sees the failure
  }
}
</code></pre>
<p>Because you <code>await</code> the promise inside <code>try</code>, a rejection is caught there. If you instead return the promise without <code>await</code>ing, an error in a later <code>catch</code> or in the caller must be handled separately. Re-throwing the error (with <code>throw err</code>) after logging it is a common pattern: it lets the immediate function log context while still propagating the failure to the caller. You can also attach a <code>catch</code> handler directly to a promise you are not <code>await</code>ing, to avoid an unhandled rejection.</p>
</section>
<section><h2 id="handling-error-events-on-event-emitters-and-streams"><a href="#handling-error-events-on-event-emitters-and-streams" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Handling &#39;error&#39; events on event emitters and streams</h2><p>Streams and event emitters such as HTTP servers, sockets, and <code>child_process</code> report errors by emitting an <code>&#39;error&#39;</code> event. If no listener is attached for <code>&#39;error&#39;</code>, Node.js throws the error, and unless something handles it as an uncaught exception, the process crashes. So every <code>EventEmitter</code> you create or consume should register an <code>&#39;error&#39;</code> handler:</p>
<pre><code class="language-js">const fs = require(&quot;node:fs&quot;);
const stream = fs.createReadStream(&quot;big.bin&quot;);

stream.on(&quot;error&quot;, (err) =&gt; {
  console.error(&quot;Stream failed:&quot;, err.message);
});
</code></pre>
<p>An <code>&#39;error&#39;</code> event cannot be caught with <code>try...catch</code>, because it is emitted after the calling code has returned. The handler must be attached before the error can occur — attach it as soon as you create the emitter, not later. For a TCP or HTTP server, an unhandled <code>&#39;error&#39;</code> event on the server object is a common cause of a sudden crash in production.</p>
</section>
<section><h2 id="global-fallbacks-uncaughtexception-and-unhandledrejection"><a href="#global-fallbacks-uncaughtexception-and-unhandledrejection" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Global fallbacks: uncaughtException and unhandledRejection</h2><p>For errors that still escape your code, Node.js provides two process-level events. <code>uncaughtException</code> fires when an exception is not caught anywhere and the runtime is about to exit; <code>unhandledRejection</code> fires when a promise is rejected with no handler. The official guidance is to use these as a last resort: after <code>uncaughtException</code> the state of the process is unreliable, because there is no guarantee the stack is intact or internal data is consistent. The recommended practice is to log the error and shut the process down cleanly, letting an external supervisor (such as a process manager) restart it, rather than trying to keep running on a suspect state.</p>
<pre><code class="language-js">process.on(&quot;uncaughtException&quot;, (err) =&gt; {
  console.error(&quot;Uncaught exception, exiting:&quot;, err);
  process.exit(1);
});
</code></pre>
<p>Logging the error and exiting gives you a chance to record what happened while avoiding undefined behavior. Relying on these events to &quot;keep the server alive&quot; is discouraged: the safer, more maintainable design is to catch errors at the point where they occur, and to let truly unexpected failures crash the process so the supervisor restarts it fresh.</p>
</section>
<section><h2 id="building-an-express-error-handling-middleware"><a href="#building-an-express-error-handling-middleware" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Building an Express error-handling middleware</h2><p>In Express, an error thrown inside a route handler is caught by Express and passed to special error-handling middleware, which is a middleware function with <strong>four</strong> parameters: <code>(err, req, res, next)</code>. This is how you return a safe JSON response to the client instead of a raw stack trace:</p>
<pre><code class="language-js">app.get(&quot;/users/:id&quot;, async (req, res, next) =&gt; {
  try {
    const user = await db.findUser(req.params.id);
    if (!user) return res.status(404).json({ error: &quot;Not found&quot; });
    res.json(user);
  } catch (err) {
    next(err); // forward to the error middleware
  }
});

// Error-handling middleware — must have 4 parameters.
app.use((err, req, res, next) =&gt; {
  console.error(err);
  res.status(500).json({ error: &quot;Internal server error&quot; });
});
</code></pre>
<p>The four-parameter signature is what tells Express this is error-handling middleware, not a normal one. <code>next(err)</code> forwards the error so it skips regular middleware and reaches your handler, where you can log it, choose a status code, and send a safe response. Never send the raw error message back to the client, because it can leak internal details such as file paths or connection strings.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-is-the-error-first-callback-pattern-in-node-js">What is the error-first callback pattern in Node.js?</h3><p>It is the convention used by most Node.js core APIs where an asynchronous callback receives an error as its first argument, or <code>null</code> if the operation succeeded. You must check this argument at the top of the callback and handle it; if you ignore it, the failure silently does nothing, and if you throw inside the callback it becomes an uncaught exception.</p>
<h3 id="why-can-t-i-catch-an-asynchronous-error-with-try-catch">Why can&#39;t I catch an asynchronous error with try/catch?</h3><p><code>try/catch</code> only catches errors thrown synchronously inside the <code>try</code> block. Asynchronous callbacks and <code>&#39;error&#39;</code> events fire later, after the calling code has already returned, so a throw there escapes the block. To catch asynchronous failures you must use promise <code>catch</code>/<code>await</code> inside <code>try</code>, check the error argument of a callback, or attach an <code>&#39;error&#39;</code> event handler.</p>
<h3 id="what-happens-if-you-do-not-listen-for-the-error-event-on-an-eventemitter">What happens if you do not listen for the &#39;error&#39; event on an EventEmitter?</h3><p>If an <code>EventEmitter</code> emits an <code>&#39;error&#39;</code> event and no listener is attached, Node.js treats it as an uncaught exception. Unless the process handles it (for example via <code>uncaughtException</code>), it terminates, which is why a missing <code>&#39;error&#39;</code> listener on a server, stream, or socket commonly causes a crash.</p>
<h3 id="is-using-uncaughtexception-to-keep-the-server-alive-a-good-idea">Is using uncaughtException to keep the server alive a good idea?</h3><p>No. Official Node.js guidance recommends treating <code>uncaughtException</code> as a last resort: after it fires there is no guarantee the process is in a consistent state. The recommended approach is to log the error and exit cleanly so an external supervisor can restart the process, rather than attempting to continue running on an unreliable state.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>GraphQL vs gRPC: which API technology should you choose?</title>
      <link>https://debod.id/en/posts/graphql-vs-grpc/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/graphql-vs-grpc/</guid>
      <pubDate>Tue, 01 Sep 2026 00:00:00 GMT</pubDate>
      <category>api</category>
      <category>graphql</category>
      <category>api</category>
      <category>http</category>
      <category>backend</category>
      <category>web-development</category>
      <description>GraphQL and gRPC are two modern ways to build APIs. GraphQL lets clients query exactly the fields they need over a single HTTP endpoint; gRPC calls typed remote methods over HTTP/2 with binary Protocol Buffers. Compare the trade-offs and learn when to choose each.</description>
      <content:encoded><![CDATA[<section><p>GraphQL and gRPC are the two most hyped alternatives to REST-style APIs, and people often ask which one to adopt. The honest answer is that they solve different problems. GraphQL is a data query language that runs over HTTP and returns JSON — it is a client-driven way to fetch and shape data. gRPC is a remote procedure call (RPC) framework that runs over HTTP/2 and sends binary Protocol Buffers — it is a high-performance, strongly typed way for services to call each other. GraphQL shines on the client-facing edge, where you do not control the consumers. gRPC shines in the service-to-service core, where you control both ends. This guide compares them across the choices that actually matter.</p></section>
<section><h2 id="what-graphql-is"><a href="#what-graphql-is" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What GraphQL is</h2><p>GraphQL is a data query and manipulation language that lets a client specify exactly what data it wants, and nothing more. Facebook started developing it in 2012 and released a draft specification and reference implementation as open source in 2015; in 2018 the project moved to the GraphQL Foundation, hosted by the non-profit Linux Foundation.</p>
<p>A GraphQL API is built around a <strong>schema</strong>: you define types and their fields, then provide <strong>resolvers</strong> — functions that fetch the data for each field. The type system is the contract. The root <code>Query</code> type names every field a client can ask for, and fields are typed, nullable by default with <code>!</code> marking a required (non-null) field.</p>
<p>Clients send a query to a single endpoint. The server validates it against the schema, executes it, and returns a result that <strong>mirrors the shape of the query</strong>, typically as JSON:</p>
<pre><code class="language-graphql">query CurrentUser {
  currentUser {
    name
    age
  }
}
</code></pre>
<pre><code class="language-json">{ &quot;currentUser&quot;: { &quot;name&quot;: &quot;John Doe&quot;, &quot;age&quot;: 23 } }
</code></pre>
<p>Because the client names the exact fields, one round-trip can fetch precisely the nested data a screen needs — no over-fetching and no under-fetching, and no need for the server to invent a bespoke endpoint for every client shape. GraphQL also supports <strong>mutations</strong> for creating, updating, and deleting data, and <strong>subscriptions</strong> for live updates pushed from the server to subscribed clients (commonly implemented over WebSockets).</p>
<p>GraphQL takes a strong opinion against versioning. New fields or types can be added without a breaking change, old ones can be marked with the <code>@deprecated</code> directive, and servers commonly stay versionless.</p>
</section>
<section><h2 id="what-grpc-is"><a href="#what-grpc-is" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What gRPC is</h2><p>gRPC is an open-source RPC framework originally developed by Google and released in August 2016. Its core idea is that a client application can call a method on a server application on another machine <strong>as if it were a local object</strong> — making distributed applications feel like normal function calls.</p>
<p>gRPC uses Protocol Buffers (protobuf) both as its Interface Definition Language and as its binary message format. You write a <code>.proto</code> file that defines your <strong>service</strong> (with named RPC methods) and the <strong>messages</strong> those methods send and receive:</p>
<pre><code class="language-proto">service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}
</code></pre>
<p>You then run the <code>protoc</code> compiler with a gRPC plugin, which generates client and server code in your language from that definition. The generated code handles serialization, deserialization, and the network call automatically.</p>
<p>Protocol Buffers use a compact binary encoding. A message encodes field numbers and wire types rather than repeating human-readable key names, so a protobuf payload is smaller and faster to parse than equivalent JSON. gRPC runs over HTTP/2, which brings multiplexing (many calls share one connection), header compression, and native bi-directional streaming.</p>
</section>
<section><h2 id="the-key-differences"><a href="#the-key-differences" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The key differences</h2><table>
<thead>
<tr>
<th>Aspect</th>
<th>GraphQL</th>
<th>gRPC</th>
</tr>
</thead>
<tbody><tr>
<td>What it is</td>
<td>Data query/manipulation language</td>
<td>RPC framework</td>
</tr>
<tr>
<td>Origin</td>
<td>Facebook 2012, open-sourced 2015, GraphQL Foundation 2018</td>
<td>Google, open-sourced 2016</td>
</tr>
<tr>
<td>Transport</td>
<td>HTTP (any version), usually a single endpoint</td>
<td>HTTP/2 only</td>
</tr>
<tr>
<td>Data format</td>
<td>JSON (human-readable)</td>
<td>Protocol Buffers (binary)</td>
</tr>
<tr>
<td>Contract</td>
<td>Schema + type system</td>
<td><code>.proto</code> file (IDL)</td>
</tr>
<tr>
<td>Client duties</td>
<td>Write a query naming exact fields</td>
<td>Use code-generated stubs</td>
</tr>
<tr>
<td>Control of payload</td>
<td>Client picks exactly the fields</td>
<td>Contract fixed by <code>.proto</code></td>
</tr>
<tr>
<td>Streaming</td>
<td>Via subscriptions (often WebSockets)</td>
<td>Native bi-directional streaming over HTTP/2</td>
</tr>
<tr>
<td>Browser support</td>
<td>Native — just HTTP + JSON</td>
<td>Requires a proxy or gRPC-Web</td>
</tr>
<tr>
<td>Public API fit</td>
<td>Excellent — clients self-serve</td>
<td>Poor — needs generated stubs</td>
</tr>
</tbody></table>
</section>
<section><h2 id="when-graphql-is-the-better-choice"><a href="#when-graphql-is-the-better-choice" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>When GraphQL is the better choice</h2><p>GraphQL wins on the client-facing edge, where consumers are varied and you cannot control them:</p>
<ul>
<li><strong>Web and mobile clients that need exactly the right data.</strong> The client states its shape in the query; the server returns matching JSON with no over-fetching. This collapses many round-trips in data-heavy apps.</li>
<li><strong>Many unknown consumers.</strong> A schema is self-documenting, and clients can explore it via introspection without you shipping generated stubs.</li>
<li><strong>Public-facing APIs.</strong> Because GraphQL is just HTTP + JSON, browsers can call it natively with no proxy and no build step.</li>
<li><strong>Iterating without version hell.</strong> Add fields and mark old ones <code>@deprecated</code>; existing clients keep working while new clients adopt the addition.</li>
</ul>
<p>GraphQL&#39;s main costs: a schema and resolvers are extra server machinery compared to plain REST; caching is harder because queries are per-client and don&#39;t map to fixed URLs; and one complex query can be expensive unless you add query-cost analysis and depth limiting.</p>
</section>
<section><h2 id="when-grpc-is-the-better-choice"><a href="#when-grpc-is-the-better-choice" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>When gRPC is the better choice</h2><p>gRPC wins in the service-to-service core, where you control both ends:</p>
<ul>
<li><strong>Internal microservices.</strong> A shared <code>.proto</code> contract generates compatible stubs in every supported language (Go, Java, Python, C#, Ruby, and more), so client and server stay in sync at compile time. This kills a whole class of serialization bugs.</li>
<li><strong>Low latency and high throughput.</strong> Binary protobuf is smaller and faster than JSON, and HTTP/2 multiplexing lets many calls share one connection. At scale this cuts latency and bandwidth.</li>
<li><strong>Bi-directional streaming.</strong> Server push and request/response streams are first-class, which is awkward with request/response REST or GraphQL subscriptions.</li>
<li><strong>Strongly typed, polyglot teams.</strong> The <code>.proto</code> file is the single source of truth, and codegen prevents drift.</li>
</ul>
<p>gRPC&#39;s main costs: generated code and a build step rule out casual browser clients; the wire format is binary, so payloads are hard to read in logs; and it needs a proxy (or gRPC-Web) to work from a browser.</p>
</section>
<section><h2 id="the-honest-downsides"><a href="#the-honest-downsides" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The honest downsides</h2><p>GraphQL&#39;s flexibility has a real price: because the client dictates the query, servers need protection against abusive or accidental over-fetching (cost analysis, depth limits), and caching at the CDN level is hard since responses vary per query rather than per URL.</p>
<p>gRPC&#39;s price is ergonomics for people, not machines: it demands code generation, does not work directly in browsers, and produces binary payloads that are painful to debug. It also gives the client no power to shape responses — you get whatever the <code>.proto</code> defines.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="is-graphql-faster-than-grpc">Is GraphQL faster than gRPC?</h3><p>Not inherently. gRPC&#39;s binary Protocol Buffers and HTTP/2 multiplexing typically give lower latency and higher throughput than GraphQL&#39;s JSON over plain HTTP — the reason gRPC dominates internal service-to-service traffic. GraphQL&#39;s value is not raw speed but precise data fetching: it can reduce the number of round-trips a client needs, which often feels faster even though each call may be heavier. For small payloads with no nesting or streaming, the difference is usually negligible.</p>
<h3 id="can-i-use-graphql-and-grpc-together">Can I use GraphQL and gRPC together?</h3><p>Yes, and many systems do. A common pattern is GraphQL on the client-facing edge — where many unknown consumers query a schema over HTTP and get JSON — backed by gRPC services internally, where strongly typed, binary, low-latency calls happen between your own services. The GraphQL layer can resolve its fields by making gRPC calls to the services behind it.</p>
<h3 id="does-grpc-use-http">Does gRPC use HTTP?</h3><p>Yes. gRPC runs over HTTP/2 exclusively and leans on its features — multiplexing, header compression, and bi-directional streams. GraphQL, by contrast, is transport-agnostic in design but is almost always served over plain HTTP as JSON.</p>
<h3 id="how-do-i-expose-a-grpc-api-to-a-browser">How do I expose a gRPC API to a browser?</h3><p>Not directly. Browsers cannot send the HTTP/2 trailers gRPC uses for metadata, so you need either a proxy that translates between gRPC and HTTP/1.1, or the gRPC-Web protocol with its own proxy. gRPC-Web covers a subset of gRPC and does not support full bi-directional streaming. If your primary consumers are browsers, GraphQL (or REST) over plain HTTP is the practical choice.</p>
</section>
<section><h2 id="verdict"><a href="#verdict" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Verdict</h2><p>Choose GraphQL when your API sits on the edge and serves many clients you do not control — web apps, mobile apps, third parties — and you want them to fetch exactly the data they need over HTTP with a self-documenting schema. Choose gRPC when you control both ends of the connection and need the performance, strong typing, and streaming of an internal service mesh. If you have both, the strongest architecture is usually GraphQL in front, gRPC behind: GraphQL owns the developer-facing contract, gRPC owns the machine-to-machine trunk.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>What is a database transaction? ACID properties explained</title>
      <link>https://debod.id/en/posts/database-transactions-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/database-transactions-explained/</guid>
      <pubDate>Tue, 01 Sep 2026 00:00:00 GMT</pubDate>
      <category>database</category>
      <category>database</category>
      <category>devops</category>
      <category>backend</category>
      <category>performance</category>
      <category>programming</category>
      <description>A database transaction bundles several operations into one all-or-nothing unit. This guide explains transactions, the ACID properties (atomicity, consistency, isolation, durability), and isolation levels.</description>
      <content:encoded><![CDATA[<section><p>A database transaction is a sequence of operations treated as a single, indivisible unit of work: either all of them complete successfully, or none of them take effect. The best-known example is a money transfer between two bank accounts — withdrawing from one account and depositing into another must happen together. Transactions guarantee that databases stay valid and consistent even under crashes, power failures, and concurrent users.</p></section>
<section><h2 id="why-transactions-exist"><a href="#why-transactions-exist" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Why transactions exist</h2><p>Databases store data that many operations read and write at the same time. Without safeguards, a failure halfway through a multi-step operation could leave the data in a partial, inconsistent state, and concurrent operations could interfere with each other.</p>
<p>Transactions solve both problems. The essential idea — as PostgreSQL&#39;s documentation puts it — is that a transaction bundles multiple steps into a single all-or-nothing operation. The intermediate states between the steps are not visible to other concurrent transactions, and if a failure prevents the transaction from completing, none of the steps affect the database at all.</p>
<p>In most SQL databases you start a transaction with <code>BEGIN</code>, make your changes, then either make them permanent with <code>COMMIT</code> or undo them with <code>ROLLBACK</code>. Many systems also treat a single statement as an implicit transaction: if you do not issue <code>BEGIN</code>, each statement gets an implicit <code>BEGIN</code> and, if successful, a <code>COMMIT</code> wrapped around it.</p>
</section>
<section><h2 id="the-acid-properties"><a href="#the-acid-properties" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The ACID properties</h2><p>The guarantees that transactions provide are summarized by the acronym ACID: atomicity, consistency, isolation, and durability.</p>
<h3 id="atomicity">Atomicity</h3><p>Atomicity means a transaction is all-or-nothing: if one part fails, the whole transaction fails and the database is left unchanged. To the outside world, a committed transaction appears indivisible, and an aborted transaction does not happen at all. As an example, a transfer that withdraws money from account A and deposits into account B should never leave the amount removed from A without it appearing in B. An atomic system must guarantee this even in power failures, errors, and crashes.</p>
<h3 id="consistency">Consistency</h3><p>Consistency ensures that a transaction can only bring the database from one valid state to another, preserving all defined rules — constraints, triggers, cascades, and any combination of them. This prevents database corruption by an illegal transaction. An example is referential integrity, which guarantees that a primary key–foreign key relationship stays valid. If a validation check fails partway through a transaction, the whole transaction is cancelled and the affected rows are rolled back to their pre-transaction state.</p>
<h3 id="isolation">Isolation</h3><p>Isolation ensures that concurrent execution of transactions leaves the database in the same state that would be obtained if the transactions ran one at a time. The main goal is concurrency control: while a transaction is open, its incomplete changes are not visible to other transactions, and all its changes become visible simultaneously when it completes. Isolation levels trade some of this strictness for performance, as explained below.</p>
<h3 id="durability">Durability</h3><p>Durability guarantees that once a transaction has been committed, it stays committed even if the system crashes, loses power, or errors immediately afterward. This usually means completed transactions and their effects are recorded in non-volatile (permanent) storage. The PostgreSQL docs give the example of recording a cash withdrawal: the debit must not disappear in a crash right after the customer walks out the door.</p>
</section>
<section><h2 id="isolation-levels"><a href="#isolation-levels" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Isolation levels</h2><p>The SQL standard defines four isolation levels that describe how much one transaction can see of another&#39;s work. From weakest to strongest:</p>
<ul>
<li><strong>Read uncommitted</strong> — the weakest level; a transaction could see another&#39;s uncommitted changes (a &quot;dirty read&quot;). In practice, many databases, including PostgreSQL, internally treat this exactly like Read committed because they never expose uncommitted data.</li>
<li><strong>Read committed</strong> — a transaction never sees uncommitted data from other transactions. This is the default in PostgreSQL and many other databases and is adequate for most applications.</li>
<li><strong>Repeatable read</strong> — a transaction only sees data committed before it began; it never sees uncommitted data or changes committed by other transactions during its execution. This prevents dirty reads and non-repeatable reads.</li>
<li><strong>Serializable</strong> — the strictest level; any concurrent execution of a set of serializable transactions produces the same effect as running them one at a time. If concurrent transactions would produce a result impossible in any serial order, one is rolled back with a serialization failure.</li>
</ul>
<p>The four levels differ in which anomalies they allow: dirty reads, non-repeatable reads, phantom reads, and serialization anomalies. Stronger isolation gives more correctness but more locking and contention, so it is slower under heavy concurrency. Most applications default to Read committed and raise the level only where they have a documented need.</p>
</section>
<section><h2 id="acid-vs-base"><a href="#acid-vs-base" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>ACID vs BASE</h2><p>ACID and BASE describe opposite design philosophies for data systems. ACID databases prioritize consistency over availability: the whole transaction fails if an error occurs in any step. By contrast, BASE (Basically Available, Soft state, Eventually consistent) databases prioritize availability over consistency — instead of failing, users may temporarily access inconsistent data, and consistency is achieved eventually rather than immediately. No single system is fully ACID and fully BASE at the same time; they sit at opposite ends of the same spectrum. Relational databases such as PostgreSQL and MySQL are ACID; many large-scale NoSQL systems are closer to BASE.</p>
</section>
<section><h2 id="practical-guidance"><a href="#practical-guidance" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Practical guidance</h2><ul>
<li>Use transactions for any group of operations that must succeed or fail together — financial transfers, order + payment + inventory updates, multi-row writes with invariants.</li>
<li>Keep transactions short. A long open transaction holds locks and increases contention and the chance of conflicts under concurrency.</li>
<li>Prefer the default isolation level and raise it only when your queries need a stronger guarantee.</li>
<li>Handle rollback deliberately in application code: detect failures and issue <code>ROLLBACK</code> rather than leaving a transaction half-open.</li>
<li>Remember that transactions guarantee database consistency, not application correctness — misuse of a transaction (for example, missing a consistency constraint in the application) is not fixed by ACID.</li>
</ul>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-is-a-database-transaction">What is a database transaction?</h3><p>A database transaction is a sequence of operations treated as a single, all-or-nothing unit of work. Either all operations complete successfully and the changes are made permanent, or none take effect, leaving the database unchanged. The classic example is a money transfer that debits one account and credits another as one operation.</p>
<h3 id="what-do-the-letters-in-acid-stand-for">What do the letters in ACID stand for?</h3><p>ACID stands for Atomicity, Consistency, Isolation, and Durability. Atomicity means each transaction is all-or-nothing; consistency means a transaction only moves the database between valid states; isolation means concurrent transactions do not see each other&#39;s incomplete work; durability means a committed transaction stays committed even after a crash or power loss.</p>
<h3 id="what-is-the-difference-between-acid-and-base">What is the difference between ACID and BASE?</h3><p>ACID and BASE are opposite data-system design philosophies. ACID prioritizes consistency over availability, failing the whole transaction if any step errors. BASE (Basically Available, Soft state, Eventually consistent) prioritizes availability, allowing temporary inconsistent data and achieving consistency eventually. Relational databases are typically ACID, while many large-scale NoSQL systems are closer to BASE.</p>
<h3 id="what-are-the-sql-transaction-isolation-levels">What are the SQL transaction isolation levels?</h3><p>The SQL standard defines four: Read uncommitted, Read committed, Repeatable read, and Serializable, ordered from weakest to strongest. They control how much one transaction can see of another&#39;s changes — weaker levels allow anomalies like dirty reads and are faster, while Serializable guarantees every concurrent execution behaves as if the transactions ran one at a time.</p>
</section>
<section><h2 id="summary"><a href="#summary" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Summary</h2><p>A database transaction is an all-or-nothing unit of work that keeps data valid under crashes and concurrency. The ACID properties — atomicity, consistency, isolation, and durability — define the guarantees transactions provide. Isolation levels let you tune how strictly concurrent transactions are separated, trading correctness for speed. Relational databases use these mechanisms to keep data consistent, which is why they remain the default choice for systems where integrity matters. For related concepts, see our guides on <a href="/en/posts/database-index-explained/">database indexes</a> and <a href="/en/posts/sql-vs-nosql/">SQL vs NoSQL</a>.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>What is CSRF (Cross-Site Request Forgery)? How it works and how to prevent it</title>
      <link>https://debod.id/en/posts/csrf-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/csrf-explained/</guid>
      <pubDate>Tue, 01 Sep 2026 00:00:00 GMT</pubDate>
      <category>security</category>
      <category>security</category>
      <category>web</category>
      <category>javascript</category>
      <description>Cross-Site Request Forgery (CSRF) tricks a logged-in browser into sending unwanted requests to a trusted site. This guide explains how the attack works and the defenses that stop it.</description>
      <content:encoded><![CDATA[<section><p>Cross-Site Request Forgery (CSRF, also called XSRF) is an attack that tricks a user&#39;s browser into performing an unwanted action on a site where the user is already logged in. Unlike Cross-Site Scripting (XSS), which injects malicious code into a page, CSRF exploits the browser&#39;s normal behavior: automatically attaching cookies to every request to a site. An attacker does not need to steal credentials — they just need to trigger the browser to send a crafted request that carries the victim&#39;s valid session cookie.</p>
<p>CSRF is a confused deputy attack. The browser acts as an unwitting agent, following the attacker&#39;s instructions with the victim&#39;s authenticated identity. The server cannot tell the difference between a legitimate request from the victim and a forged one from the attacker&#39;s page, because both arrive with the same cookies and look identical to the server.</p>
<p>This guide explains how CSRF attacks work, what an attacker can achieve, and the defenses that reliably prevent them.</p></section>
<section><h2 id="how-csrf-works"><a href="#how-csrf-works" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How CSRF works</h2><p>For a CSRF attack to succeed, three conditions must be met:</p>
<ol>
<li>The user is logged into a target site and holds a valid session cookie.</li>
<li>The target site performs an action based only on the presence of that session cookie (no secondary verification).</li>
<li>The attacker can trigger an HTTP request to the target site from the user&#39;s browser.</li>
</ol>
<p>The classic attack vector is a crafted link or auto-submitting form on an attacker&#39;s page. When the victim visits or clicks, the browser automatically attaches the victim&#39;s session cookie and sends the request to the target site. The server processes it as a legitimate, authenticated action.</p>
<p>A concrete example: a banking site at <code>https://example-bank.com</code> has a transfer endpoint:</p>
<pre><code>POST /transfer
Content-Type: application/x-www-form-urlencoded

account=987654321&amp;amount=1000&amp;csrf_token=abc123
</code></pre>
<p>An attacker crafts a page with a hidden form:</p>
<pre><code class="language-html">&lt;form action=&quot;https://example-bank.com/transfer&quot; method=&quot;POST&quot;&gt;
  &lt;input type=&quot;hidden&quot; name=&quot;account&quot; value=&quot; attacker-account&quot; /&gt;
  &lt;input type=&quot;hidden&quot; name=&quot;amount&quot; value=&quot;10000&quot; /&gt;
&lt;/form&gt;
&lt;script&gt;document.forms[0].submit();&lt;/script&gt;
</code></pre>
<p>When a logged-in user visits the attacker&#39;s page, the browser submits the form. It includes the user&#39;s session cookie automatically. The bank sees a valid authenticated request and processes the transfer. The user may not even notice — the attacker&#39;s page redirects somewhere or stays blank.</p>
<p>The attack also works via an image or script tag:</p>
<pre><code class="language-html">&lt;img src=&quot;https://example-bank.com/transfer?account=attacker&amp;amount=10000&quot; /&gt;
</code></pre>
<p>Even a GET request triggered this way executes the action if the endpoint accepts GET. This is why GET must never perform state-changing operations.</p>
</section>
<section><h2 id="what-an-attacker-can-do-with-csrf"><a href="#what-an-attacker-can-do-with-csrf" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What an attacker can do with CSRF</h2><p>CSRF is limited to the actions the target site exposes and the victim&#39;s own privileges. If the victim is a regular user, CSRF might change their email, update their profile, or make a purchase. If the victim is an administrator, CSRF can modify site settings, add or remove users, or compromise the entire application.</p>
<p>Common CSRF targets include:</p>
<ul>
<li><strong>Account settings</strong> — changing the victim&#39;s email address, password, or security questions to lock the victim out.</li>
<li><strong>Financial transactions</strong> — transferring funds, authorizing payments, or modifying billing information.</li>
<li><strong>Social actions</strong> — posting a message, sending a message, or changing privacy settings.</li>
<li><strong>Administrative operations</strong> — creating or deleting user accounts, changing permissions, modifying site configuration.</li>
</ul>
<p>CSRF cannot read the response of the forged request (due to the Same-Origin Policy), but it does not need to. The action executes regardless of whether the attacker sees the result.</p>
</section>
<section><h2 id="defenses-that-prevent-csrf"><a href="#defenses-that-prevent-csrf" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Defenses that prevent CSRF</h2><h3 id="anti-csrf-tokens-synchronizer-token-pattern">Anti-CSRF tokens (synchronizer token pattern)</h3><p>The most widely recommended defense. The server generates a cryptographically random, unpredictable token for each user session or each request, and requires it in every state-changing request.</p>
<pre><code>POST /transfer HTTP/1.1
Cookie: session=abc123

account=987654321&amp;amount=1000&amp;csrf_token=OWY4NmQwODE4ODRjN2Q2NTlhMmZl
</code></pre>
<p>The server validates that the submitted token matches the one stored for the session. An attacker on a different origin cannot read or guess the token value, so forged requests are rejected.</p>
<p>Token requirements (per OWASP):</p>
<ul>
<li><strong>Unique</strong> per user session or per request.</li>
<li><strong>Secret</strong> — never transmitted in a URL or log.</li>
<li><strong>Unpredictable</strong> — generated with a cryptographically secure pseudo-random number generator (CSPRNG).</li>
</ul>
<h3 id="samesite-cookies">SameSite cookies</h3><p>The <code>SameSite</code> attribute tells the browser when to omit the cookie from cross-site requests. It is defined in RFC 6265bis and supported in all modern browsers.</p>
<table>
<thead>
<tr>
<th>Value</th>
<th>Behavior</th>
</tr>
</thead>
<tbody><tr>
<td><code>Strict</code></td>
<td>Cookie is sent only on same-site requests. Most secure, but breaks legitimate cross-site navigation (e.g., clicking a link from an email).</td>
</tr>
<tr>
<td><code>Lax</code></td>
<td>Cookie is sent on same-site requests and top-level cross-site navigations using safe methods (GET, HEAD, OPTIONS). Most requests from other sites (POST forms, images, iframes) do not send the cookie. Recommended for most applications.</td>
</tr>
<tr>
<td><code>None</code></td>
<td>Cookie is sent on all requests. Requires <code>Secure</code> (HTTPS). Disables CSRF protection from the cookie. Use only when the cookie is needed for legitimate cross-site requests (e.g., embedded widgets).</td>
</tr>
</tbody></table>
<p>Setting <code>SameSite=Lax</code> for session cookies stops most CSRF attacks without breaking normal browsing. The major browsers now default new cookies to <code>Lax</code> when the attribute is not specified.</p>
<h3 id="fetch-metadata-headers">Fetch Metadata headers</h3><p>Modern browsers send additional request headers that describe the context of the request. The <code>Sec-Fetch-Site</code> header indicates the relationship between the requester&#39;s origin and the target:</p>
<ul>
<li><code>same-origin</code> — request comes from the same site.</li>
<li><code>same-site</code> — request comes from the same site (including subdomain variation).</li>
<li><code>cross-site</code> — request comes from a different site.</li>
<li><code>none</code> — direct navigation (typed in address bar).</li>
</ul>
<p>Servers can inspect <code>Sec-Fetch-Site</code> and reject unsafe cross-site requests (POST, PUT, PATCH, DELETE) from <code>cross-site</code> origins. This requires no client-side changes and works across all modern browsers (98%+ global coverage since 2023).</p>
<p>Example server-side check (per OWASP):</p>
<pre><code>Sec-Fetch-Site: cross-site  +  POST  →  reject (403)
Sec-Fetch-Site: same-origin +  POST  →  allow
Sec-Fetch-Site: cross-site  +  GET   →  allow (safe method)
</code></pre>
<h3 id="origin-and-referer-header-verification">Origin and Referer header verification</h3><p>The <code>Origin</code> and <code>Referer</code> HTTP headers indicate the source of a request. Servers can check that the origin matches an allowed list. However, these headers are not always present — browsers do not send <code>Origin</code> on all requests, and <code>Referer</code> can be stripped by privacy extensions, HTTPS-to-HTTP transitions, or browser settings. Relying solely on these headers is not recommended, but they serve as useful supplementary checks.</p>
<h3 id="double-submit-cookie-pattern">Double-submit cookie pattern</h3><p>When server-side state storage is not available, the double-submit cookie pattern provides a stateless alternative. The server sets a random token in a cookie. The client reads this cookie value and also includes it as a custom request header or form parameter. The server verifies that both values match. Because an attacker cannot read or set cookies on the target domain from a cross-site page, they cannot forge a matching value.</p>
<p>The <strong>signed double-submit</strong> variant binds the token to the session using HMAC with a server-side secret, adding protection against cookie injection attacks. The naive variant (without signing) is discouraged per OWASP because subdomains under attacker control can inject matching cookies.</p>
</section>
<section><h2 id="what-does-not-stop-csrf"><a href="#what-does-not-stop-csrf" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What does NOT stop CSRF</h2><p>Several common beliefs about CSRF prevention are wrong:</p>
<ul>
<li><strong>Using HTTPS alone</strong> does not stop CSRF. Encryption protects the transport but not the request — the browser still sends the cookie.</li>
<li><strong>Secret cookies</strong> (a cookie whose value is unknown to the attacker) do not help. All cookies, including secret ones, are sent automatically with every request.</li>
<li><strong>Only accepting POST requests</strong> is not sufficient. POST forms can be submitted from an attacker&#39;s page just as easily.</li>
<li><strong>Checking the Referer header</strong> alone is unreliable because it can be absent or stripped.</li>
<li><strong>URL rewriting</strong> (embedding the session ID in the URL) exposes the session ID in browser history, server logs, and the Referer header sent to other sites.</li>
</ul>
</section>
<section><h2 id="the-relationship-between-xss-and-csrf"><a href="#the-relationship-between-xss-and-csrf" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The relationship between XSS and CSRF</h2><p>Cross-Site Scripting (XSS) and CSRF are separate vulnerabilities, but XSS can defeat all CSRF protections. If an attacker injects a script into the target site itself, that script runs in the trusted origin and can read page content, including anti-CSRF tokens. It can also make direct requests with the user&#39;s cookies — no need to forge a request at all. This is why XSS prevention (input validation, output encoding) and CSRF prevention must both be implemented; they protect against different threats and together form defense in depth.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-is-the-difference-between-csrf-and-xss">What is the difference between CSRF and XSS?</h3><p>XSS injects malicious script into a trusted page, letting the attacker read and modify page content or execute code in the user&#39;s context. CSRF tricks the user&#39;s browser into sending a legitimate-looking request without the user&#39;s knowledge. XSS can be used to steal CSRF tokens and launch CSRF attacks, but the two are distinct vulnerabilities with different root causes and defenses.</p>
<h3 id="do-modern-browsers-protect-against-csrf-by-default">Do modern browsers protect against CSRF by default?</h3><p>Modern browsers have improved the situation with the SameSite cookie default (most browsers now default to <code>Lax</code> for new sessions), but this is not a complete solution. Legacy sites that explicitly set <code>SameSite=None</code> remain vulnerable, and <code>Lax</code> only blocks certain cross-site vectors. Server-side CSRF protection (tokens or Fetch Metadata checks) is still required for applications that handle sensitive actions.</p>
<h3 id="can-csrf-attacks-be-launched-from-emails">Can CSRF attacks be launched from emails?</h3><p>Yes. HTML email clients that render content can trigger GET requests via embedded images or linked resources. This is why GET endpoints should never perform state-changing actions. For POST requests, email clients that render HTML forms can be exploited similarly to malicious web pages.</p>
<h3 id="is-csrf-still-relevant-in-2026">Is CSRF still relevant in 2026?</h3><p>Yes. CSRF remains in the OWASP Top 10 and affects applications that do not implement proper defenses. While SameSite cookie defaults have reduced the overall attack surface, many existing applications still lack CSRF protections, and <code>SameSite=None</code> is still required for legitimate cross-site use cases (embedded widgets, single sign-on flows) where CSRF tokens remain essential.</p>
</section>
<section><h2 id="summary"><a href="#summary" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Summary</h2><p>CSRF exploits the browser&#39;s automatic cookie behavior to make authenticated requests look legitimate to the server. It requires the user to be logged in, the site to rely solely on session cookies for authentication, and the attacker to be able to trigger a request from the victim&#39;s browser.</p>
<p>The primary defenses are anti-CSRF tokens (synchronizer token pattern), <code>SameSite=Lax</code> session cookies, and Fetch Metadata header checks (<code>Sec-Fetch-Site</code>). These are complementary — defense in depth means using more than one. XSS can bypass CSRF protections, so both classes of vulnerability must be addressed together.</p>
</section>
<section><h2 id="sources"><a href="#sources" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Sources</h2><ul>
<li><a href="https://owasp.org/www-community/attacks/csrf">OWASP — Cross Site Request Forgery</a> (CC BY-SA 4.0)</li>
<li><a href="https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Request_Forgery_Prevention_Cheat_Sheet.html">OWASP — Cross-Site Request Forgery Prevention Cheat Sheet</a> (CC BY-SA 4.0)</li>
<li><a href="https://en.wikipedia.org/wiki/Cross-site_request_forgery">Wikipedia — Cross-site request forgery</a> (CC BY-SA 4.0)</li>
</ul>
</section>]]></content:encoded>
    </item>
    <item>
      <title>What is a cron job? How cron works and how to schedule tasks</title>
      <link>https://debod.id/en/posts/cron-jobs-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/cron-jobs-explained/</guid>
      <pubDate>Tue, 01 Sep 2026 00:00:00 GMT</pubDate>
      <category>devops</category>
      <category>devops</category>
      <category>automation</category>
      <description>What is a cron job? Learn how cron and crontab work, the five-field schedule syntax with examples, common use cases, and modern alternatives like systemd timers and managed schedulers.</description>
      <content:encoded><![CDATA[<section><p>A cron job is a task that runs automatically on a fixed schedule. The <code>cron</code> daemon is a time-based job scheduler found on Unix-like operating systems: it watches the clock, reads a list of scheduled jobs from a configuration file, and runs each job when its schedule matches the current time. The daemon name comes from <em>Chronos</em>, the Greek word for time. Cron was created at AT&amp;T Bell Laboratories and released in May 1975, so the tool has been scheduling background work for fifty years.</p>
<p>Cron is best suited to repetitive tasks — things that should happen hourly, daily, weekly, or monthly. If you need to run a task exactly once at a specific moment, the companion <code>at</code> utility exists for that purpose instead.</p></section>
<section><h2 id="crontab-the-file-that-defines-jobs"><a href="#crontab-the-file-that-defines-jobs" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Crontab: the file that defines jobs</h2><p>The actions of cron are driven by a <strong>crontab</strong> (cron table) file, which stores the list of jobs and the instructions to the cron daemon. Each Unix user can have their own crontab, and there is usually also a system-wide crontab stored in <code>/etc</code> (often in a subdirectory such as <code>/etc/cron.d</code>) that only system administrators can edit. Users edit their own schedule with the <code>crontab -e</code> command, regardless of where the actual implementation stores the file on disk.</p>
<p>Every line of a crontab represents one job. Each line has five time fields followed by the command to run:</p>
<pre><code># ┌───────────── minute (0–59)
# │ ┌───────────── hour (0–23)
# │ │ ┌───────────── day of the month (1–31)
# │ │ │ ┌───────────── month (1–12)
# │ │ │ │ ┌───────────── day of the week (0–6, Sunday=0; on some systems 7 is also Sunday)
# │ │ │ │ │
# * * * * * &lt;command to execute&gt;
</code></pre>
<p>For example, this line clears the web server&#39;s error log one minute after midnight every day:</p>
<pre><code>1 0 * * * printf &quot;&quot; &gt; /var/log/apache/error_log
</code></pre>
<p>This line runs a backup script at 23:45 every Saturday:</p>
<pre><code>45 23 * * 6 /home/example/scripts/export_dump.sh
</code></pre>
<p>The five fields accept several patterns beyond a single number:</p>
<ul>
<li><code>*</code> matches every allowed value.</li>
<li>A range like <code>9-17</code> matches every value in that span.</li>
<li>A list like <code>1,15,30</code> matches those specific values (and lists can combine ranges, as in <code>1-5,10,15</code>).</li>
<li>A step value like <code>*/15</code> or <code>9-17/2</code> skips through the values — <code>*/15</code> in the minute field means every 15 minutes, and <code>9-17/2</code> in the hour field means every two hours from 9 to 17.</li>
</ul>
<p>The common <em>Vixie cron</em> implementation — the 4th BSD edition written by Paul Vixie that ships in many Linux distributions — also supports a few convenience macros such as <code>@daily</code>, <code>@weekly</code>, and <code>@monthly</code>, and adds an optional sixth field giving the account username that runs the job.</p>
</section>
<section><h2 id="how-cron-decides-when-to-run"><a href="#how-cron-decides-when-to-run" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How cron decides when to run</h2><p>Cron normally runs a job only when all five fields (minute, hour, day of month, month, day of week) match the current time. There is one important exception: if both the <em>day of month</em> field (field 3) and the <em>day of week</em> field (field 5) are restricted rather than <code>*</code>, then the job runs when <strong>either</strong> field matches. This lets you write a schedule like &quot;run on the 1st and on Mondays&quot; without contradiction.</p>
</section>
<section><h2 id="what-environment-a-cron-job-gets"><a href="#what-environment-a-cron-job-gets" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What environment a cron job gets</h2><p>When cron executes a command from a crontab entry, it supplies a default environment that defines at least four variables: <code>HOME</code> (the user&#39;s home directory), <code>LOGNAME</code> (the user&#39;s login name), <code>PATH</code> (a search path for standard utilities), and <code>SHELL</code> (the command interpreter, normally <code>sh</code>). This matters in practice: because cron does not load your interactive shell&#39;s startup files, your usual environment — extra <code>PATH</code> entries, aliases, and variables — is not available unless you set it inside the crontab or the script itself.</p>
<p>If a job writes to standard output or standard error and these are not redirected, cron mails that output to the user. On a headless server this often ends up in <code>$MAIL</code> or the system mail spool, so it is common practice to redirect output with <code>&gt;&gt; logfile 2&gt;&amp;1</code> and to track failures separately.</p>
</section>
<section><h2 id="common-uses-and-best-practices"><a href="#common-uses-and-best-practices" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Common uses and best practices</h2><p>Cron is typically used for system maintenance and administration, but it can automate almost any repetitive task: running nightly backups, rotating logs, sending scheduled reports or emails, cleaning up temporary files, syncing databases, fetching data from an API on a schedule, and triggering batch jobs that don&#39;t need to run in real time.</p>
<p>A few habits keep cron jobs reliable:</p>
<ul>
<li>Use <strong>absolute paths</strong> to commands and files. Since the default <code>PATH</code> is minimal and timing-based jobs can run before interactive mounts are available, script in full paths where possible.</li>
<li><strong>Redirect output</strong> so cron does not try to mail a growing log (or drop a failure you need to see) — and consider having failures exit with a non-zero status so monitoring catches them.</li>
<li><strong>Lock single-instance jobs</strong> so a long-running job does not start a second copy before the first finishes.</li>
<li><strong>Mind the clock</strong>: cron generally runs on server-local time, so watch seasonal clock changes (daylight saving time) — a job set for 2:30 may run oddly on the day the clock shifts — and pick a stable time zone for servers that serve users across regions.</li>
<li>Make jobs <strong>idempotent</strong> where possible so a re-run is safe — good defense for the rare case a job runs twice.</li>
</ul>
</section>
<section><h2 id="modern-alternatives-and-managed-schedulers"><a href="#modern-alternatives-and-managed-schedulers" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Modern alternatives and managed schedulers</h2><p>Cron and crontab remain ubiquitous, but many modern systems replace them:</p>
<ul>
<li><strong>systemd timers</strong> are the modern replacement on many Linux distributions. Instead of 5-field crontab lines, you define <code>.timer</code> units that run associated <code>.service</code> units, with more expressive calendars and built-in features such as predictable run-once and randomized start offsets. Many distributions now provide them as the preferred scheduler.</li>
<li><strong>Kubernetes CronJobs</strong> bring the cron format into a cluster. A <code>CronJob</code> object creates a <code>Job</code> on a repeating schedule written in cron syntax; each schedule entry matches a <em>crontab</em> line in spirit, and fields like <code>startingDeadlineSeconds</code> let you cap how late a missed run may start (a job that misses its deadline is skipped and counted as failed).</li>
<li><strong>Managed schedulers in the cloud</strong> do the same work without managing servers, commonly as part of serverless platforms. Google Cloud Scheduler, for example, accepts &quot;unix-cron&quot; format as a job schedule, targets an endpoint or message queue (such as Pub/Sub), and lets you choose a time zone while noting that daylight-saving time can cause jobs to run or not run unexpectedly.</li>
<li><strong>In-process schedulers</strong> (an app&#39;s own timing or a library like a task queue) cover the many applications where a small callback on a schedule beats a separate scheduler process.</li>
</ul>
<p>The choice is usually about <em>where the work lives</em>: cron and systemd timers schedule work on your own machine, while managed schedulers and CronJobs schedule work that runs in cloud functions, containers, or serverless platforms.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-does-a-cron-job-do">What does a cron job do?</h3><p>A cron job runs a command automatically on a fixed schedule — for example, every night at 3:00. Cron reads the schedule from a crontab file and executes the associated command whenever the current time matches the schedule&#39;s five fields.</p>
<h3 id="what-do-the-five-fields-in-a-cron-line-mean">What do the five fields in a cron line mean?</h3><p>The five fields are minute (0–59), hour (0–23), day of the month (1–31), month (1–12), and day of the week (0–6, Sunday=0). Together they describe when a job should run, and each field can use <code>*</code>, ranges, lists, or step values.</p>
<h3 id="how-do-i-edit-my-cron-jobs">How do I edit my cron jobs?</h3><p>Use the <code>crontab -e</code> command to open your user&#39;s crontab in an editor, add or change lines, and save. <code>crontab -l</code> lists your current jobs, and <code>crontab -r</code> removes them. System-wide schedules live in <code>/etc</code>, usually under <code>/etc/cron.d</code>.</p>
<h3 id="what-is-the-difference-between-cron-and-systemd-timers">What is the difference between cron and systemd timers?</h3><p>Cron uses 5-field crontab lines that the cron daemon checks every minute. systemd timers are the modern replacement on many Linux distributions: they use <code>.timer</code> units with more expressive calendar syntax, built-in run-once behavior, and randomized start offsets, and are increasingly the preferred scheduler on systemd-based systems.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>What is code splitting? How it works and when to use it</title>
      <link>https://debod.id/en/posts/code-splitting-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/code-splitting-explained/</guid>
      <pubDate>Tue, 01 Sep 2026 00:00:00 GMT</pubDate>
      <category>performance</category>
      <category>javascript</category>
      <category>performance</category>
      <category>web</category>
      <description>Code splitting divides a JavaScript bundle into smaller chunks that load on demand, speeding up initial page load. This guide explains the technique, the tools, and when it matters most.</description>
      <content:encoded><![CDATA[<section><p>Modern web applications ship significant amounts of JavaScript. The median JavaScript payload on mobile devices runs into hundreds of kilobytes, and complex applications can reach several megabytes. Every byte of JavaScript must be downloaded, parsed, compiled, and executed before a page becomes interactive — a process that is measurably slower on mobile devices and constrained networks than on a desktop with a fast connection.</p>
<p>Code splitting is the practice of dividing a JavaScript bundle into smaller, independently loadable pieces so that the browser downloads only the code needed for the current view. The initial page load gets a small bundle with the critical path code; additional code loads only when the user needs it.</p></section>
<section><h2 id="why-bundling-alone-is-not-enough"><a href="#why-bundling-alone-is-not-enough" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Why bundling alone is not enough</h2><p>Without code splitting, a bundler such as webpack, Rollup, or esbuild produces a single JavaScript file (or a small number of files) containing the entire application. This makes deployment simple and reduces the number of network requests, but it forces users to download the entire application — including code for features they may never use — before the page becomes interactive.</p>
<p>For large single-page applications, this means users on slower connections wait seconds before the page responds. Lighthouse flags a warning when JavaScript execution time exceeds 2 seconds and fails when it exceeds 3.5 seconds. Code splitting directly addresses this by reducing the initial JavaScript payload.</p>
</section>
<section><h2 id="route-based-and-component-based-splitting"><a href="#route-based-and-component-based-splitting" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Route-based and component-based splitting</h2><p>The two main strategies are route-based splitting and component-based splitting.</p>
<h3 id="route-based-splitting">Route-based splitting</h3><p>Route-based splitting divides code at the page level. Each route — home, dashboard, settings — becomes its own chunk. The browser downloads the chunk for the current route only; chunks for other routes load only when the user navigates to them.</p>
<p>Most modern frameworks support this automatically. In React, <code>React.lazy()</code> and <code>Suspense</code> handle route splitting. In Vue, dynamic imports on route definitions produce separate chunks. In Svelte, dynamic <code>import()</code> in the router achieves the same result.</p>
<h3 id="component-based-splitting">Component-based splitting</h3><p>Component-based splitting targets individual heavy components — a chart library, a rich-text editor, a map widget. These modules may weigh hundreds of kilobytes but are needed only when the user interacts with a specific feature.</p>
<p>The pattern uses the dynamic <code>import()</code> syntax, which returns a promise that resolves once the module loads:</p>
<pre><code>button.addEventListener(&#39;click&#39;, async () =&gt; {
  const { Chart } = await import(&#39;./chart.js&#39;);
  new Chart(container, data);
});
</code></pre>
<p>The code inside <code>import()</code> does not appear in the initial bundle. The bundler creates a separate chunk that loads only when the event fires.</p>
</section>
<section><h2 id="react-lazy-and-suspense"><a href="#react-lazy-and-suspense" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>React.lazy and Suspense</h2><p>React applications commonly use <code>React.lazy()</code> to split component-level imports. <code>React.lazy()</code> accepts a function that calls <code>import()</code> and returns a component that suspends rendering while the module loads:</p>
<pre><code>import { lazy, Suspense } from &#39;react&#39;;
const HeavyChart = lazy(() =&gt; import(&#39;./HeavyChart&#39;));

function App() {
  return (
    &lt;Suspense fallback={&lt;LoadingSpinner /&gt;}&gt;
      &lt;HeavyChart /&gt;
    &lt;/Suspense&gt;
  );
}
</code></pre>
<p><code>Suspense</code> shows a fallback UI while the chunk downloads. The fallback should be lightweight — a spinner or skeleton — so it renders fast and does not block interactivity.</p>
</section>
<section><h2 id="when-to-split-and-when-not-to"><a href="#when-to-split-and-when-not-to" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>When to split and when not to</h2><p>Code splitting adds a network round-trip for each chunk. A page with ten lazy-loaded components triggers ten separate network requests. On HTTP/1.1, this overhead can outweigh the benefit. On HTTP/2 (which most modern servers and CDNs use), multiplexing allows many requests to share a single connection efficiently, making the overhead negligible.</p>
<p>Guidelines:</p>
<ul>
<li><strong>Split route-level code.</strong> Every significant page or view deserves its own chunk. This is the highest-impact split because the browser downloads nothing it will not need on the current route.</li>
<li><strong>Defer heavy components.</strong> A map, chart, or markdown editor that appears below the fold or behind a click should lazy-load. Do not lazy-load components that are visible above the fold — deferring the hero section delays Largest Contentful Paint (LCP).</li>
<li><strong>Preload on hover.</strong> Adding <code>&lt;link rel=&quot;prefetch&quot;&gt;</code> or bundler prefetch hints on navigation links fetches the next route&#39;s chunk before the user clicks, making the navigation feel instant.</li>
</ul>
</section>
<section><h2 id="measuring-the-impact"><a href="#measuring-the-impact" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Measuring the impact</h2><p>Use the browser&#39;s Network panel or Lighthouse to measure JavaScript payload sizes before and after splitting. Webpack Bundle Analyzer and source-map-explorer visualize bundle contents, making it easy to spot large dependencies that belong in a lazy chunk.</p>
<p>Set performance budgets in the bundler configuration to prevent payload sizes from growing silently over time. A budget of 150–200 KB compressed for the initial bundle is a reasonable target for most applications.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-is-the-difference-between-code-splitting-and-tree-shaking">What is the difference between code splitting and tree shaking?</h3><p>Tree shaking removes unused code from the bundle at build time based on static import/export analysis. Code splitting divides the bundle into separate files loaded at different times. Both reduce payload size, but tree shaking works at compile time while code splitting works at runtime. They are complementary — use both.</p>
<h3 id="does-code-splitting-work-with-server-side-rendering">Does code splitting work with server-side rendering?</h3><p>Yes. Server-side rendered pages still benefit from code splitting on the client. The initial HTML renders on the server, giving users a fast first paint. JavaScript bundles for interactive components then load separately, and hydration happens progressively as chunks arrive. React&#39;s <code>Suspense</code> and Next.js dynamic imports work in SSR environments without sacrificing the initial paint.</p>
<h3 id="what-happens-if-a-lazy-loaded-chunk-fails-to-load">What happens if a lazy-loaded chunk fails to load?</h3><p>If the network fails or the server returns an error for a lazy chunk, the dynamic <code>import()</code> promise rejects. Handle this with a <code>.catch()</code> block to show a user-friendly error message and offer a retry option, rather than silently breaking the feature.</p>
<h3 id="how-does-code-splitting-affect-caching">How does code splitting affect caching?</h3><p>Code splitting improves cache efficiency because each chunk changes only when the code it contains changes. A navigation component that never changes produces a chunk that remains cached indefinitely. With a single monolithic bundle, any code change invalidates the entire cache. However, very small chunks (under 5 KB) may be less efficiently cached because the browser overhead per request becomes significant.</p>
</section>
<section><h2 id="summary"><a href="#summary" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Summary</h2><p>Code splitting is one of the most effective ways to reduce initial page load time. By dividing JavaScript into smaller chunks loaded on demand — route-level or component-level — the browser downloads only the code the user actually needs. Use route-based splitting as a baseline; add component-level splitting for heavy features behind user interaction. Pair splitting with prefetching for navigable routes and measure with Lighthouse to keep payloads within budget.</p>
<p>For more on JavaScript performance, see <a href="/en/posts/web-compression-explained/">Web compression: how gzip and Brotli shrink the web</a> and <a href="/en/posts/browser-cache-explained/">What is a browser cache?</a>. For the broader performance picture, <a href="/en/posts/caching-explained/">What is caching?</a> covers the full caching stack.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>What is Bash scripting? Syntax, variables, control flow, and best practices explained</title>
      <link>https://debod.id/en/posts/bash-scripting-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/bash-scripting-explained/</guid>
      <pubDate>Tue, 01 Sep 2026 00:00:00 GMT</pubDate>
      <category>devops</category>
      <category>devops</category>
      <description>Bash is the default Unix shell on most Linux distributions and on macOS. Learn how Bash scripts work: variables, parameter expansion, conditionals, loops, functions, exit codes, and best practices for reliable, safe scripts.</description>
      <content:encoded><![CDATA[<section><p>Bash (Bourne Again Shell) is the default Unix shell on most Linux distributions and on macOS. A shell is a program that reads commands from the user, runs them, and prints the results. Besides its interactive mode, Bash can also read commands from a text file called a <em>shell script</em> — a sequence of commands executed line by line, just as if you had typed them in the terminal.</p>
<p>Bash scripts are the backbone of automation on Linux servers: cron jobs, CI/CD pipelines, provisioning config, and one-off file processing. Unlike Python or JavaScript, Bash is not compiled — the interpreter executes each line directly. This makes writing and testing scripts fast, but it also means small mistakes (an unbalanced quote, a misplaced space, an unquoted variable) can produce subtle bugs. A solid understanding of how Bash interprets a command line is the main prerequisite for writing correct scripts.</p></section>
<section><h2 id="what-is-bash-and-where-it-came-from"><a href="#what-is-bash-and-where-it-came-from" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What is Bash and where it came from</h2><p>Bash was created by Brian Fox for the GNU Project and released on 8 June 1989. Its name is a play on the <em>Bourne shell</em> (<code>sh</code>) it succeeds — Bash is the &quot;Bourne Again shell&quot;. Its original goal was to provide a free, POSIX-compatible shell for the GNU system, because at the time POSIX set the standard yet free <code>sh</code> implementations were limited.</p>
<p>Over the years Bash kept backward compatibility with <code>sh</code> scripts while adding new features: arrays, parameter expansion, arithmetic expansion, <code>[[ ... ]]</code>, process substitution, and job control. Modern versions (5.x, maintained by Chet Ramey) are the default interpreter on mainstream Linux distributions and the default interactive shell on macOS up to Catalina. Today Bash is available on nearly every Unix-like system, and through WSL or Cygwin on Windows too.</p>
</section>
<section><h2 id="how-a-shell-script-runs"><a href="#how-a-shell-script-runs" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How a shell script runs</h2><p>A shell script is an ordinary text file. The first line starting with <code>#!</code> (called the <em>shebang</em>) tells the kernel which interpreter to use:</p>
<pre><code class="language-bash">#!/usr/bin/env bash
echo &quot;Hello, world&quot;
</code></pre>
<p>Without a shebang, the kernel may refuse to execute the script. When a script runs, Bash reads it line by line. For each line, Bash performs several steps: <em>tokenizing</em> (splitting the line into words and operators), alias expansion, brace expansion, tilde expansion, parameter and variable expansion, <em>command substitution</em>, arithmetic expansion, <em>word splitting</em>, and pathname expansion (<em>globbing</em>) — all before the command is actually executed.</p>
<p>This expansion order matters. Because word splitting happens after variable expansion, a variable containing a space gets split into multiple arguments. That is why the idiom <code>echo &quot;$var&quot;</code> (with quotes) differs from <code>echo $var</code>: the quotes prevent word splitting.</p>
</section>
<section><h2 id="variables-parameters-and-expansion"><a href="#variables-parameters-and-expansion" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Variables, parameters, and expansion</h2><p>Bash distinguishes <em>shell variables</em> (local to the shell) from <em>environment variables</em> (inherited by child processes). You assign both with <code>name=value</code>, mark a variable for export with <code>export VAR=&quot;value&quot;</code>, and read them with a <code>$</code> prefix.</p>
<p>Parameter expansion is Bash&#39;s main power tool. Some common forms:</p>
<ul>
<li><code>${var}</code> — the value of <code>var</code>. The form <code>${var:-default}</code> returns a default if <code>var</code> is unset or empty.</li>
<li><code>${var:=default}</code> — set a default if <code>var</code> is unset.</li>
<li><code>${var#pattern}</code> — remove the shortest matching prefix. <code>${var##pattern}</code> removes the longest.</li>
<li><code>${var%suffix}</code> — remove the shortest matching suffix. <code>%%</code> removes the longest.</li>
<li><code>${var/from/to}</code> — replace the first match; <code>${var//from/to}</code> replaces all.</li>
<li><code>${#var}</code> — string length.</li>
<li><code>${array[i]}</code> — an array element. <code>${array[@]}</code> all elements. <code>${!array[@]}</code> the keys.</li>
</ul>
<p>Always quote variable expansions with double quotes unless you explicitly want word splitting or globbing. A handy rule: <code>echo &quot;$var&quot;</code> is almost always right; <code>echo $var</code> often introduces bugs.</p>
</section>
<section><h2 id="control-flow-conditionals-case-and-loops"><a href="#control-flow-conditionals-case-and-loops" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Control flow: conditionals, case, and loops</h2><p>Bash&#39;s control structures use reserved words: <code>if</code>, <code>then</code>, <code>else</code>, <code>elif</code>, <code>fi</code>, <code>case</code>, <code>esac</code>, <code>for</code>, <code>while</code>, <code>until</code>, <code>do</code>, <code>done</code>, <code>break</code>, and <code>continue</code>. These keywords cannot be used as variable names, which is why you see naming conventions like <code>my_count</code> or <code>result</code> rather than <code>count</code>.</p>
<p>The <code>if</code> conditional tests a command, not an expression. The exit status (0 means true, nonzero means false) decides which branch runs. For tests on numbers, strings, and files, use <code>[[ ... ]]</code>:</p>
<pre><code class="language-bash">if [[ -f &quot;$path&quot; &amp;&amp; -r &quot;$path&quot; ]]; then
  echo &quot;file is readable&quot;
elif [[ -d &quot;$path&quot; ]]; then
  echo &quot;it&#39;s a directory&quot;
else
  echo &quot;not found&quot;
fi
</code></pre>
<p>For many patterns, <code>case</code> is cleaner than a long <code>if</code>/<code>elif</code> chain. The <code>for</code> loop iterates over a word list, brace expansion, or a glob result: <code>for f in *.log; do ...; done</code>. <code>while</code> tests a condition each iteration. <code>break</code> exits a loop; <code>continue</code> jumps to the next iteration. Understanding exit status is fundamental: every command produces a code from 0–255, and <code>&amp;&amp;</code>/<code>||</code> compose them.</p>
</section>
<section><h2 id="functions-and-exit-codes"><a href="#functions-and-exit-codes" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Functions and exit codes</h2><p>Functions group reusable commands. The syntax:</p>
<pre><code class="language-bash">greet() {
  local name=&quot;${1:-world}&quot;
  echo &quot;Hello, $name&quot;
}
greet &quot;Bash&quot;
</code></pre>
<p>Always declare local variables with <code>local</code> to prevent them leaking into the caller&#39;s environment. Positional parameters (<code>$1</code>, <code>$2</code>, ... <code>$@</code>) carry arguments. <code>$#</code> is the argument count; <code>&quot;$@&quot;</code> (quoted) expands to the arguments as separate words.</p>
<p>Every command, including a function, returns an exit code between 0 and 255. Zero means success; nonzero indicates an error. <code>exit N</code> sets the script&#39;s exit code; bare <code>exit</code> returns the last command&#39;s code. Convention: 0 success, 1 general error, 2 usage error (such as a missing argument), 126 command not executable, 127 command not found, 128+N a process killed by signal N. Consistent exit codes make scripts composable with <code>&amp;&amp;</code> and usable in CI pipelines.</p>
</section>
<section><h2 id="input-output-redirection-and-pipelines"><a href="#input-output-redirection-and-pipelines" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Input, output, redirection, and pipelines</h2><p>Bash treats three standard streams: stdin (0), stdout (1), and stderr (2). Redirection rearranges these streams:</p>
<ul>
<li><code>cmd &gt; file</code> — write stdout to <code>file</code> (overwrite).</li>
<li><code>cmd &gt;&gt; file</code> — append stdout to <code>file</code>.</li>
<li><code>cmd 2&gt; err.log</code> — write stderr to <code>err.log</code>.</li>
<li><code>cmd &gt; out 2&gt;&amp;1</code> — merge stderr into stdout.</li>
<li><code>cmd &lt; input</code> — read stdin from <code>input</code>.</li>
<li><code>cmd1 | cmd2</code> — connect cmd1&#39;s stdout to cmd2&#39;s stdin.</li>
</ul>
<p>A <em>pipeline</em> (<code>|</code>) runs commands in parallel; the kernel handles the buffering. <code>$PIPESTATUS</code> is an array of each stage&#39;s exit codes. <code>xargs</code> turns stdin into arguments for another command; without <code>-d</code> it splits on spaces, a common source of bugs.</p>
<p>A heredoc (<code>&lt;&lt;EOF</code>) lets you embed multi-line text blocks inside a script:</p>
<pre><code class="language-bash">cat &lt;&lt;&#39;USAGE&#39; &gt;&amp;2
Usage: deploy.sh &lt;env&gt;
USAGE
</code></pre>
<p>Quoting the delimiter (<code>&#39;EOF&#39;</code>) disables expansion inside the heredoc, useful for help messages and templates.</p>
</section>
<section><h2 id="best-practices-and-common-pitfalls"><a href="#best-practices-and-common-pitfalls" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Best practices and common pitfalls</h2><p>Reliable Bash scripts follow a few ground rules:</p>
<ul>
<li><strong>Always quote variables</strong>: <code>echo &quot;$var&quot;</code>, not <code>echo $var</code>. Spaces and globs in values will surface bugs.</li>
<li><strong>Use <code>set -euo pipefail</code></strong>: <code>-e</code> stops on error, <code>-u</code> catches unset variables, <code>-o pipefail</code> catches failures inside a pipeline.</li>
<li><strong>Perform input validation</strong>: check the argument count (<code>$#</code>), test types with <code>[[ ]]</code>, use <code>getopts</code> for options.</li>
<li><strong>Log to stderr</strong> (<code>&gt;&amp;2</code>), not stdout, so output can be piped to other programs.</li>
<li><strong>Use <code>local</code></strong> inside functions, and <code>readonly</code> for constants.</li>
<li><strong>Format with <code>shellcheck</code></strong>: a static linter that catches most common pitfalls.</li>
</ul>
<p>The most common pitfalls: unquoted comparisons, <code>[</code> vs <code>[[</code> confusion, <code>cd</code> without checking success, <code>for f in $(ls)</code> which breaks file names with spaces, and <code>cat file | grep</code> when <code>grep ... file</code> is correct. Recognizing these patterns saves debugging time.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-is-the-difference-between-bash-and-sh">What is the difference between Bash and sh?</h3><p><code>sh</code> is the POSIX name for the Bourne shell family. Bash is POSIX-compliant but adds many features (arrays, <code>[[ ]]</code>, <code>$RANDOM</code>, brace expansion, process substitution, and more). On most Linux systems <code>/bin/sh</code> is a symlink to <code>dash</code> or to <code>bash</code> in POSIX mode. For scripts that must run across many shells, write to <code>sh</code>; for scripts targeting modern Linux, use <code>bash</code> with the <code>#!/usr/bin/env bash</code> shebang.</p>
<h3 id="why-should-i-use-instead-of">Why should I use <code>[[</code> instead of <code>[</code>?</h3><p><code>[[ ... ]]</code> is a Bash keyword that offers safer, richer features: pattern matching (<code>[[ $f == *.log ]]</code>), <code>&amp;&amp;</code>/<code>||</code> logical operators without escaping, and no word splitting or pathname expansion on variables. <code>[</code> (alias <code>test</code>) is a portable command that receives arguments after word splitting, so unquoted variables often break. Use <code>[[</code> in Bash; use <code>[</code> only if you must write a POSIX <code>sh</code> script.</p>
<h3 id="how-do-i-handle-errors-in-a-script">How do I handle errors in a script?</h3><p>Enable strict mode at the top: <code>set -euo pipefail</code>. Handle expected errors explicitly with <code>||</code> or <code>if</code> (for example, <code>mkdir -p &quot;$dir&quot; || exit 1</code>). Check the success of critical commands (<code>cd</code>, <code>cp</code>, <code>curl</code>) before continuing. Write error messages to stderr (<code>echo &quot;failed&quot; &gt;&amp;2</code>) and use meaningful exit codes. For cleanup, use <code>trap &#39;rm -f &quot;$tmp&quot;&#39; EXIT</code> so resources are always cleaned up.</p>
<h3 id="are-bash-scripts-portable-to-macos-and-windows">Are Bash scripts portable to macOS and Windows?</h3><p>On macOS, an older Bash (3.2) ships for licensing reasons; install 5.x via Homebrew for modern features. On Windows, Bash is available through WSL (Windows Subsystem for Linux) or Git Bash (MSYS). Scripts using <code>[[</code>, <code>==</code> inside <code>[[</code>, and associative arrays (<code>declare -A</code>) are not portable to pure POSIX <code>sh</code>, but work across all modern Bash installations on Unix, Linux, and macOS with Bash 4+.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>What is authentication? Passwords, biometrics, SSO, and modern methods explained</title>
      <link>https://debod.id/en/posts/authentication-methods-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/authentication-methods-explained/</guid>
      <pubDate>Tue, 01 Sep 2026 00:00:00 GMT</pubDate>
      <category>security</category>
      <category>authentication</category>
      <category>security</category>
      <category>web</category>
      <description>Authentication is the process of verifying who you are. Learn how passwords, multi-factor authentication, biometrics, SSO, OAuth, and password managers work — and which methods offer the best protection for your application.</description>
      <content:encoded><![CDATA[<section><p>Authentication is the process of confirming that a user, device, or system is who they claim to be. It is distinct from <em>authorization</em>, which determines what an authenticated entity is allowed to do. Every time you log into an app, unlock your phone with a fingerprint, or sign a document digitally, you are going through an authentication step.</p>
<p>The most common authentication method for decades has been a username combined with a password — something you know. But passwords have well-documented weaknesses: they are easy to guess, reused across services, and phished at scale. Modern authentication systems layer multiple factors and use cryptographic protocols to reduce these risks.</p></section>
<section><h2 id="how-passwords-work-and-why-they-fail"><a href="#how-passwords-work-and-why-they-fail" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How passwords work and why they fail</h2><p>A password is a shared secret: both you and the system know it, and nobody else should. When you create an account, the system does not usually store your password in plain text. Instead, it runs the password through a <em>cryptographic hash function</em> and stores only the hash — a fixed-length string that cannot be reversed to reveal the original password.</p>
<p>When you log in, the system hashes what you typed and compares it to the stored hash. If they match, you are in. This means even if the database is leaked, the attacker only sees hashes, not passwords — provided the system uses a secure, slow hash function (such as bcrypt, Argon2, or scrypt) with a per-user random salt.</p>
<p>Despite this protection, passwords fail in practice for several reasons:</p>
<ul>
<li><strong>Reuse</strong> — people use the same password on multiple sites. When one site is breached, attackers try the same credentials on other services (credential stuffing).</li>
<li><strong>Predictability</strong> — common passwords like <code>123456</code> or <code>password</code> are guessed in seconds by dictionaries.</li>
<li><strong>Phishing</strong> — fake login pages steal credentials directly from real users.</li>
<li><strong>No cryptographic protection</strong> — a password alone proves nothing about the device or session context.</li>
</ul>
</section>
<section><h2 id="multi-factor-authentication-mfa"><a href="#multi-factor-authentication-mfa" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Multi-factor authentication (MFA)</h2><p>Multi-factor authentication adds one or more additional verification steps, so that compromising a single factor is not enough for access. The three classic factor categories are:</p>
<table>
<thead>
<tr>
<th>Factor type</th>
<th>What it is</th>
<th>Example</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Something you know</strong></td>
<td>Knowledge secret</td>
<td>Password, PIN</td>
</tr>
<tr>
<td><strong>Something you have</strong></td>
<td>Physical device</td>
<td>Phone, security key, smart card</td>
</tr>
<tr>
<td><strong>Something you are</strong></td>
<td>Biometric</td>
<td>Fingerprint, face, iris</td>
</tr>
</tbody></table>
<p>True MFA requires factors from at least two different categories. Using two passwords (something you know twice) is <em>two-step</em> but not <em>two-factor</em>.</p>
<h3 id="time-based-one-time-passwords-totp">Time-based one-time passwords (TOTP)</h3><p>TOTP is the most common second factor. Both your device and the server share a secret key, and both run it through a shared algorithm that produces a 6–8 digit code that changes every 30 seconds. You see the code in an authenticator app (Google Authenticator, Authy, Bitwarden Authenticator) or a hardware token.</p>
<p>The code is valid for a short window (usually 30 seconds, with a 1-step tolerance before and after), so even if an attacker sees one code, it expires before they can use it.</p>
<h3 id="sms-based-2fa-a-restricted-method">SMS-based 2FA: a restricted method</h3><p>SMS-based codes are sent by text message. Whether this channel is secure enough depends on your threat model: the US National Institute of Standards and Technology classifies SMS/voice codes sent over the public switched telephone network (PSTN) as a <em>restricted</em> authenticator under NIST SP 800-63B — permitted only with a documented risk assessment, user notification, and a migration plan, because of known weaknesses:</p>
<ul>
<li><strong>SIM swap attacks</strong> let an attacker convince or bribe a carrier to move a victim&#39;s phone number to a SIM the attacker controls, redirecting the text messages to them.</li>
<li><strong>SS7 signaling attacks</strong> can let an operator near the victim intercept SMS traffic.</li>
<li><strong>VoIP and reassignable numbers</strong> may be registered by an attacker.</li>
</ul>
<p>NIST guidance is clear that implementers of new systems should carefully consider alternatives. FIDO2/WebAuthn (hardware security keys) and TOTP authenticator apps are the recommended replacements for SMS-based OTP.</p>
<h3 id="hardware-security-keys-fido2-webauthn">Hardware security keys (FIDO2/WebAuthn)</h3><p>The strongest second factor for most users is a hardware security key — a physical USB or NFC device that implements the FIDO2 standard. WebAuthn is the web API that lets websites use FIDO2 without installing plugins.</p>
<p>When you authenticate with a security key, the device generates a cryptographic challenge-response pair using a private key stored in tamper-resistant hardware. The server stores only the public key. There is no shared secret to phish, intercept, or replay.</p>
<p>WebAuthn supports both second-factor (password + key) and passwordless (key only) flows. Major services including Google, GitHub, and Cloudflare support it.</p>
</section>
<section><h2 id="single-sign-on-sso"><a href="#single-sign-on-sso" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Single sign-on (SSO)</h2><p>Single sign-on lets users authenticate once and gain access to multiple applications without logging in again to each one. Instead of each app managing its own username and password database, a central <em>identity provider</em> (IdP) vouches for the user.</p>
<p>The most widely deployed SSO protocol is <strong>SAML 2.0</strong> (Security Assertion Markup Language), an XML-based standard used by enterprise IdPs such as Okta, Azure AD, and Active Directory Federation Services. SAML exchanges signed XML assertions between the IdP and the service provider.</p>
<p><strong>OAuth 2.0</strong> (often paired with <strong>OpenID Connect</strong>) is the dominant standard for modern web and mobile applications. OAuth 2.0 is an <em>authorization</em> framework — it lets a user grant one application permission to access resources on another without sharing credentials. OpenID Connect (OIDC) adds an <em>authentication</em> layer on top, providing an ID token that the relying party can use to identify the user.</p>
<p>The flow with OpenID Connect works like this:</p>
<ol>
<li>Your app redirects the user to the identity provider.</li>
<li>The user logs in at the IdP.</li>
<li>The IdP redirects back with an authorization code.</li>
<li>Your app exchanges the code for an ID token (user identity) and an access token (API access).</li>
<li>Your app validates the ID token and creates a session.</li>
</ol>
<p>OAuth 2.0 and OIDC eliminate password sharing between services, centralize authentication security, and let users manage their identity in one place.</p>
</section>
<section><h2 id="biometric-authentication"><a href="#biometric-authentication" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Biometric authentication</h2><p>Biometric authentication verifies identity using physical or behavioral characteristics: fingerprints, facial geometry, iris patterns, voice, or typing rhythm. These are <em>inherently tied to the person</em> — you cannot forget them or lose them the way you can a password or a phone.</p>
<p>Biometric systems work in two phases. <em>Enrollment</em> captures and stores a digital representation (a template) of the biometric trait. <em>Verification</em> captures a new sample and compares it to the stored template using a matching algorithm. Modern systems use machine learning models to handle variation (different angles, lighting, age-related changes) while rejecting spoofed inputs (photos, silicone fingerprints, deepfake videos).</p>
<p>Biometrics are stored as templates, not images. Even if a database is breached, the original biometric trait cannot be reconstructed from the template — though it may be possible to generate a functional replica given enough known samples, which is why biometric systems should be paired with a liveness detection mechanism.</p>
<p>The main limitation of biometrics as a standalone factor is that they cannot be revoked. If a fingerprint is compromised, you cannot issue a new one. This is why biometrics work best as a convenience layer paired with a PIN or password (something you know) in a multi-factor setup.</p>
</section>
<section><h2 id="password-managers"><a href="#password-managers" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Password managers</h2><p>A password manager stores credentials in an encrypted vault. The master password — the only one you need to remember — unlocks the vault. All other passwords are generated randomly, stored securely, and auto-filled by the manager.</p>
<p>The security of a password manager depends on the strength of the master password and the security of the vault encryption. Reputable managers (Bitwarden, 1Password, KeePass) use zero-knowledge architecture: the vault is encrypted client-side before being sent to the server. The server never sees your master password or unencrypted data.</p>
<p>Password managers solve the reuse and predictability problems: every account gets a unique, long, random password. Combined with a strong master password and MFA on the manager itself, they are one of the most practical improvements to personal authentication hygiene.</p>
</section>
<section><h2 id="passwordless-authentication"><a href="#passwordless-authentication" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Passwordless authentication</h2><p>The long-term direction of authentication is passwordless: replacing passwords entirely with cryptographic credentials tied to a device or biometric. FIDO2/WebAuthn enables true passwordless login, where the private key is generated on your device and the public key is registered with the service.</p>
<p>Microsoft, Apple, and Google have all moved toward passwordless authentication. Passkeys — a FIDO2 credential managed by the operating system or a password manager — can replace passwords on supported sites, removing the entire class of phishing and credential-theft attacks.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-is-the-difference-between-authentication-and-authorization">What is the difference between authentication and authorization?</h3><p>Authentication confirms who you are — it verifies that a user, device, or system is who it claims to be. Authorization determines what an authenticated entity is allowed to do. Authentication always comes first: you prove your identity before the system decides which resources you may access.</p>
<h3 id="what-is-the-strongest-form-of-multi-factor-authentication">What is the strongest form of multi-factor authentication?</h3><p>The strongest widely available second factor is a FIDO2 hardware security key, which uses WebAuthn. It stores a private key in tamper-resistant hardware, the server keeps only the public key, and there is no shared secret to phish, intercept, or replay. Time-based one-time passwords (TOTP) in an authenticator app are a strong and more convenient alternative.</p>
<h3 id="why-is-sms-2fa-considered-weak">Why is SMS 2FA considered weak?</h3><p>The US National Institute of Standards and Technology classifies SMS/voice codes sent over the public switched telephone network (PSTN) as a restricted authenticator under NIST SP 800-63B. SMS is vulnerable to SIM swap attacks, where an attacker convinces a carrier to move the victim&#39;s number to a SIM they control, and to SS7 signaling attacks that can intercept SMS traffic. FIDO2/WebAuthn and TOTP apps are recommended instead.</p>
<h3 id="can-biometric-authentication-be-hacked">Can biometric authentication be hacked?</h3><p>Biometrics are stored as templates, not images, and cannot be reconstructed from a stolen template. However, a functional replica can sometimes be generated from enough known samples, and — unlike a password — a biometric cannot be revoked if compromised. That is why biometrics work best as a convenience layer combined with a PIN or password in a multi-factor setup, ideally with liveness detection.</p>
</section>
<section><h2 id="related-guides"><a href="#related-guides" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Related guides</h2><ul>
<li><a href="/en/posts/jwt-authentication-nodejs/">JWT authentication in Node.js</a> — implement token-based authentication</li>
<li><a href="/en/posts/oauth-explained/">What is OAuth?</a> — the authorization framework behind SSO</li>
<li><a href="/en/posts/csrf-explained/">What is CSRF?</a> — protect authenticated sessions from cross-site attacks</li>
<li><a href="/en/posts/web-crypto-api/">Web Crypto API</a> — the browser APIs behind modern cryptographic operations</li>
</ul>
</section>]]></content:encoded>
    </item>
    <item>
      <title>REST API design best practices: resource naming, HTTP methods, and status codes</title>
      <link>https://debod.id/en/posts/rest-api-design-best-practices/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/rest-api-design-best-practices/</guid>
      <pubDate>Mon, 31 Aug 2026 00:00:00 GMT</pubDate>
      <category>programming</category>
      <category>rest</category>
      <category>api</category>
      <category>http</category>
      <category>programming</category>
      <category>web</category>
      <description>How to design a clean REST API: plural resource URLs, safe and idempotent HTTP methods, meaningful status codes, pagination, and error responses — with practical examples.</description>
      <content:encoded><![CDATA[<section><p>A well-designed REST API is easy to read, easy to use, and safe to retry. This guide covers the practices that matter most in practice: resource naming, choosing the right HTTP methods, returning meaningful status codes, paginating large collections, and structuring errors so clients can handle them automatically.</p></section>
<section><h2 id="design-resources-around-nouns-not-verbs"><a href="#design-resources-around-nouns-not-verbs" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Design resources around nouns, not verbs</h2><p>REST is a software architectural style that treats data and functionality as <em>resources</em> identified by URLs. The core principle is a uniform interface: resources are identified in requests by URIs, manipulated through representations (usually JSON), and described by self-contained messages.</p>
<p>In practice, this means two rules:</p>
<ul>
<li><strong>Use plural nouns for collections.</strong> A list of books lives at <code>/books</code>; a single book at <code>/books/42</code>. The collection is plural, the item is reached by appending its identifier.</li>
<li><strong>Let the HTTP method express the action</strong> — never put CRUD verbs in the URL. <code>/books</code> with <code>POST</code> creates, <code>/books/42</code> with <code>DELETE</code> deletes. Avoid URLs like <code>/createBook</code> or <code>/deleteBookById</code>: the method already says it, and verb-in-URL URLs break the uniform interface.</li>
</ul>
<p>Nesting is fine for sub-resources: <code>/books/42/reviews</code> returns the reviews of book 42. But avoid going deep — two levels is usually enough, and relationships beyond that are often better reached by query parameters.</p>
</section>
<section><h2 id="use-the-http-methods-correctly"><a href="#use-the-http-methods-correctly" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Use the HTTP methods correctly</h2><p>HTTP defines methods with precise semantics. Choosing the right one makes your API predictable and lets clients rely on safe and idempotent behavior.</p>
<table>
<thead>
<tr>
<th>Method</th>
<th>What it does</th>
<th>Safe</th>
<th>Idempotent</th>
</tr>
</thead>
<tbody><tr>
<td>GET</td>
<td>Retrieve a representation of the resource</td>
<td>Yes</td>
<td>Yes</td>
</tr>
<tr>
<td>HEAD</td>
<td>Same as GET but no response body</td>
<td>Yes</td>
<td>Yes</td>
</tr>
<tr>
<td>POST</td>
<td>Submit an entity, often creating a resource or causing side effects</td>
<td>No</td>
<td>No</td>
</tr>
<tr>
<td>PUT</td>
<td>Replace the entire target resource</td>
<td>No</td>
<td>Yes</td>
</tr>
<tr>
<td>PATCH</td>
<td>Apply a partial modification</td>
<td>No</td>
<td>No</td>
</tr>
<tr>
<td>DELETE</td>
<td>Delete the target resource</td>
<td>No</td>
<td>Yes</td>
</tr>
</tbody></table>
<p><strong>Safe</strong> means the request does not change server state. <strong>Idempotent</strong> means repeating the request produces the same result. GET, PUT, and DELETE are idempotent, so a client that loses the response to a network error can safely retry them. POST and PATCH are not, so a client should not blindly retry them — that is why creating a resource with <code>POST /books</code> is followed by a <code>201 Created</code>, and an update uses <code>PUT</code> (full replace) or <code>PATCH</code> (partial change) so retries stay harmless.</p>
</section>
<section><h2 id="use-status-codes-that-mean-something"><a href="#use-status-codes-that-mean-something" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Use status codes that mean something</h2><p>HTTP response status codes fall into five classes, and each code tells the client something specific:</p>
<ul>
<li><strong>2xx success:</strong> <code>200 OK</code> for a normal successful read or write, <code>201 Created</code> when a resource was created, <code>204 No Content</code> when an operation succeeded but there is nothing to return (for example, a successful <code>DELETE</code>).</li>
<li><strong>3xx redirection:</strong> the resource moved; clients should follow the <code>Location</code> header.</li>
<li><strong>4xx client error:</strong> the request was understood but cannot be fulfilled. <code>400 Bad Request</code> for malformed input, <code>401 Unauthorized</code> when authentication is missing or invalid, <code>403 Forbidden</code> when the client is authenticated but not allowed, <code>404 Not Found</code> when the resource does not exist, <code>409 Conflict</code> when the request clashes with the current state, <code>422 Unprocessable Entity</code> for well-formed requests that fail validation, and <code>429 Too Many Requests</code> when rate-limited.</li>
<li><strong>5xx server error:</strong> <code>500 Internal Server Error</code> for unexpected failures and <code>503 Service Unavailable</code> when the server is overloaded or down for maintenance.</li>
</ul>
<p>Using the right code is more than cosmetics: automated clients branch on these codes, and caches and proxies behave differently based on them.</p>
</section>
<section><h2 id="paginate-large-collections"><a href="#paginate-large-collections" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Paginate large collections</h2><p>A GET on a large collection should not return every row. The two most common approaches are offset-based and cursor-based pagination.</p>
<pre><code class="language-http">GET /books?limit=20&amp;offset=0
</code></pre>
<p>Offset pagination (<code>offset</code> and <code>limit</code>) is simple and lets clients jump to any page, but it gets slow and drifting on large, frequently-changing datasets because rows shift as they are added or removed. Cursor-based pagination uses an opaque token to keep the position stable:</p>
<pre><code class="language-http">GET /books?limit=20&amp;cursor=abc123
</code></pre>
<p>The response includes a <code>next_cursor</code> (and possibly <code>prev_cursor</code>) field that the client passes back to fetch the next page. Regardless of approach, include the total or pagination metadata in the response so clients can render paging controls.</p>
</section>
<section><h2 id="return-consistent-structured-errors"><a href="#return-consistent-structured-errors" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Return consistent, structured errors</h2><p>Clients need enough information to react without parsing human prose. A helpful error response carries the status code plus a consistent JSON shape:</p>
<pre><code class="language-json">{
  &quot;error&quot;: {
    &quot;code&quot;: &quot;validation_failed&quot;,
    &quot;message&quot;: &quot;title is required&quot;,
    &quot;details&quot;: { &quot;field&quot;: &quot;title&quot;, &quot;reason&quot;: &quot;missing&quot; }
  }
}
</code></pre>
<p>Keep the structure the same on every endpoint, use a stable machine-readable <code>code</code> (not just a translated message), and reserve <code>message</code> for a developer-facing explanation. This makes retries, logging, and client-side handling uniform.</p>
</section>
<section><h2 id="add-api-keys-and-versioning-early"><a href="#add-api-keys-and-versioning-early" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Add API keys and versioning early</h2><p>Public APIs authenticate clients with an <strong>API key</strong> — a secret string sent in a request header. Treat keys like passwords: never store them in client-side code or public repositories. The server uses the key to identify you and apply rate limits; a missing or invalid key returns <code>401 Unauthorized</code>, and exceeding a limit returns <code>429 Too Many Requests</code>.</p>
<p>Version your API from the start, because changing an endpoint later breaks every client. A common pattern is a path prefix such as <code>/v1/books</code>, which lets old and new versions live side by side while clients migrate.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-makes-a-url-a-good-rest-resource-url">What makes a URL a good REST resource URL?</h3><p>Use plural nouns for collections (<code>/books</code>), append the identifier for a single item (<code>/books/42</code>), keep names lowercase with hyphens between words, and let HTTP methods express the action instead of verbs inside the URL. Keep nesting shallow — one or two levels.</p>
<h3 id="should-i-use-put-or-patch-to-update-a-resource">Should I use PUT or PATCH to update a resource?</h3><p>Use <code>PUT</code> to replace the entire resource: it is idempotent, so a retry is safe. Use <code>PATCH</code> for a partial update of only the fields you pass. Because <code>PATCH</code> is not idempotent, be careful about retrying it after a network failure.</p>
<h3 id="how-do-i-choose-the-right-status-code">How do I choose the right status code?</h3><p>Pick from the class that matches the outcome: <code>2xx</code> for success (create → <code>201</code>, no content → <code>204</code>), <code>4xx</code> for client errors (bad input → <code>400</code>, unauthenticated → <code>401</code>, forbidden → <code>403</code>, missing → <code>404</code>, conflict → <code>409</code>, validation → <code>422</code>, rate-limited → <code>429</code>), and <code>5xx</code> for server failures (<code>500</code> unexpected, <code>503</code> overloaded/maintenance).</p>
<h3 id="what-is-idempotency-and-why-does-it-matter-for-retries">What is idempotency and why does it matter for retries?</h3><p>An idempotent request produces the same result no matter how many times it is repeated. <code>GET</code>, <code>PUT</code>, and <code>DELETE</code> are idempotent, so a client can safely retry them after a network error without causing duplicate side effects. <code>POST</code> and <code>PATCH</code> are not, so clients should not blindly retry them.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>What is caching? Browser, CDN, server, and database caching explained</title>
      <link>https://debod.id/en/posts/caching-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/caching-explained/</guid>
      <pubDate>Mon, 31 Aug 2026 00:00:00 GMT</pubDate>
      <category>performance</category>
      <category>cache</category>
      <category>performance</category>
      <category>web</category>
      <category>cdn</category>
      <category>database</category>
      <description>How caching works at every layer of the web — browser cache, CDN edge cache, server-side cache, and database cache — and when each one matters for speed and scalability.</description>
      <content:encoded><![CDATA[<section><p>Caching is the practice of storing a copy of data so that future requests can be served faster. Without caching, every request for the same piece of information would require recomputing it or reading it from the original source — which is slower and more expensive. Caching appears at every layer of the modern web stack, from your browser to the database, and understanding how each layer works helps you build faster, more scalable applications.</p></section>
<section><h2 id="why-caching-exists"><a href="#why-caching-exists" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Why caching exists</h2><p>Accessing data from cache is orders of magnitude faster than recomputing or re-reading it. A cache hit — a request served from cache — avoids a network round-trip, reduces database load, and delivers a response in microseconds. The more cache hits a system gets, the less load it places on its backing store and the more users it can handle with the same infrastructure.</p>
<p>Caches work because of <em>locality of reference</em>: programs tend to access the same data repeatedly (temporal locality) and data that is nearby in storage is often accessed together (spatial locality). By predicting which data will be reused, caches eliminate redundant work.</p>
</section>
<section><h2 id="the-four-layers-of-web-caching"><a href="#the-four-layers-of-web-caching" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The four layers of web caching</h2><h3 id="1-browser-cache-private-cache">1. Browser cache (private cache)</h3><p>The browser cache is a private store on your device. When your browser downloads a file — an image, a stylesheet, a JavaScript file, a font — it keeps a copy. On your next visit, the browser reuses that copy instead of downloading it again.</p>
<p>Each cached entry has a lifetime, controlled by HTTP headers:</p>
<ul>
<li><strong><code>Cache-Control: max-age=31536000</code></strong> — the server says this file is good for one year. Within that window, the browser uses the cached copy without asking.</li>
<li><strong><code>Cache-Control: no-store</code></strong> — the server forbids caching; the browser never stores this response.</li>
<li><strong><code>Cache-Control: no-cache</code></strong> — the browser may store the file, but must ask the server (&quot;is this still valid?&quot;) before using it.</li>
<li><strong><code>ETag</code> / <code>Last-Modified</code></strong> — validation tokens. When the browser revalidates, the server answers <code>304 Not Modified</code> if the file has not changed, avoiding a full re-download.</li>
</ul>
<p>For more detail, see <a href="/en/posts/browser-cache-explained/">What is a browser cache?</a>.</p>
<h3 id="2-cdn-edge-cache-shared-cache">2. CDN edge cache (shared cache)</h3><p>A content delivery network (CDN) places cache servers at dozens or hundreds of locations worldwide. When a visitor requests a file, the CDN serves it from the nearest edge server rather than your origin server. For the first visitor in a region, the CDN fetches the file from the origin and caches a copy at the edge. All subsequent visitors in that region get the cached copy.</p>
<p>CDNs dramatically reduce latency for global audiences because the cache is physically close to users. They also reduce origin load — the CDN handles most requests, so your server only receives cache misses.</p>
<p>CDNs use HTTP caching headers just like the browser cache, but they operate on shared infrastructure. Cloudflare, Fastly, and Akamai are prominent CDN providers.</p>
<p>For more detail, see <a href="/en/posts/cdn-explained/">What is a CDN?</a>.</p>
<h3 id="3-server-side-cache-application-cache">3. Server-side cache (application cache)</h3><p>Server-side caching stores the results of expensive operations — database queries, API calls, template rendering — in memory so they can be reused without recomputing. Unlike the browser and CDN caches (which serve pre-computed HTTP responses), a server-side cache sits inside your application logic.</p>
<p>Common server-side caches:</p>
<ul>
<li><strong>Object cache</strong> — stores results of database queries or complex computations. Redis and Memcached are popular in-memory key-value stores used for this purpose. Redis (Remote Dictionary Server) is an in-memory key-value database, released in 2009, used as a distributed cache and message broker. Memcached, released in 2003, is a general-purpose distributed memory caching system.</li>
<li><strong>Full-page cache</strong> — stores complete HTML pages (or fragments) so they can be served without running application code or querying the database.</li>
<li><strong>OPcache</strong> (PHP) — caches precompiled script bytecode, eliminating the overhead of parsing and compiling PHP on every request.</li>
<li><strong>CDN as a cache</strong> — a reverse proxy or CDN in front of your application server can cache entire pages or API responses.</li>
</ul>
<p>Server-side caches are invalidated when the underlying data changes. A product page cache, for example, must be cleared when the product price changes. Strategies include time-based expiration (TTL), event-driven invalidation, and cache tagging (grouping keys by content type so an entire category can be flushed at once).</p>
<h3 id="4-database-cache">4. Database cache</h3><p>Databases maintain internal caches of query results and recently accessed pages. The database buffer pool holds frequently read data pages in memory; indexes reduce the amount of data that must be scanned on each query.</p>
<p>For more detail, see <a href="/en/posts/database-index-explained/">What is a database index?</a>.</p>
<p>Beyond the database&#39;s own cache, external caches like Redis sit between the application and the database to absorb repeated queries. A product catalog that is read ten thousand times per minute does not need ten thousand database queries — one query cached in Redis for a few seconds covers all of them.</p>
</section>
<section><h2 id="cache-invalidation-the-hard-problem"><a href="#cache-invalidation-the-hard-problem" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Cache invalidation: the hard problem</h2><p>Caching is only useful if the cached data stays current. Cache invalidation is the process of removing or updating stale entries. The two main strategies:</p>
<ul>
<li><strong>TTL (time-to-live)</strong> — set an expiration time. Simple, but data can be stale until the TTL expires.</li>
<li><strong>Event-driven invalidation</strong> — when data changes, your application explicitly removes the corresponding cache entry. This keeps data fresh but requires more infrastructure logic.</li>
</ul>
<p>The famous quip from Phil Karlton — &quot;there are only two hard things in computer science: cache invalidation and naming things&quot; — captures why this remains one of the most challenging problems in distributed systems.</p>
</section>
<section><h2 id="caching-and-compression-work-together"><a href="#caching-and-compression-work-together" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Caching and compression work together</h2><p>Caching and compression are often deployed together. A CDN can cache a gzipped or Brotli-compressed response and serve it to all subsequent visitors, combining the speed of cache hits with the bandwidth savings of compression. For more, see <a href="/en/posts/web-compression-explained/">Web compression: how gzip and Brotli shrink the web</a>.</p>
</section>
<section><h2 id="when-to-use-which-cache"><a href="#when-to-use-which-cache" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>When to use which cache</h2><table>
<thead>
<tr>
<th>Situation</th>
<th>Cache layer</th>
</tr>
</thead>
<tbody><tr>
<td>Static assets (images, fonts, CSS, JS)</td>
<td>Browser + CDN</td>
</tr>
<tr>
<td>Frequently read database queries</td>
<td>Server-side (Redis, Memcached)</td>
</tr>
<tr>
<td>Rendered HTML pages</td>
<td>CDN or reverse proxy</td>
</tr>
<tr>
<td>Full-page app fragments</td>
<td>Server-side cache</td>
</tr>
<tr>
<td>Reverse proxy and load balancing</td>
<td>Server cache (Nginx, Varnish)</td>
</tr>
</tbody></table>
<p>For more on reverse proxies and caching, see <a href="/en/posts/nginx-reverse-proxy-guide/">How to set up a reverse proxy with Nginx</a>.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-is-the-difference-between-a-cache-hit-and-a-cache-miss">What is the difference between a cache hit and a cache miss?</h3><p>A cache hit occurs when the requested data is found in the cache — the request is served from cache with no backend access. A cache miss occurs when the data is not in the cache — the system must fetch or compute the data from the original source, which is slower. The cache hit rate (percentage of requests served from cache) is a key performance metric for any caching system.</p>
<h3 id="how-does-redis-differ-from-memcached">How does Redis differ from Memcached?</h3><p>Both are in-memory key-value stores used for caching, but they differ in capabilities. Redis supports richer data structures (strings, lists, sets, sorted sets, hashes, bitmaps, hyperloglogs, geospatial indexes, and streams) and can optionally persist data to disk. Memcached is simpler — it stores only string values and has no persistence. Redis also supports replication, clustering, and Lua scripting. Memcached&#39;s simplicity can be an advantage when raw speed and memory efficiency are the only goals. Redis is the more common choice for modern applications.</p>
<h3 id="what-is-cache-invalidation">What is cache invalidation?</h3><p>Cache invalidation is the process of removing or updating cached entries when the underlying data changes. Without invalidation, a cache serves stale (outdated) data. The two main approaches are TTL-based expiration (automatic removal after a set time) and event-driven invalidation (explicit removal when data changes in the database or backing store). Event-driven invalidation is more complex but provides fresher data.</p>
<h3 id="can-caching-cause-problems">Can caching cause problems?</h3><p>Yes. A misconfigured cache that serves stale data can make a site appear broken — users see old prices, old content, or old designs. Cache stampedes occur when many requests arrive at once after a cache expires, overwhelming the origin server with simultaneous requests for the same data. Cache poisoning (injecting malicious data into a cache) is a security concern in shared cache environments. Careful TTL settings, cache busting, and event-driven invalidation mitigate these risks.</p>
</section>
<section><h2 id="summary"><a href="#summary" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Summary</h2><p>Caching is the practice of storing copies of data at every layer of the web stack — browser, CDN, application server, and database — so that repeat requests can be served faster and with less load on the backing store. Each layer serves a different purpose: the browser cache speeds up repeat visits on the same device, a CDN brings content close to users worldwide, a server-side cache reduces database queries, and a database cache minimizes disk reads. The challenge in any caching system is keeping the cache in sync with the source of truth — a problem known as cache invalidation.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>What is a Regular Expression (regex)? A practical guide</title>
      <link>https://debod.id/en/posts/regular-expression-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/regular-expression-explained/</guid>
      <pubDate>Sun, 30 Aug 2026 00:00:00 GMT</pubDate>
      <category>programming</category>
      <category>programming</category>
      <category>javascript</category>
      <category>developer-tools</category>
      <description>A regular expression (regex or regexp) is a sequence of characters that forms a search pattern for matching and manipulating text. Built on a formal mathematical foundation and common to almost every programming language and tool, regex lets you validate input, find and replace text, extract data, and transform strings with a compact, declarative syntax. This guide explains what regex is, its core syntax, how it is implemented, and how to use it safely.</description>
      <content:encoded><![CDATA[<section><p>A regular expression (regex or regexp) is a sequence of characters that forms a search pattern for matching and manipulating text. Regex lets you validate input, find and replace text, extract data, and transform strings with a compact, declarative syntax rather than writing long procedural search logic. The concept was formalized in the 1950s by the mathematician Stephen Kleene for the theory of regular languages, and it came into common use with Unix text-processing utilities; today nearly every programming language and many tools support some dialect of it.</p></section>
<section><h2 id="what-problems-does-regex-solve"><a href="#what-problems-does-regex-solve" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What problems does regex solve?</h2><p>Without regex, checking whether a phone number is valid, extracting every URL from a document, or renaming a set of files by pattern requires multiple statements, loops, and hand-written comparison logic. A regex collapses that into one pattern:</p>
<ul>
<li><strong>Validate input</strong> — check whether an email, phone number, or postal code matches an expected shape.</li>
<li><strong>Find and extract</strong> — pull every log line, ID, or URL matching a pattern out of a larger text.</li>
<li><strong>Search and replace</strong> — rename or transform text consistently, using captured groups to reuse parts of a match.</li>
<li><strong>Split text</strong> — break a string into pieces at any point that matches a pattern.</li>
</ul>
<p>Because it is declarative, the same pattern can be reused across languages and tools once you account for dialect differences.</p>
</section>
<section><h2 id="core-regex-syntax"><a href="#core-regex-syntax" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Core regex syntax</h2><p>A regex is built from literal characters and <strong>metacharacters</strong> that carry special meaning:</p>
<ul>
<li><strong>Literals</strong> — ordinary characters match themselves. <code>/abc/</code> matches the exact sequence <code>abc</code> in order.</li>
<li><strong>Character classes</strong> — <code>[abc]</code> matches any one of <code>a</code>, <code>b</code>, or <code>c</code>; <code>[^abc]</code> matches any character that is not one of those; a range such as <code>[a-z]</code> matches any lowercase letter.</li>
<li><strong>Shorthand classes</strong> — <code>\d</code> matches any digit, <code>\w</code> any word character, <code>\s</code> any whitespace; their uppercase forms (<code>\D</code>, <code>\W</code>, <code>\S</code>) match the negation.</li>
<li><strong>Quantifiers</strong> — <code>*</code> means &quot;zero or more of the preceding item&quot;, <code>+</code> means &quot;one or more&quot;, <code>?</code> means &quot;zero or one&quot;, and <code>{n,m}</code> means &quot;between n and m&quot;.</li>
<li><strong>Anchors and assertions</strong> — <code>^</code> anchors to the start of the string (or line with the multiline flag), <code>$</code> to the end; <code>\b</code> marks a word boundary; look-ahead and look-behind assert conditions without consuming characters.</li>
<li><strong>Groups and alternation</strong> — parentheses <code>( )</code> group a subpattern and capture its match for later reuse (backreference); <code>|</code> means alternation (&quot;or&quot;).</li>
</ul>
<p>For example, the pattern <code>/^a.c$/</code> matches a string that starts with <code>a</code>, then any one character, then ends with <code>c</code> — like <code>abc</code> or <code>aXc</code>, but not <code>ac</code> or <code>abbc</code>.</p>
</section>
<section><h2 id="two-major-families-posix-and-perl-compatible"><a href="#two-major-families-posix-and-perl-compatible" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Two major families: POSIX and Perl-compatible</h2><p>Regex dialects broadly fall into two families:</p>
<ul>
<li><strong>POSIX</strong> — the POSIX standard defines Basic Regular Expressions (BRE) and Extended Regular Expressions (ERE). ERE adds <code>+</code>, <code>?</code>, and <code>|</code>, and treats <code>( )</code> and <code>{ }</code> as metacharacters without escaping; BRE requires them to be escaped (<code>\(</code> and <code>\{</code>). GNU tools like <code>grep</code> expose these modes (<code>grep -E</code> for ERE, <code>grep -G</code> for BRE by default).</li>
<li><strong>Perl-compatible (PCRE)</strong> — beginning in 1997, Philip Hazel built PCRE to closely mimic Perl&#39;s richer regex features. It added non-greedy matching, look-around assertions, backreferences, named groups, and recursion. Because of that expressive power, Perl-like syntax became a de facto standard adopted by JavaScript, Java, Python, Ruby, PHP, and many other languages and tools.</li>
</ul>
<p>The differences matter in practice: a pattern that is valid in one dialect may fail or behave differently in another, so always test patterns in the specific environment you are working in.</p>
</section>
<section><h2 id="using-regex-in-javascript"><a href="#using-regex-in-javascript" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Using regex in JavaScript</h2><p>In JavaScript, a regex is an object. You create it with a literal (a pattern between slashes) or with the <code>RegExp</code> constructor:</p>
<ul>
<li><code>const re = /ab+c/;</code> — a literal, compiled once when the script loads (faster for fixed patterns).</li>
<li><code>const re = new RegExp(&quot;ab+c&quot;);</code> — the constructor, compiled at runtime (useful when the pattern comes from user input or changes).</li>
</ul>
<p>You then use it with the <code>RegExp</code> methods <code>test()</code> and <code>exec()</code>, or with the <code>String</code> methods <code>match()</code>, <code>matchAll()</code>, <code>replace()</code>, <code>replaceAll()</code>, <code>search()</code>, and <code>split()</code>. To test whether a string contains a match, use <code>test()</code>:</p>
<pre><code class="language-javascript">const emailLike = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
emailLike.test(&quot;hello@debod.id&quot;); // true
</code></pre>
<p>Flags tune behavior: <code>g</code> for a global search, <code>i</code> for case-insensitive, <code>m</code> to make <code>^</code> and <code>$</code> match per line, <code>s</code> to let <code>.</code> match newlines, and <code>u</code> to treat the pattern as Unicode code points (enabling Unicode property escapes like <code>\p{L}</code>).</p>
</section>
<section><h2 id="pitfalls-and-safe-use"><a href="#pitfalls-and-safe-use" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Pitfalls and safe use</h2><p>Regex is powerful but easy to misuse:</p>
<ul>
<li><strong>Catastrophic backtracking</strong> — patterns with nested quantifiers over overlapping alternatives can take exponential time on certain inputs, a classic denial-of-service vector. Keep patterns simple, and avoid deeply nested quantifiers when matching untrusted input.</li>
<li><strong>Dialect differences</strong> — escaping rules differ; in POSIX BRE, for example, <code>(</code> and <code>{</code> must be escaped to be metacharacters, while in ERE and Perl-like syntax they are metacharacters by default.</li>
<li><strong>Escape special characters</strong> — when you need a literal <code>*</code>, <code>.</code>, or <code>/</code>, escape it with a backslash (<code>/a\*b/</code>), or wrap it in a character class (<code>/a[*]b/</code>). In a <code>RegExp</code> string literal, backslashes must themselves be escaped so the pattern survives string escaping.</li>
<li><strong>Test before shipping</strong> — because dialects vary, always validate a pattern against real inputs in the exact tool or language, not just in an online tester.</li>
</ul>
<p>When you need to validate input, prefer the simplest pattern that works, combine it with normalization at the boundary (trimming, length checks), and treat untrusted strings as data — never execute user-supplied patterns as code.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-is-a-regular-expression-regex">What is a regular expression (regex)?</h3><p>A regular expression, or regex for short, is a sequence of characters that forms a search pattern for matching text. It lets you validate input, find and replace strings, extract data, and transform text with a compact declarative syntax instead of writing long search logic by hand.</p>
<h3 id="where-are-regular-expressions-used">Where are regular expressions used?</h3><p>Regular expressions appear in almost every programming language (JavaScript, Python, Java, Ruby, PHP, and more) and in many developer tools, including code editors for find-and-replace, command-line tools like <code>grep</code> and <code>sed</code>, and API testing and issue-tracking platforms. The concept dates to the 1950s with Stephen Kleene&#39;s work on regular languages and became common through Unix text-processing utilities.</p>
<h3 id="what-is-the-difference-between-posix-and-pcre-regex">What is the difference between POSIX and PCRE regex?</h3><p>POSIX defines Basic (BRE) and Extended (ERE) regex, where ERE adds <code>+</code>, <code>?</code>, and <code>|</code> and makes <code>( )</code> and <code>{ }</code> metacharacters without escaping. PCRE (Perl Compatible Regular Expressions), started in 1997, closely mimics Perl&#39;s richer feature set, adding non-greedy matching, look-around assertions, backreferences, named groups, and recursion, and it became a de facto standard adopted by many modern languages.</p>
<h3 id="why-should-i-escape-special-characters-in-a-regex">Why should I escape special characters in a regex?</h3><p>Metacharacters like <code>*</code>, <code>.</code>, <code>?</code>, <code>(</code>, and <code>/</code> carry special meaning, so matching them literally requires a backslash (<code>/a\*b/</code>) or a character class (<code>/a[*]b/</code>). In JavaScript&#39;s <code>RegExp(&quot;...&quot;)</code> constructor, backslashes must also be escaped to survive string escaping — otherwise the pattern changes meaning.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>What is a message queue? How message queues work and when to use them</title>
      <link>https://debod.id/en/posts/message-queues-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/message-queues-explained/</guid>
      <pubDate>Sun, 30 Aug 2026 00:00:00 GMT</pubDate>
      <category>architecture</category>
      <category>devops</category>
      <category>async</category>
      <category>microservices</category>
      <category>programming</category>
      <description>A message queue is a software component that lets services exchange data asynchronously. Learn how queues, topics, publishers and consumers work, and when they are worth the complexity.</description>
      <content:encoded><![CDATA[<section><p>Message queues are one of the most important building blocks of modern distributed systems, yet many developers only meet them after a service starts failing under load. This guide explains what a message queue is, how publishers, consumers and brokers fit together, and — most importantly — when adding one actually helps versus when it is just extra complexity.</p></section>
<section><h2 id="what-a-message-queue-is"><a href="#what-a-message-queue-is" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What a message queue is</h2><p>A message queue is a software-engineering component used for communication between processes or between threads within a process. As Wikipedia describes it, message queues are &quot;typically used for inter-process communication (IPC) or for inter-thread communication within the same process.&quot; They use a queue — a first-in, first-out (FIFO) data structure — for messaging, which is the passing of control or content from one component to another.</p>
<p>The key idea is that the sending and receiving parties do not need to interact at the same time. A sender places a message onto the queue, and the queue stores it until a recipient is ready to retrieve it. This is the defining trait that separates a message queue from a direct, synchronous call: <strong>the sender and receiver are decoupled in time</strong> — neither has to wait for the other to be available.</p>
<p>A message queue is typically one part of a larger <strong>message-oriented middleware</strong> (MOM) system. Most messaging systems support both the publisher/subscriber model and the plain message queue model in their APIs.</p>
</section>
<section><h2 id="core-concepts-producer-consumer-broker-queue"><a href="#core-concepts-producer-consumer-broker-queue" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Core concepts: producer, consumer, broker, queue</h2><p>The model has a small set of moving parts that repeat across every implementation:</p>
<ul>
<li><strong>Producer (publisher)</strong> — any component that creates a message and sends it to the queue. It does not care who eventually reads the message.</li>
<li><strong>Consumer (subscriber)</strong> — any component that reads and processes messages from the queue.</li>
<li><strong>Queue</strong> — the buffer that holds messages between production and consumption. Messages are generally consumed in order (FIFO) and removed once a consumer acknowledges them.</li>
<li><strong>Broker</strong> — the software that runs the queues, routes messages, and coordinates producers and consumers. RabbitMQ, Apache Kafka, and cloud queues are all brokers.</li>
<li><strong>Topic / exchange</strong> — a named channel that allows the publish/subscribe pattern, where many consumers can each receive a copy of a message.</li>
</ul>
<p>The flow is simple: a producer sends a message to the broker, the broker places it on the named queue (or routes it to a topic), and a consumer pulls it off and processes it. Because the broker sits between producer and consumer, neither side needs to know the other&#39;s address, availability, or even identity.</p>
</section>
<section><h2 id="two-messaging-models"><a href="#two-messaging-models" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Two messaging models</h2><p>Most message systems implement one or both of two patterns:</p>
<p><strong>Point-to-point (message queue model).</strong> A message is consumed by exactly one receiver. If several consumers are attached to a queue, the broker usually delivers each message to a single one of them, dividing the load. This is the classic &quot;work queue&quot; — ideal for distributing jobs across a pool of workers.</p>
<p><strong>Publish/subscribe (pub/sub).</strong> Messages are published to a topic, and every subscriber that is registered for that topic receives its own copy. This model is great for broadcasting events that many independent systems want to react to — for example, &quot;a new user signed up&quot; delivered to an email service, an analytics pipeline, and a search index at the same time.</p>
</section>
<section><h2 id="why-message-queues-help"><a href="#why-message-queues-help" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Why message queues help</h2><p>The main benefits all flow from decoupling — breaking the tight, synchronous link between components:</p>
<ul>
<li><strong>Resilience to outages.</strong> If a consumer service is down, messages pile up in the queue instead of failing the caller. When the service recovers, it processes the backlog. Messages are not &quot;lost&quot; just because a component was temporarily unavailable.</li>
<li><strong>Load leveling / buffering.</strong> Producers can push work as fast as they like; consumers pull at their own pace. A burst of traffic no longer forces the backend to scale instantly — the queue evens out the spikes.</li>
<li><strong>Scalability.</strong> You can add more consumers to a queue to process work in parallel, without touching the producer at all.</li>
<li><strong>Asynchronous processing.</strong> The producer does not block waiting for the result; it fires a message and continues. Long-running or slow work (emails, image processing, notifications, third-party calls) moves out of the request path, so user-facing latency drops.</li>
<li><strong>Loose coupling between teams/architecture.</strong> Services exchange messages rather than calling each other&#39;s internals, which makes it easier to add, replace, or scale one service without rewriting its consumers.</li>
</ul>
</section>
<section><h2 id="when-a-message-queue-is-the-wrong-tool"><a href="#when-a-message-queue-is-the-wrong-tool" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>When a message queue is the wrong tool</h2><p>A queue is not free — it brings a broker to run and monitor, plus new failure modes. It is often the wrong choice when:</p>
<ul>
<li><strong>You need a synchronous response.</strong> A queue is asynchronous by design. If the caller must know the result immediately (e.g. a login check), a direct request/response call or an HTTP/RPC call is the right pattern.</li>
<li><strong>The traffic simply does not need it.</strong> A single service with a single consumer and modest load gains little from a broker — you have added an always-on component and serialization overhead for no benefit.</li>
<li><strong>You do not need durability.</strong> Queues persist messages so they survive an outage; if your use case tolerates dropping data, that durability is wasted cost and complexity.</li>
<li><strong>Operational cost is a concern.</strong> A broker is a stateful service that needs capacity planning, monitoring, backups and upgrades. On a small team that might be more overhead than the decoupling is worth.</li>
</ul>
<p>The common rule of thumb: reach for a queue when you have <em>multiple</em> services that must produce or consume work asynchronously, when you need to survive a consumer outage, or when you want to decouple heavy work from the user-facing request path. For a small monolith or a single request/response service, skip it.</p>
</section>
<section><h2 id="a-closer-look-at-the-two-most-common-brokers"><a href="#a-closer-look-at-the-two-most-common-brokers" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>A closer look at the two most common brokers</h2><p>Two open-source brokers dominate the conversation, and picking the right one is mostly about the messaging pattern, not a popularity contest:</p>
<p><strong>RabbitMQ</strong> is a mature, general-purpose broker that implements the AMQP protocol. It is built around <strong>queues and exchanges</strong>, supports sophisticated routing (direct, topic, fanout, headers), and excels at point-to-point work queues and complex routing. It is a strong default when your dominant pattern is pushing jobs to workers with flexible routing rules. RabbitMQ is licensed under the Mozilla Public License 2.0.</p>
<p><strong>Apache Kafka</strong> is a distributed event-streaming platform designed for high-throughput, ordered, replayable streams. It is organized around <strong>topics divided into partitions</strong>, keeps messages for a configurable retention period so consumers can replay them, and is built to scale to enormous volumes across many nodes. It shines in event-driven architectures, log aggregation, and stream processing (with Kafka Streams). Kafka is licensed under the Apache License 2.0.</p>
<p>The short version: RabbitMQ is often easier to operate and better for classic work queues with routing; Kafka is the choice when you need high throughput, ordered streams, replay, and event-driven scale.</p>
</section>
<section><h2 id="managed-cloud-queues"><a href="#managed-cloud-queues" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Managed cloud queues</h2><p>Every major cloud provider offers a managed queue service, which removes the operational burden of running a broker:</p>
<ul>
<li><strong>Amazon Simple Queue Service (SQS)</strong> — a fully managed, point-to-point message queue on AWS.</li>
<li><strong>Google Cloud Pub/Sub</strong> — a pub/sub messaging service with push/pull delivery.</li>
<li><strong>Azure Queue Storage / Service Bus</strong> — Microsoft&#39;s queue and pub/sub messaging.</li>
<li><strong>Cloudflare Queues</strong> — a managed queue that works with Cloudflare Workers, using a publish/consume model where messages stay until acknowledged.</li>
</ul>
<p>Managed queues trade the control and low cost of self-hosting for zero operational overhead — you get a broker without running one. For most teams, that is the right trade-off until scale or a specific feature forces them to self-host.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="is-a-message-queue-the-same-as-a-database">Is a message queue the same as a database?</h3><p>No. A queue is optimized for moving messages between components with FIFO ordering and short-lived storage, while a database is optimized for storing, querying and managing long-lived structured data. Some queues keep messages for a long retention period (Kafka can), but a queue is not a substitute for a database — it is a transport and buffering mechanism between producers and consumers, not a place to store and query your data.</p>
<h3 id="what-is-the-difference-between-a-message-queue-and-pub-sub">What is the difference between a message queue and pub/sub?</h3><p>In the plain message queue (point-to-point) model, each message is consumed by exactly one receiver — it is a work distribution mechanism. In the publish/subscribe model, a message published to a topic is delivered to every subscriber, so many independent services can each react to the same event. Most brokers support both; the choice depends on whether you want one worker per message or every interested service to get a copy.</p>
<h3 id="do-message-queues-guarantee-delivery-only-once">Do message queues guarantee delivery only once?</h3><p>Message delivery guarantees depend on the broker and configuration. Many queues offer &quot;at least once&quot; delivery, which means a message might be delivered more than once — so consumers must be <strong>idempotent</strong> (able to handle processing the same message twice) if duplicates matter. Exact &quot;exactly once&quot; processing requires additional mechanisms (deduplication, transactional consumers, idempotent operations). You should design consumers that can tolerate at-least-once delivery rather than assume a message arrives exactly once.</p>
<h3 id="does-using-a-message-queue-make-my-system-slower">Does using a message queue make my system slower?</h3><p>A message queue moves work out of the synchronous request path, which usually makes user-facing requests <em>faster</em> — the caller just fires a message instead of waiting for slow work (email, image processing, third-party calls) to finish. But the message itself travels through a broker, which adds a small amount of network and serialization latency compared to a direct in-process call. The benefit is decoupling and resilience, not raw speed: you trade a little per-message overhead for much better failure tolerance and scalability.</p>
</section>
<section><h2 id="summary"><a href="#summary" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Summary</h2><p>A message queue is a software component that lets producers and consumers exchange data asynchronously through a broker, decoupling them in time and space. It gives you resilience to outages, load leveling, easy scalability, and lower user-facing latency by moving heavy work out of the request path. It is the right tool when you have multiple services that must exchange asynchronous work or survive a consumer outage — and the wrong tool when you need a synchronous response or simply do not yet have the traffic to justify running a broker. Choose the model (point-to-point vs pub/sub) and the broker (general-purpose RabbitMQ vs high-throughput Kafka, or a managed cloud queue) to match the pattern you actually have.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>What is a headless CMS? How headless content management works</title>
      <link>https://debod.id/en/posts/headless-cms-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/headless-cms-explained/</guid>
      <pubDate>Sun, 30 Aug 2026 00:00:00 GMT</pubDate>
      <category>web</category>
      <category>web-development</category>
      <category>api</category>
      <category>hosting</category>
      <description>A headless CMS keeps content separate from the site that shows it, exposing it through an API. Here&apos;s how headless CMS works, how it differs from a traditional CMS, and when to choose it.</description>
      <content:encoded><![CDATA[<section><p>A headless content management system, or headless CMS, is a back-end-only web content management system that acts primarily as a <strong>content repository</strong>. It makes content accessible through an API so it can be displayed on any device, without a built-in front end or presentation layer. Editors manage content in one place, and developers pull it into websites, mobile apps, or anything else via API.</p></section>
<section><h2 id="how-a-headless-cms-is-different-from-a-traditional-cms"><a href="#how-a-headless-cms-is-different-from-a-traditional-cms" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How a headless CMS is different from a traditional CMS</h2><p>A traditional CMS (often described as &quot;coupled&quot;) combines the content management application and the content delivery application in a single system: the back-end editing tools, the taxonomy, the website design and the templates are all part of one package. A headless CMS splits that apart. It keeps the content management and the administrative interface for editors, but it has no presentation layer of its own — content is delivered through an API instead of being rendered by the CMS itself.</p>
<p>Because of that, a headless CMS tends to focus on what editors need: an interface for content creators, tools for workflows and collaboration, and ways to organise content into taxonomies. How or where that content is shown is left to the developer.</p>
</section>
<section><h2 id="coupled-vs-decoupled-where-headless-fits"><a href="#coupled-vs-decoupled-where-headless-fits" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Coupled vs decoupled: where headless fits</h2><p>In a decoupled architecture, content management is separated from delivery and presentation. A headless CMS is a type of decoupled architecture. The decoupled approach can make systems easier to scale and more secure than a coupled one, but it does not by itself offer the same support for omnichannel delivery — and because there are multiple environments (the CMS, the front end, the API) to manage, infrastructure and maintenance costs can go up.</p>
<p>The headless idea is also used outside traditional CMS: headless commerce applies the same pattern, separating back-end product management and navigation from the front end of a website or another display type such as an IoT device.</p>
</section>
<section><h2 id="why-headless-cms-adoption-has-grown"><a href="#why-headless-cms-adoption-has-grown" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Why headless CMS adoption has grown</h2><p>Adoption has increased as API-first and composable software architectures have become more widespread. Market research links the growth to rising demand for omnichannel digital experiences, more reliance on APIs for content delivery and system integration, and the need for scalable, flexible alternatives to traditional monolithic CMS platforms. Modern front-end frameworks and static-site hosting fit naturally with a CMS that hands content out over an API.</p>
</section>
<section><h2 id="pros-and-cons-of-a-headless-cms"><a href="#pros-and-cons-of-a-headless-cms" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Pros and cons of a headless CMS</h2><p>The trade-off is flexibility versus effort:</p>
<ul>
<li><strong>Pros:</strong> because content is served through an API, the same content can power a website, a mobile app and other channels, with developers free to pick whatever front-end framework or hosting they want. This enables &quot;create once, publish everywhere&quot; workflows.</li>
<li><strong>Cons:</strong> a headless CMS has no built-in front end, so it requires more setup and configuration, and a level of web development knowledge on both the front end and the back end. Teams that only need a simple blog may find a traditional CMS quicker to run.</li>
</ul>
</section>
<section><h2 id="popular-headless-cms-examples"><a href="#popular-headless-cms-examples" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Popular headless CMS examples</h2><p>Dedicated headless CMS platforms include Contentful, Sanity, Strapi, Hygraph and Storyblok. They differ in deployment models, supported APIs, extensibility and how they model content, but they all separate content management from presentation.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-does-headless-mean-in-a-headless-cms">What does &quot;headless&quot; mean in a headless CMS?</h3><p>&quot;Headless&quot; means the CMS has no built-in front end or presentation layer (the &quot;head&quot;). It stores and manages content in a repository and delivers it through an API, so developers build the front end separately and pull content from the API.</p>
<h3 id="what-is-the-difference-between-a-traditional-cms-and-a-headless-cms">What is the difference between a traditional CMS and a headless CMS?</h3><p>A traditional (coupled) CMS combines the content management application and the presentation layer in one system. A headless CMS keeps the content management and editor interface but has no presentation layer; content is delivered via an API to any front end.</p>
<h3 id="is-a-headless-cms-the-same-as-a-decoupled-cms">Is a headless CMS the same as a decoupled CMS?</h3><p>A headless CMS is a type of decoupled architecture — content management is separated from presentation in both. What makes a CMS specifically &quot;headless&quot; is that it is back-end-only, exposing content purely through an API without any presentation layer.</p>
<h3 id="when-should-you-use-a-headless-cms">When should you use a headless CMS?</h3><p>Choose a headless CMS when you need the same content in multiple channels (web, mobile, apps) and want to use a custom front end or static-site hosting. It suits teams with the development skills to build the front end. For a simple site or blog where editors just write and publish, a traditional CMS is usually quicker.</p>
</section>
<section><h2 id="summary"><a href="#summary" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Summary</h2><p>A headless CMS is a back-end-only content repository that delivers content through an API instead of a built-in website. It separates content management from presentation, which gives developers freedom across front-end frameworks and supports omnichannel publishing — at the cost of more setup effort. When you need flexibility and reuse across channels, headless wins; for a simple site, a traditional CMS is often the faster choice.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>SSH explained: how Secure Shell works</title>
      <link>https://debod.id/en/posts/ssh-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/ssh-explained/</guid>
      <pubDate>Thu, 20 Aug 2026 00:00:00 GMT</pubDate>
      <category>security</category>
      <category>ssh</category>
      <category>security</category>
      <category>encryption</category>
      <category>networking</category>
      <description>SSH explained: what Secure Shell is, how the protocol works end to end, and why it replaced telnet and rsh.</description>
      <content:encoded><![CDATA[<section><p>SSH (Secure Shell) is a cryptographic network protocol for operating network services securely over an unsecured network — most commonly for remote login and command-line execution. Instead of sending data as plain text like the protocols it replaced, SSH encrypts everything between client and server, so passwords, commands, and files stay protected from anyone watching the network.</p></section>
<section><h2 id="why-ssh-exists-the-protocols-it-replaced"><a href="#why-ssh-exists-the-protocols-it-replaced" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Why SSH exists: the protocols it replaced</h2><p>In the early 1990s, remote administration relied on protocols that sent everything as plain text: telnet (port 23), rlogin, rsh, and FTP. They provided neither strong authentication nor confidentiality — a password sent over telnet could be read by anyone sniffing the network.</p>
<p>SSH was designed in 1995 by Tatu Ylönen, a researcher at Helsinki University of Technology in Finland, after a password-sniffing attack on his university network. The goal was a protocol with strong authentication and confidentiality, as a replacement for those older tools. Ylönen released his implementation as freeware in July 1995; by the end of that year it had grown to about 20,000 users in fifty countries, and to around 2 million by 2000. In December 1995 he founded SSH Communications Security to develop and market the software.</p>
<p>The protocol has two major versions, SSH-1 and SSH-2, which are not compatible. SSH-2 was standardized by the IETF in January 2006 (RFCs 4250–4256). The most widely used implementation is OpenSSH, forked from the last open-source SSH release by the OpenBSD developers in 1999 and shipped with OpenBSD 2.6.</p>
</section>
<section><h2 id="how-ssh-works-three-protocol-layers"><a href="#how-ssh-works-three-protocol-layers" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How SSH works: three protocol layers</h2><p>RFC 4251 defines SSH as a layered protocol suite with three principal components:</p>
<ul>
<li><strong>Transport layer</strong> — provides server authentication, confidentiality, and integrity, with perfect forward secrecy.</li>
<li><strong>User authentication protocol</strong> — authenticates the client to the server.</li>
<li><strong>Connection protocol</strong> — multiplexes the encrypted tunnel into multiple logical communication channels.</li>
</ul>
<p>SSH runs over TCP and defaults to port 22, which the IANA lists as a well-known port. When you run <code>ssh user@server</code>, the SSH client connects to the SSH server daemon (<code>sshd</code>) on that port.</p>
</section>
<section><h2 id="the-handshake-version-exchange-and-algorithm-negotiation"><a href="#the-handshake-version-exchange-and-algorithm-negotiation" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The handshake: version exchange and algorithm negotiation</h2><p>A connection starts with both sides exchanging identification strings in the form <code>SSH-2.0-softwareversion</code>. Because the protocol defined by the RFC set is version 2.0, the protocol version part is always &quot;2.0&quot; — if you have ever seen <code>SSH-2.0-OpenSSH_9.6</code> in a log, that is this string. A server that also supports older versions identifies itself as <code>1.99</code>.</p>
<p>After the version exchange, key exchange begins with each side sending name-lists of the algorithms it supports: key exchange method, host key algorithm, symmetric cipher, message authentication code, and compression. Both sides pick the first algorithm in the client&#39;s list that the server also supports, so the negotiated set can differ between implementations.</p>
</section>
<section><h2 id="key-exchange-and-host-authentication"><a href="#key-exchange-and-host-authentication" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Key exchange and host authentication</h2><p>The core of the transport layer is the Diffie-Hellman key exchange. It produces a shared secret that cannot be determined by either party alone; each side derives the same session keys from it, and because fresh random values are generated for the exchange, previously recorded sessions stay protected (forward secrecy).</p>
<p>Diffie-Hellman alone does not tell the client <em>who</em> it is talking to. RFC 4253 combines the exchange with a signature made using the server&#39;s host key: the server proves its identity by signing the exchange, which provides explicit host authentication. On a first connection the client has not seen the host key before, so SSH displays the key fingerprint and asks for confirmation. Modern implementations support RSA, ECDSA (RFC 5656), and Ed25519 (RFC 8709) host keys.</p>
</section>
<section><h2 id="authentication-how-the-client-proves-its-identity"><a href="#authentication-how-the-client-proves-its-identity" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Authentication: how the client proves its identity</h2><p>Once the transport layer is up, the user authentication protocol takes over. RFC 4252 defines several methods — and the only one that is REQUIRED of implementations is <code>publickey</code>:</p>
<ul>
<li><strong>Public key authentication</strong> — the client proves possession of the private key that matches a public key the server trusts. The secret itself never crosses the network.</li>
<li><strong>Password authentication</strong> — the password travels inside the encrypted tunnel, so it can no longer be sniffed, but it can still be guessed, phished, or reused from a breach elsewhere.</li>
<li><strong>Keyboard-interactive</strong> (RFC 4256) — the server sends one or more prompts; used for one-time passwords and similar schemes.</li>
<li><strong>Host-based</strong> — authentication based on the client machine&#39;s identity.</li>
</ul>
<p>Passwords are convenient, but public-key authentication is the standard recommendation for servers: keys are not reused across sites and cannot be intercepted from the network.</p>
</section>
<section><h2 id="the-connection-protocol-channels"><a href="#the-connection-protocol-channels" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The connection protocol: channels</h2><p>Everything you do after logging in travels inside the single encrypted tunnel as <em>channels</em>. RFC 4254 defines channels for interactive login sessions, remote command execution, forwarded TCP/IP connections, and forwarded X11 connections. Channels are flow-controlled, and many can be open at the same time — one SSH connection can run a terminal session, a port forward, and an SFTP transfer simultaneously.</p>
<p>Port forwarding (tunneling) is where SSH shines beyond the terminal: you can forward a local port to a service on the remote network, or the other way around, and the traffic rides inside the SSH encryption. This is how encrypted VPNs over SSH (supported by OpenSSH) and SOCKS proxy forwarding work.</p>
</section>
<section><h2 id="what-you-can-do-with-ssh"><a href="#what-you-can-do-with-ssh" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What you can do with SSH</h2><ul>
<li><strong>Remote login and administration</strong> — SSH clients and <code>sshd</code> are built into Linux, macOS, and BSD systems; Windows ships an OpenSSH port since Windows 10 version 1709.</li>
<li><strong>File transfer</strong> — SCP, SFTP, and rsync over SSH.</li>
<li><strong>Tunneling</strong> — local and remote port forwarding, X11 forwarding, and dynamic forwarding.</li>
</ul>
</section>
<section><h2 id="ssh-security-history-what-broke-and-what-it-means"><a href="#ssh-security-history-what-broke-and-what-it-means" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>SSH security history: what broke and what it means</h2><ul>
<li><strong>SSH-1 (1998)</strong> — a CRC-32 integrity weakness allowed content insertion into an encrypted stream; the compensating fixes themselves introduced an integer overflow vulnerability. SSH-1 is considered obsolete — implementations should disable fallback to it.</li>
<li><strong>SSH-2 (2006)</strong> — the IETF standardized SSH-2, adding stronger ciphers such as AES and new key exchange mechanisms; it is incompatible with SSH-1.</li>
<li><strong>CBC mode (2008)</strong> — researchers described a theoretical plaintext-recovery weakness (up to 32 bits) in the then-default CBC cipher mode. Counter (CTR) and AEAD modes such as AES-GCM and ChaCha20-Poly1305 avoid it; modern implementations negotiate them by default.</li>
<li><strong>Terrapin (2023)</strong> — a man-in-the-middle attack against most SSH implementations that downgrades some encryption protections; the practical impact is limited and mostly results in failed connections. The fix landed in OpenSSH 9.6, and both client and server must be upgraded for it to be fully effective.</li>
</ul>
<p>The practical takeaway: use SSH-2 only, prefer public-key authentication, and keep client and server implementations patched.</p>
</section>
<section><h2 id="ssh-vs-the-protocols-it-replaced"><a href="#ssh-vs-the-protocols-it-replaced" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>SSH vs the protocols it replaced</h2><table>
<thead>
<tr>
<th>Property</th>
<th>telnet / rlogin / rsh</th>
<th>SSH</th>
</tr>
</thead>
<tbody><tr>
<td>Credentials on the wire</td>
<td>Plain text</td>
<td>Encrypted</td>
</tr>
<tr>
<td>Data confidentiality</td>
<td>None</td>
<td>Encrypted (AES, ChaCha20, …)</td>
</tr>
<tr>
<td>Integrity protection</td>
<td>None</td>
<td>Message authentication codes</td>
</tr>
<tr>
<td>Server authentication</td>
<td>None</td>
<td>Host key verification</td>
</tr>
<tr>
<td>Default port</td>
<td>23 (telnet)</td>
<td>22</td>
</tr>
</tbody></table>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-port-does-ssh-use-by-default">What port does SSH use by default?</h3><p>Port 22 over TCP, registered as a well-known port by the IANA. The port is configurable on both client and server.</p>
<h3 id="is-ssh-the-same-as-tls-or-ssl">Is SSH the same as TLS or SSL?</h3><p>No. SSH is a complete protocol suite for secure remote access — transport, authentication, and connection layers — while TLS is a security layer used by protocols like HTTPS. They use similar cryptography but are separate, incompatible protocols.</p>
<h3 id="are-ssh-keys-safer-than-passwords">Are SSH keys safer than passwords?</h3><p>In most ways, yes. The private key never crosses the network, keys are not reused across websites, and a passphrase-protected key adds another factor. Password authentication remains useful but is only as strong as the password and its reuse habits.</p>
<h3 id="is-ssh-safe-to-use-on-public-wi-fi">Is SSH safe to use on public Wi-Fi?</h3><p>For confidentiality, yes — all data is encrypted, so an observer on the same network sees only ciphertext. The one habit to keep: on the first connection, verify the server&#39;s host key fingerprint against the value published by the server operator.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>SQL vs NoSQL: what&apos;s the difference?</title>
      <link>https://debod.id/en/posts/sql-vs-nosql/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/sql-vs-nosql/</guid>
      <pubDate>Thu, 20 Aug 2026 00:00:00 GMT</pubDate>
      <category>programming</category>
      <category>sql</category>
      <category>database</category>
      <category>programming</category>
      <description>SQL vs NoSQL compared: how relational and non-relational databases store data, the ACID and BASE trade-offs, and how to choose between them.</description>
      <content:encoded><![CDATA[<section><p>SQL and NoSQL are the two big families of databases, and the choice between them shapes how your application stores, queries, and scales its data. SQL databases store data in tables with a fixed structure, while NoSQL databases use flexible models like documents or key–value pairs. Neither is better in general — each fits different kinds of problems.</p></section>
<section><h2 id="what-sql-is-the-relational-language"><a href="#what-sql-is-the-relational-language" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What SQL is: the relational language</h2><p>SQL (Structured Query Language) is the standard language for relational databases, which organize data into tables of rows and columns, like a spreadsheet. SQL was developed at IBM in the early 1970s by Donald D. Chamberlin and Raymond F. Boyce, building on Edgar F. Codd&#39;s relational model. It was originally called SEQUEL — the name was changed because SEQUEL was a trademark owned by a British engineering company. Oracle V2, released in June 1979, was one of the first commercially available implementations, and by 1986 ANSI and ISO had adopted &quot;Database Language SQL&quot; as an official standard. The standard has been revised ever since, most recently as SQL:2023.</p>
<p>The defining trait of a relational database is its schema: you define tables, columns, and data types before you insert data, and the database enforces that structure on every row. Because data is spread across related tables, queries can join them on the fly — a classic example is an orders table referring to a customers table by ID. This is also where the object–relational impedance mismatch comes from: mapping between rows in relational tables and objects in your programming language takes extra work.</p>
</section>
<section><h2 id="what-nosql-is-not-only-sql"><a href="#what-nosql-is-not-only-sql" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What NoSQL is: &quot;not only SQL&quot;</h2><p>NoSQL (&quot;not only SQL&quot; or &quot;non-relational&quot;) covers databases that store and retrieve data outside the table model. Instead of rows and columns, these databases use one of several structures:</p>
<ul>
<li><strong>Key–value stores</strong> — a dictionary of keys and values. Examples: Redis, Amazon DynamoDB.</li>
<li><strong>Document stores</strong> — records stored as documents in JSON-like formats (MongoDB uses BSON), addressed by a unique key. Examples: MongoDB, CouchDB.</li>
<li><strong>Wide-column stores</strong> — tables that can have a different set of columns per row. Examples: Cassandra, HBase.</li>
<li><strong>Graph databases</strong> — data modeled as nodes and relationships. Example: Neo4j.</li>
</ul>
<p>The term has two histories. Carlo Strozzi used &quot;NoSQL&quot; in 1998 for a relational database without a SQL interface. The modern meaning took shape in early 2009, when Johan Oskarsson, then at Last.fm, used it for an event about open-source distributed non-relational databases. The movement&#39;s central ideas: no fixed schema — each record can have different fields — and horizontal scaling across clusters of machines, driven by web companies handling very large data sets.</p>
</section>
<section><h2 id="the-trade-offs-acid-vs-base"><a href="#the-trade-offs-acid-vs-base" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The trade-offs: ACID vs BASE</h2><p>Relational databases are built around ACID transactions: atomicity, consistency, isolation, and durability. This is why they dominate banking, accounting, and anywhere a failed half-written transaction is unacceptable.</p>
<p>Many NoSQL systems follow a different philosophy, often summarized as BASE: basically available, soft-state, eventually consistent. The CAP theorem, stated by Eric Brewer (PODC 2000) and proved by Seth Gilbert and Nancy Lynch in 2002, says a distributed data store can provide at most two of three guarantees: consistency, availability, and partition tolerance. ACID-oriented systems lean toward consistency; BASE-oriented systems lean toward availability. Under eventual consistency, a database replica may briefly serve stale data, but if updates stop, all reads eventually return the latest value.</p>
<p>The boundary is blurry, though. MongoDB, a document store, has supported multi-document ACID transactions since version 4.0, released in June 2018. And the reverse also exists: PostgreSQL can store JSON documents directly (as JSON and the faster binary JSONB format), and JSON support was added to the SQL standard itself in SQL:2016.</p>
</section>
<section><h2 id="scaling-vertical-vs-horizontal"><a href="#scaling-vertical-vs-horizontal" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Scaling: vertical vs horizontal</h2><p>Relational databases have traditionally scaled vertically — more CPU, memory, and disk in one server. That works, but has limits, and distributing one relational database across many machines is hard. NoSQL systems were designed for horizontal scaling from the start: MongoDB scales out by sharding, distributing data across many servers, and Cassandra is built on clusters of commodity machines.</p>
<p>One relational database that scales differently is SQLite — not a server at all, but an embedded library. It is the most widely deployed database engine in the world, used inside web browsers, operating systems, and mobile phones.</p>
</section>
<section><h2 id="sql-vs-nosql-comparison"><a href="#sql-vs-nosql-comparison" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>SQL vs NoSQL comparison</h2><table>
<thead>
<tr>
<th>Property</th>
<th>SQL databases</th>
<th>NoSQL databases</th>
</tr>
</thead>
<tbody><tr>
<td>Data model</td>
<td>Tables, rows, columns</td>
<td>Documents, key–value pairs, wide columns, graphs</td>
</tr>
<tr>
<td>Schema</td>
<td>Fixed, defined before inserting</td>
<td>Flexible, typically no fixed schema</td>
</tr>
<tr>
<td>Transactions</td>
<td>ACID</td>
<td>ACID in some systems; many use eventual consistency</td>
</tr>
<tr>
<td>Scaling</td>
<td>Mostly vertical (bigger server)</td>
<td>Horizontal by design (clusters of machines)</td>
</tr>
<tr>
<td>Querying</td>
<td>Standard SQL, joins across related tables</td>
<td>Database-specific APIs; some SQL-like query languages</td>
</tr>
<tr>
<td>Best fit</td>
<td>Structured, related data; reporting; financial records</td>
<td>Flexible or unstructured data; very large volumes; real-time web apps</td>
</tr>
<tr>
<td>Examples</td>
<td>PostgreSQL, MySQL, SQLite, Oracle</td>
<td>MongoDB, Redis, Cassandra, DynamoDB, Neo4j</td>
</tr>
</tbody></table>
</section>
<section><h2 id="how-to-choose"><a href="#how-to-choose" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How to choose</h2><p>Start relational for most applications. If your data is structured, related, and must be exact — user accounts, orders, financial records, anything you need to report over — PostgreSQL or MySQL (both open source) or SQLite for embedded use gives you 40 years of standardized querying and ACID guarantees.</p>
<p>Reach for NoSQL when the relational model fights you: data that changes shape constantly, documents that map naturally to JSON, very large volumes on clusters of commodity hardware, or simple lookups at huge scale (a Redis cache in front of a relational database is a classic pattern).</p>
<p>The two families are a spectrum, not a war. MongoDB now has ACID transactions; PostgreSQL now has JSON columns. Pragmatic systems use both in one architecture — polyglot persistence, &quot;not only SQL&quot; — picking per problem. Whatever you choose, benchmark against your own workload: &quot;faster&quot; depends entirely on the queries you actually run.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="is-nosql-faster-than-sql">Is NoSQL faster than SQL?</h3><p>Not in general. Many NoSQL systems prioritize speed and availability for simple lookups at very large scale, which can make them dramatically faster for those patterns. Conversely, relational databases are typically better at complex queries that join related tables. There is no universal answer — measure your own workload.</p>
<h3 id="can-you-use-sql-with-nosql-databases">Can you use SQL with NoSQL databases?</h3><p>The name itself means &quot;not only SQL&quot;: some NoSQL databases offer SQL-like query languages, and many systems combine the two in one architecture. You might store canonical data in a relational database and use a key–value store as a cache in front of it.</p>
<h3 id="which-should-i-choose-for-a-small-web-app">Which should I choose for a small web app?</h3><p>A relational database is usually the right default: PostgreSQL, MySQL, or SQLite depending on your deployment. Choose a document store like MongoDB if your data is naturally JSON-shaped and its structure changes often.</p>
<h3 id="is-mongodb-a-nosql-database">Is MongoDB a NoSQL database?</h3><p>Yes. MongoDB is a document-oriented NoSQL database, first released in February 2009 by 10gen (now MongoDB Inc.). It stores data in JSON-like BSON documents and supports multi-document ACID transactions since version 4.0 (June 2018).</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>OSI model explained: the 7 layers of networking</title>
      <link>https://debod.id/en/posts/osi-model-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/osi-model-explained/</guid>
      <pubDate>Thu, 20 Aug 2026 00:00:00 GMT</pubDate>
      <category>networking</category>
      <category>networking</category>
      <category>protocols</category>
      <description>The OSI model explained: what the 7 layers (physical to application) do, how data moves through them, and how the model compares with the real TCP/IP stack.</description>
      <content:encoded><![CDATA[<section><p>The OSI model (Open Systems Interconnection model) is a conceptual framework that divides computer networking into seven layers, from the physical cable up to the application you use. Each layer handles a specific job and communicates only with the layers directly above and below it, so any layer can be swapped or upgraded without touching the others. It was standardized in 1984 as ISO 7498 and ITU-T X.200, and it is still the standard way to teach, document, and debug networks — even though the Internet itself does not follow it strictly.</p></section>
<section><h2 id="the-idea-behind-the-model"><a href="#the-idea-behind-the-model" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The idea behind the model</h2><p>Communication protocols enable an entity in one host to interact with a corresponding entity at the same layer in another host. In the model, a layer <em>N</em> provides services to the layer above it (<em>N+1</em>) and uses services from the layer below it (<em>N−1</em>). Two devices talk to each other through their peer layers: for example, the transport layer on your computer &quot;speaks&quot; to the transport layer on the server, even though the data physically travels through every lower layer on the way.</p>
<p>Data processing works by encapsulation. The data to be transmitted is composed at the topmost layer into a <em>protocol data unit</em> (PDU), which is passed down as a <em>service data unit</em> (SDU) to the next layer. That layer concatenates the SDU with a header, a footer, or both, producing its own PDU, and passes it further down. The process continues until the lowest layer transmits the data to the receiving device. On the receiving side the process is reversed: each layer strips its own header or footer until the topmost layer consumes the original data.</p>
</section>
<section><h2 id="the-seven-layers"><a href="#the-seven-layers" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The seven layers</h2><p>The recommendation X.200 describes seven layers, labelled 1 to 7. Layer 1 is the lowest layer in the model.</p>
<h3 id="layer-1-physical-layer">Layer 1: Physical layer</h3><p>The physical layer is responsible for the transmission and reception of unstructured raw data between a device — a network interface controller, an Ethernet hub, or a switch — and a physical transmission medium. It converts digital bits into electrical, radio, or optical signals. Its specifications define voltage levels, the timing of voltage changes, physical data rates, maximum transmission distances, modulation schemes, channel access methods, and physical connectors. Bit rate control happens here, and the layer may define transmission as simplex, half-duplex, or full-duplex. Physical layer specifications are included in the standards for Bluetooth, Ethernet, and USB.</p>
<h3 id="layer-2-data-link-layer">Layer 2: Data link layer</h3><p>The data link layer provides node-to-node data transfer — a link between two directly connected nodes. It detects and possibly corrects errors that occur in the physical layer, and defines protocols to establish and terminate the connection between two physically connected devices, including flow control between them. The IEEE 802 standards divide this layer into two sublayers: the MAC (medium access control) layer, which controls how devices gain access to a medium and permission to transmit, and the LLC (logical link control) layer, which identifies and encapsulates network layer protocols and handles error checking and frame synchronization. Ethernet (802.3), Wi-Fi (802.11), and Zigbee (802.15.4) all operate at this layer.</p>
<h3 id="layer-3-network-layer">Layer 3: Network layer</h3><p>The network layer provides the means of transferring packets from one node to another across different networks. Every node on a network has an address, and the network finds the way to deliver a message to the destination node, possibly routing it through intermediate nodes. If a message is too large for the data link layer between two nodes, the network layer splits it into fragments, sends them independently, and reassembles them at the destination. Delivery is not necessarily reliable: a network layer protocol may provide reliable delivery, but it does not need to. Routing protocols, multicast group management, and network-layer error and address-assignment functions live here, and security at this layer can be applied with IPsec.</p>
<h3 id="layer-4-transport-layer">Layer 4: Transport layer</h3><p>The transport layer transfers variable-length data sequences from a source host to a destination host, from one application to another, while maintaining quality-of-service functions. Because the network layer imposes a maximum packet size (the MTU), the transport layer may break large data streams into smaller chunks called segments. For example, over Ethernet the MTU is 1500 bytes; subtracting the minimum 20-byte TCP header and 20-byte IPv4 header leaves a maximum segment size of 1460 bytes. The transport layer also controls reliability through flow control, error control, and acknowledgments. TCP and UDP of the Internet protocol suite are commonly categorized as layer 4 protocols, although they were not developed under the OSI model. TCP is connection-oriented and reliable; UDP is connectionless and is used by streaming media, real-time games, and VoIP, where some packet loss is acceptable.</p>
<h3 id="layer-5-session-layer">Layer 5: Session layer</h3><p>The session layer creates, controls, and ends the connection between two or more computers — the &quot;session&quot;. Common functions include user logon and logoff, and it establishes procedures for checkpointing, suspending, restarting, and terminating a session between two related streams of data, such as audio and video in a web-conferencing application. The session layer is commonly implemented explicitly in environments that use remote procedure calls.</p>
<h3 id="layer-6-presentation-layer">Layer 6: Presentation layer</h3><p>The presentation layer establishes data formatting and translation. Outgoing messages are converted into a format specified by the application layer during encapsulation, and incoming messages are converted back during de-encapsulation. It handles protocol conversion, encryption and decryption, compression and decompression, and incompatibilities of data representation between operating systems. Because it converts data into a display format for the application layer, it is sometimes called the syntax layer. In the modern Internet, TLS (Transport Layer Security) is often cited as a real example: it contains characteristics of both the transport and presentation layers.</p>
<h3 id="layer-7-application-layer">Layer 7: Application layer</h3><p>The application layer is the layer closest to the end user — it is what software applications use to communicate. Application programs such as web browsers and email programs implement application-layer protocols, the most common being HTTP, FTP, SMB/CIFS, TFTP, and SMTP. An important distinction exists between the application entity and the application itself: a reservation website might use HTTP to talk to its users and a remote database protocol to record reservations — neither protocol knows anything about reservations; that logic lives in the application. The application layer determines the identity and availability of communication partners, but it has no means to determine the availability of resources in the network.</p>
</section>
<section><h2 id="how-data-travels-an-example"><a href="#how-data-travels-an-example" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How data travels: an example</h2><p>Imagine loading a website. Your browser (application layer) sends an HTTP request, which the presentation and session layers pass down with their own formatting and session management. The transport layer (TCP) splits the request into segments and numbers them for reliability. The network layer (IP) wraps each segment into a packet with source and destination addresses and routes it across the network. The data link layer puts each packet into a frame for the local link, and the physical layer sends the frames as electrical, radio, or optical signals. The server performs the same process in reverse, from physical layer up to its own application layer — and its response travels back down the same way.</p>
</section>
<section><h2 id="osi-vs-the-real-internet-tcp-ip"><a href="#osi-vs-the-real-internet-tcp-ip" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>OSI vs the real Internet: TCP/IP</h2><p>The OSI model is a reference, not a description of the Internet. The design of protocols in the TCP/IP model does not concern itself with strict hierarchical encapsulation and layering — RFC 3439 even contains a section titled &quot;Layering considered harmful&quot;. TCP/IP recognizes four broad layers, which are often compared with the OSI scheme like this:</p>
<ul>
<li>The Internet application layer maps to the OSI application, presentation, and most of the session layer.</li>
<li>The TCP/IP transport layer maps to the graceful close function of the OSI session layer plus the OSI transport layer.</li>
<li>The Internet (internetworking) layer performs functions of a subset of the OSI network layer.</li>
<li>The link layer corresponds to the OSI data link layer and may include similar functions as the physical layer, plus some network layer protocols.</li>
</ul>
<p>The OSI protocol suite itself — the specific protocols designed for the model — was considered by many as too complicated, inefficient, and to a large extent unimplementable. It took a &quot;forklift upgrade&quot; approach: replacing all existing protocols at all layers of the stack, which vendors and users with investments in other technologies resisted. The Internet protocol suite won because of its pragmatic, simplified approach. Still, some OSI-era specifications remain in use: IS-IS, specified for OSI as ISO/IEC 10589 and adapted for the Internet as RFC 1142, is one example.</p>
</section>
<section><h2 id="why-the-model-is-still-useful"><a href="#why-the-model-is-still-useful" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Why the model is still useful</h2><p>The OSI model remains the standard framework for teaching networking and for documentation, and it is the shared vocabulary engineers use to describe problems: &quot;that&#39;s a layer 2 issue&quot; or &quot;the problem is at layer 7&quot;. Each layer has real protocols and hardware attached to it, which makes the model a practical map for debugging — from the cable (layer 1) up to the application (layer 7). Its limitations are just as instructive: the Internet works because protocols do not obey strict layering, which is why TCP/IP, with its four pragmatic layers, became the standard for networking.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-does-osi-stand-for">What does OSI stand for?</h3><p>OSI stands for Open Systems Interconnection. The model is formally the Basic Reference Model for Open Systems Interconnection, published in 1984 as ISO 7498 and ITU-T X.200.</p>
<h3 id="how-do-i-remember-the-7-layers">How do I remember the 7 layers?</h3><p>From top to bottom: Application, Presentation, Session, Transport, Network, Data link, Physical — one word per layer, from layer 7 to layer 1.</p>
<h3 id="is-the-osi-model-the-same-as-tcp-ip">Is the OSI model the same as TCP/IP?</h3><p>No. OSI is a seven-layer reference model; TCP/IP is the four-layer model of the protocols the Internet actually uses. The Internet does not follow OSI strictly, but the two are usually compared layer by layer because the OSI model is the common teaching vocabulary.</p>
<h3 id="which-osi-layer-is-http">Which OSI layer is HTTP?</h3><p>HTTP operates at layer 7, the application layer, like FTP and SMTP. The model treats these protocols as the interface between the user&#39;s application and the network below.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>Kubernetes explained: how container orchestration works</title>
      <link>https://debod.id/en/posts/kubernetes-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/kubernetes-explained/</guid>
      <pubDate>Thu, 20 Aug 2026 00:00:00 GMT</pubDate>
      <category>programming</category>
      <category>kubernetes</category>
      <category>containers</category>
      <category>orchestration</category>
      <category>programming</category>
      <description>Kubernetes explained: what K8s is, how the control plane and worker nodes work, what pods, services, and deployments are, and how Kubernetes orchestrates containers at scale.</description>
      <content:encoded><![CDATA[<section><p>Kubernetes, also known as K8s, is an open-source container orchestration system for automating software deployment, scaling, and management. It assembles one or more computers — virtual machines or bare metal — into a cluster that runs workloads inside containers, and it keeps those workloads running at the state you declare, no matter which machine they land on. Kubernetes was originally designed by Google and is now maintained by a worldwide community of contributors, with the trademark held by the Cloud Native Computing Foundation (CNCF).</p></section>
<section><h2 id="where-the-name-comes-from"><a href="#where-the-name-comes-from" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Where the name comes from</h2><p>The name <em>Kubernetes</em> comes from the Ancient Greek term <em>kubernḗtēs</em>, meaning &quot;helmsman&quot; or &quot;pilot&quot; — the same word that gave us <em>cybernetics</em> and, through Latin, <em>governor</em>. The abbreviation &quot;K8s&quot; is a numeronym: the letter K, followed by 8 letters, followed by s.</p>
</section>
<section><h2 id="what-a-cluster-is"><a href="#what-a-cluster-is" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What a cluster is</h2><p>A Kubernetes cluster is a group of computers that work together as a single system. Any of them can be a virtual machine or a bare-metal server. The cluster runs workloads in containers and works with various container runtimes, such as containerd and CRI-O. Because containers already bundle the application and its dependencies, Kubernetes can move them between machines without rebuilding anything.</p>
<p>The software consists of a control plane and the nodes on which the actual applications run. It includes tools like <code>kubeadm</code> (to set up a cluster) and <code>kubectl</code> (to interact with its REST-based API).</p>
</section>
<section><h2 id="the-control-plane"><a href="#the-control-plane" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The control plane</h2><p>The control plane manages the workload and directs communication across the system. It is split into several components, each its own process, which can run on a single node or on multiple nodes for high-availability clusters.</p>
<table>
<thead>
<tr>
<th>Component</th>
<th>What it does</th>
</tr>
</thead>
<tbody><tr>
<td><strong>etcd</strong></td>
<td>Persistent, lightweight, distributed key-value store that holds the cluster&#39;s configuration data — the overall state of the cluster at any moment. It favors consistency over availability during a network partition (the CAP theorem trade-off).</td>
</tr>
<tr>
<td><strong>API server</strong></td>
<td>Serves the Kubernetes API as JSON over HTTPS. It processes and validates REST requests and updates the state of API objects in etcd, so clients configure workloads and containers across all worker nodes.</td>
</tr>
<tr>
<td><strong>Scheduler</strong></td>
<td>Chooses the node on which an unscheduled pod will run, based on resource availability and other constraints such as quality-of-service, affinity rules, and data locality.</td>
</tr>
<tr>
<td><strong>Controllers</strong></td>
<td>Reconciliation loops that drive the actual cluster state toward the desired state, creating, updating, or deleting resources to match the declaration. The controller manager runs several core controllers in one process.</td>
</tr>
</tbody></table>
<p>A concrete example ties them together: an operator declares that three instances of a particular pod must be running, and etcd stores that fact. If the Deployment controller finds that only two instances are running, it schedules the creation of the missing instance.</p>
</section>
<section><h2 id="worker-nodes"><a href="#worker-nodes" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Worker nodes</h2><p>A node — also called a worker — is a machine where containers are deployed. Every node must run a container runtime plus the components that connect it to the control plane:</p>
<ul>
<li><strong>kubelet</strong> is responsible for the running state of each node: it starts, stops, and maintains the application containers that the control plane asks it to run. It reports node status to the API server every few seconds via heartbeat messages.</li>
<li><strong>Container runtime</strong> manages the lifecycle of containers — launching them, reconciling them, and killing them. Kubelet talks to the runtime through the Container Runtime Interface (CRI). Originally kubelet was tightly coupled to Docker; the &quot;dockershim&quot; shim bridged them until it was deprecated in 2020 and removed entirely in Kubernetes v1.24 (May 2022). As of 2026, widely used CRI-compliant runtimes include containerd and CRI-O.</li>
<li><strong>kube-proxy</strong> is a network proxy and load balancer running on each node. It supports the service abstraction and routes traffic to the right container based on the IP address and port of the incoming request.</li>
</ul>
</section>
<section><h2 id="pods-the-smallest-unit-of-work"><a href="#pods-the-smallest-unit-of-work" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Pods: the smallest unit of work</h2><p>The basic deployable unit in Kubernetes is the <strong>pod</strong>, which consists of one or more containers co-located on the same node. Each pod gets a unique IP address inside the cluster, so applications can use ports without the risk of conflict, and the containers inside one pod share a context and can reference each other. In the most common use case, each pod holds a single container — but the pod, not the container, is what Kubernetes schedules and manages.</p>
</section>
<section><h2 id="workloads-managing-pods-at-a-higher-level"><a href="#workloads-managing-pods-at-a-higher-level" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Workloads: managing pods at a higher level</h2><p>Kubernetes offers several workload abstractions layered on top of pods, so you declare the shape of your application instead of managing individual pods by hand:</p>
<ul>
<li><strong>ReplicaSet</strong> keeps a stable set of replica pods running at any given time — it guarantees the availability of a specified number of identical pods. The older ReplicationController did the same job; it was deprecated in favor of the ReplicaSet&#39;s set-based label selectors.</li>
<li><strong>Deployment</strong> is a higher-level management mechanism for ReplicaSets: it decides whether an update is rolled out or rolled back, and when you scale a Deployment up or down, the underlying ReplicaSet declaration changes with it.</li>
<li><strong>StatefulSet</strong> enforces uniqueness and ordering among pod instances, which is what stateful applications need. Databases running in high-availability mode have a primary and secondary instances, and Apache Kafka distributes data among brokers — in both cases instances are not interchangeable, so ordering and identity matter.</li>
<li><strong>DaemonSet</strong> ensures that a pod runs on every single node in the cluster, scaling up as nodes are added. This suits workloads tied to the host machine, such as log collection, ingress controllers, and storage services.</li>
</ul>
</section>
<section><h2 id="services-stable-addresses-for-changing-pods"><a href="#services-stable-addresses-for-changing-pods" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Services: stable addresses for changing pods</h2><p>Pods are ephemeral — they come and go as the cluster scales or heals. A <strong>Service</strong> fixes this by grouping the pods of one tier (for example, all backend pods) behind a label selector and giving the group a stable IP address and DNS name. Kubernetes discovers services in two ways — through environment variables or through Kubernetes DNS — and load balances traffic in a round-robin fashion among the pods that match the selector. A Service can be exposed inside the cluster only, or outside it, for clients to reach the front-end pods.</p>
</section>
<section><h2 id="namespaces-storage-and-configuration"><a href="#namespaces-storage-and-configuration" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Namespaces, storage, and configuration</h2><p>Kubernetes keeps its resources organized with a few supporting concepts:</p>
<ul>
<li><strong>Namespaces</strong> divide cluster resources into distinct, non-intersecting collections — useful when many users share a cluster across teams or projects, or to separate development, test, and production environments.</li>
<li><strong>Volumes</strong> provide persistent storage. A container&#39;s filesystem is ephemeral and wiped out on restart, but a Volume survives and can even be shared as disk space by the containers inside one pod.</li>
<li><strong>ConfigMaps and Secrets</strong> let you change configuration without rebuilding the application. Both can be exposed as environment variables or mounted into the container&#39;s filesystem as volumes — mounted ConfigMaps reload automatically without restarting. Secrets are meant for sensitive data such as passwords, SSH keys, and registry credentials, although by default they are not encrypted at rest.</li>
</ul>
</section>
<section><h2 id="labels-and-selectors-how-kubernetes-finds-things"><a href="#labels-and-selectors-how-kubernetes-finds-things" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Labels and selectors: how Kubernetes finds things</h2><p>Labels are key-value pairs attached to any API object — pods, nodes, services. Label selectors are queries against those labels that resolve to the matching objects. A Service, for example, selects the pods that receive its traffic: pods labeled <code>tier=backend AND release_track=canary</code> can be addressed as a group in one operation. Because traffic routing depends only on labels, switching the labels or the selector changes which pods get traffic — the mechanism behind blue-green deployments and A/B testing.</p>
</section>
<section><h2 id="declarative-api-custom-resources-and-operators"><a href="#declarative-api-custom-resources-and-operators" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Declarative API, custom resources, and operators</h2><p>Everything in Kubernetes is a <strong>record of intent</strong>. Object definitions carry <code>spec</code> (the desired state, written by the user or a higher-level controller) and <code>status</code> (the current state, actively updated by the controller that manages the object). That declarative model is what lets controllers keep converging the cluster toward what you declared.</p>
<p>The API itself is extensible. <strong>Custom resources</strong> are objects that are not part of the standard installation, registered dynamically through Custom Resource Definitions (CRDs). Pair a custom resource with a custom controller — a reconciliation loop of your own — and you have an <strong>Operator</strong>: automation that captures how a human operator would run and care for a specific service, from taking backups to handling upgrades. Notable CNCF projects that follow the operator pattern include Argo, Open Policy Agent, and Istio.</p>
</section>
<section><h2 id="api-security"><a href="#api-security" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>API security</h2><p>The API server is the single point of access to a cluster, so it is locked down: it serves HTTPS with TLS certificate authority verification. Requests are authenticated with X.509 client certificates, bearer tokens, or service account tokens for programmatic access, usually supplied through a <code>kubeconfig</code> file. Authorization runs through pluggable modes — Node, Attribute-Based Access Control (ABAC), Role-Based Access Control (RBAC), and a webhook mode that delegates authorization decisions to an external service.</p>
</section>
<section><h2 id="how-kubernetes-came-to-be"><a href="#how-kubernetes-came-to-be" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How Kubernetes came to be</h2><p>Kubernetes was announced by Google on June 6, 2014, created by Joe Beda, Brendan Burns, and Craig McLuckie. Its design was inspired by Google&#39;s internal Borg cluster manager, and the internal codename was &quot;Project 7&quot; after the Star Trek ex-Borg character Seven of Nine — the logo&#39;s seven-spoked ship&#39;s wheel comes from that. Unlike Borg, which was written in C++, Kubernetes is written in Go. The first tagged release, v0.2, came in September 2014, and version 1.0 was released in July 2015. Google then worked with the Linux Foundation to form the Cloud Native Computing Foundation and offered Kubernetes as its seed technology.</p>
<p>Adoption accelerated quickly. In 2017 the principal competitors rallied around Kubernetes and announced native support: VMware in August, Mesosphere in September, Docker Inc. and Microsoft Azure in October, and Amazon Web Services with its Elastic Kubernetes Service (EKS) in November. On March 6, 2018, Kubernetes reached ninth place among GitHub projects by number of commits, and second place in authors and issues after the Linux kernel, as the first CNCF project to graduate.</p>
<p>Kubernetes follows a version support policy of N-3 since version 1.19 — the three most recent minor versions receive security updates and bug fixes, for roughly one year of full support plus a maintenance period.</p>
</section>
<section><h2 id="kubernetes-vs-docker"><a href="#kubernetes-vs-docker" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Kubernetes vs Docker</h2><p>Kubernetes and Docker answer different questions. Docker (via the Docker Engine) builds, ships, and runs containers on a single machine; Kubernetes schedules and manages containers across a cluster of machines. The two are complementary: Docker produces the images and the containers, Kubernetes runs them at scale, heals them when a node fails, and load balances traffic between them. Historically Kubernetes even used Docker directly as a runtime through the dockershim, until the container runtime interface matured and the shim was removed in v1.24.</p>
</section>
<section><h2 id="distributions"><a href="#distributions" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Distributions</h2><p>Because Kubernetes is open source under the Apache License 2.0, it ships in many forms. Open-source distributions include k3s (a lightweight build for edge and small clusters), k0s, Amazon EKS-D, SUSE Rancher&#39;s RKE, and OKD (the community distribution behind Red Hat OpenShift). Commercial distributions add enterprise support, such as Red Hat OpenShift, SUSE Rancher Prime, Nutanix, and Mirantis Kubernetes Engine. Every major cloud vendor also offers a managed version — Amazon EKS, Google GKE, Microsoft AKS, plus offerings from Alibaba Cloud, IBM, Oracle, DigitalOcean, and Huawei — where the cloud provider operates the control plane for you.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-does-kubernetes-do">What does Kubernetes do?</h3><p>Kubernetes automates the deployment, scaling, and management of containerized applications. It groups machines into a cluster, schedules containers onto them, keeps them running at the state you declare, and load balances traffic between them.</p>
<h3 id="is-kubernetes-a-container-runtime">Is Kubernetes a container runtime?</h3><p>No. Kubernetes is an orchestration system: it schedules and manages containers, but the containers themselves are run by a container runtime such as containerd or CRI-O, which Kubernetes talks to through the Container Runtime Interface.</p>
<h3 id="what-is-the-difference-between-kubernetes-and-docker">What is the difference between Kubernetes and Docker?</h3><p>Docker builds and runs containers on one machine. Kubernetes manages containers across a cluster of machines — scheduling, scaling, healing, and networking them. In practice, Docker builds the images and Kubernetes runs them at scale.</p>
<h3 id="is-kubernetes-free">Is Kubernetes free?</h3><p>Yes. Kubernetes is open source under the Apache License 2.0. Managed offerings from cloud vendors (EKS, GKE, AKS) are paid services, but the platform itself is free to run on your own infrastructure.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>Git explained: how version control works</title>
      <link>https://debod.id/en/posts/git-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/git-explained/</guid>
      <pubDate>Thu, 20 Aug 2026 00:00:00 GMT</pubDate>
      <category>programming</category>
      <category>git</category>
      <category>programming</category>
      <description>Git explained: what version control is, how Git stores history as content-addressed snapshots, why branches are cheap, and how Git and GitHub fit together.</description>
      <content:encoded><![CDATA[<section><p>Git is a distributed version control system that manages versions of source code or any other files. Every developer who uses Git keeps a full copy of the entire project history on their own machine, which makes branching, merging, and offline work fast. Git was created by Linus Torvalds in 2005 for Linux kernel development and has since become the version control system most commonly used by software developers.</p></section>
<section><h2 id="what-version-control-is"><a href="#what-version-control-is" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What version control is</h2><p>Version control is the practice of controlling, organizing, and tracking different versions of computer files in history — primarily source code, but any type of file. Without it, coordinating a team means passing files around, keeping backups with names like <code>final-v2-real</code>, and hoping nobody overwrites anyone else&#39;s work. A version control system records every change so you can see what changed, who changed it, and when, and return to any earlier state.</p>
<p>Version control systems come in two broad models. <strong>Centralized</strong> systems such as Apache Subversion keep the history on a single server; clients check out a working copy and talk to the server for history and commits. <strong>Distributed</strong> systems such as Git, Mercurial, and Bazaar give every developer a local repository with the complete history, independent of network access. The repositories are peers: changes travel between them, and teams usually still keep one central shared copy, but nothing blocks work when the server is unreachable.</p>
</section>
<section><h2 id="the-history-of-git"><a href="#the-history-of-git" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The history of Git</h2><p>Git was born from a licensing dispute. From 2002, the Linux kernel was developed with BitKeeper, a proprietary source-control system whose free license was revoked for Linux in April 2005. BitKeeper&#39;s copyright holder claimed that Andrew Tridgell had built SourcePuller by reverse-engineering the BitKeeper protocols. The same incident also spurred the creation of Mercurial.</p>
<p>Torvalds wanted a distributed system he could use like BitKeeper, but none of the free systems available met Linux&#39;s needs. He cited a system that took 30 seconds to apply a patch and update metadata — at the scale of kernel development, where synchronizing with fellow maintainers could require 250 such actions at once, that did not scale. His requirements: patching should take no more than three seconds, take CVS as an example of what <em>not</em> to do, support a distributed BitKeeper-like workflow, and include strong safeguards against corruption.</p>
<p>Development began on 3 April 2005. Torvalds announced the project on 6 April and the next day Git was already hosting itself. On 29 April, the young system was benchmarked recording patches to the kernel tree at 6.7 patches per second; on 16 June it managed the Linux 2.6.12 release. Torvalds handed maintenance to Junio Hamano on 26 July 2005, who released Git 1.0 on 21 December 2005. As for the name, Torvalds quipped: &quot;I&#39;m an egotistical bastard, and I name all my projects after myself. First &#39;Linux&#39;, now &#39;git&#39;&quot; — the word is British slang for an unpleasant person. The man page calls Git &quot;the stupid content tracker&quot;.</p>
</section>
<section><h2 id="how-git-stores-history-snapshots-not-file-deltas"><a href="#how-git-stores-history-snapshots-not-file-deltas" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How Git stores history: snapshots, not file deltas</h2><p>Older systems like SCCS and RCS saved space by storing versions of individual files as deltas. Torvalds rejected that model. Git snapshots the whole directory tree: each commit records the complete state of the project, and files that did not change are reused by reference. This is why Git operations are so fast — diffing a big repository is an order of magnitude faster than the older systems, and reading history from a local repository can be a hundred times faster than fetching from a remote server.</p>
<p>Git stores everything in a content-addressable object database. Objects are named by a SHA-1 hash of their contents, and each commit&#39;s hash depends on the entire history leading up to it, in a structure similar to a Merkle tree. Once history is published, changing an old version is noticed immediately — that is the safeguard against corruption, accidental or malicious. Torvalds has said he designed Git as &quot;a filesystem&quot; more than a traditional source-control system.</p>
<p>There are four types of objects:</p>
<ul>
<li><strong>Blob</strong> — the content of a file, with no name, timestamp, or other metadata.</li>
<li><strong>Tree</strong> — a directory: a list of file names and references to blobs or other trees.</li>
<li><strong>Commit</strong> — links trees into history: a tree, timestamp, log message, and zero or more parent commits.</li>
<li><strong>Tag</strong> — a fixed reference to another object, often a signed commit for a release.</li>
</ul>
<p>Branches are the trick that makes Git&#39;s non-linear workflows practical: a branch is just a named reference to one commit, and committing on top of it moves the reference automatically. Creating a branch is nearly free, so thousands of parallel branches on different computers are a design goal, not a burden.</p>
</section>
<section><h2 id="the-basic-workflow-three-states-five-commands"><a href="#the-basic-workflow-three-states-five-commands" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The basic workflow: three states, five commands</h2><p>In Git&#39;s terminology, a file lives in one of three places: the <strong>working tree</strong> (files you edit), the <strong>index</strong> (also called the staging area — your next commit, selected with <code>git add</code>), and the <strong>repository</strong> (committed history). The flow is short:</p>
<ul>
<li><code>git init</code> — create a repository in the current directory.</li>
<li><code>git add [file]</code> — stage a file into the index.</li>
<li><code>git commit -m [message]</code> — record the staged files as a new commit in history.</li>
<li><code>git log</code> — see the history of commits.</li>
<li><code>git status</code> — see what has changed and what is staged.</li>
</ul>
<p>To share work, clone a repository with <code>git clone [URL]</code>, pull others&#39; changes with <code>git pull</code>, and publish yours with <code>git push</code>. Because every clone holds full history, most operations work offline. Files listed in a <code>.gitignore</code> file are not tracked by Git — a plain text file that typically lists build output, dependencies, and secrets.</p>
</section>
<section><h2 id="merging-and-pull-requests"><a href="#merging-and-pull-requests" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Merging and pull requests</h2><p>Git implements several merge strategies, selected automatically or at merge time: <code>resolve</code> (classic three-way merge), <code>recursive</code> (the default when merging one branch — when multiple common ancestors exist, it builds a merged tree of them first, which Torvalds reported results in fewer merge conflicts), and <code>octopus</code> (default when merging more than two heads). When changes conflict, Git stops and asks you to edit the conflicting files manually, then completes the merge.</p>
<p>Git itself has no pull request. A pull request — &quot;please merge my branch into yours&quot; — is a feature of hosting services like GitHub, GitLab, and Bitbucket. Under the hood it is no different from a repository maintainer pulling changes from another remote; the hosting service just wraps that operation in a ticket with review, discussion, and status checks.</p>
</section>
<section><h2 id="git-and-github"><a href="#git-and-github" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Git and GitHub</h2><p>GitHub is a proprietary developer platform that uses Git to provide version control, plus access control, bug tracking, task management, continuous integration, and wikis. It was developed by Chris Wanstrath, P. J. Hyett, Tom Preston-Werner, and Scott Chacon, and launched in April 2008. GitHub grew from 1 million repositories in 2010 to more than 5 million by January 2013, when it passed 3 million users; by the end of 2013 it hosted 10 million repositories. On June 4, 2018, Microsoft announced it would acquire GitHub for US$7.5 billion; the deal closed on October 26, 2018, and GitHub continued to operate independently. In January 2019 GitHub began offering limited free private repositories, and in April 2020 it made all core features free, including private repositories with unlimited collaborators.</p>
<p>GitHub is not the only forge. The same Git protocol is served by GitLab, Bitbucket, and SourceForge, and self-hosted options like Gitea and Forgejo. You can also run your own Git server — Git ships with <code>git daemon</code>, a simple TCP server for the Git protocol (default port 9418), and repositories can be served over SSH or HTTPS.</p>
</section>
<section><h2 id="how-widely-is-git-used"><a href="#how-widely-is-git-used" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How widely is Git used?</h2><p>Git is the most popular distributed version control system, and the most widely used source-code management tool among professional developers. In Stack Overflow&#39;s annual developer surveys, Git was the overwhelming favorite among respondents who use version control, reporting as high as 93.9% in 2022 — and Wikipedia cites a figure of nearly 95% of developers reporting Git as their primary version control system as of 2022.</p>
</section>
<section><h2 id="git-vs-centralized-systems"><a href="#git-vs-centralized-systems" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Git vs centralized systems</h2><table>
<thead>
<tr>
<th>Property</th>
<th>Git (distributed)</th>
<th>Centralized (e.g. Subversion)</th>
</tr>
</thead>
<tbody><tr>
<td>History location</td>
<td>Full local copy in every clone</td>
<td>Single central server, client working copy</td>
</tr>
<tr>
<td>Network dependency</td>
<td>None for local history; sync when convenient</td>
<td>Server contact needed for history and commits</td>
</tr>
<tr>
<td>Branches</td>
<td>Lightweight named references, near-free to create</td>
<td>Separate lines of development, copy-based tags</td>
</tr>
<tr>
<td>Integrity</td>
<td>SHA-1 content hashing; commit hashes chain history</td>
<td>Global revision numbers, per-revision roots</td>
</tr>
<tr>
<td>Offline work</td>
<td>Full history and commits offline</td>
<td>Limited to the working copy</td>
</tr>
</tbody></table>
<p>Subversion, created by CollabNet in 2000 as an open-source successor to CVS, uses a centralized model: the history lives on one server, revision numbers are global, and clients hold a working copy that talks to the server for most operations. Git&#39;s local full-history model is the fundamental difference — branch, commit, and explore history without asking anyone&#39;s permission.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="is-git-the-same-as-github">Is Git the same as GitHub?</h3><p>No. Git is a version control system — a tool you run locally. GitHub is a proprietary developer platform that hosts Git repositories and adds features like pull requests, issue tracking, and CI. Alternatives include GitLab, Bitbucket, SourceForge, and self-hosted options like Gitea and Forgejo.</p>
<h3 id="is-git-free">Is Git free?</h3><p>Yes. Git is licensed under the GNU General Public License version 2 (GPL-2.0-only, since 11 April 2005, with some parts under compatible licenses). GitHub, the hosting service, is proprietary software that is free for public repositories and for private repositories on its free tier.</p>
<h3 id="do-i-need-a-server-to-use-git">Do I need a server to use Git?</h3><p>No. A Git repository works fully on one machine: <code>git init</code>, commit locally, and every clone is a complete backup of history. A server or hosting service is only needed to share work between people — <code>git daemon</code>, SSH, or HTTPS will do, and forges like GitHub add collaboration features on top.</p>
<h3 id="what-is-a-pull-request">What is a pull request?</h3><p>A pull request is a request by a user to merge a branch into another branch, managed by a hosting service. It is not a feature of Git itself — Git only provides the underlying mechanics of pulling changes between repositories.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>Docker explained: how containers work</title>
      <link>https://debod.id/en/posts/docker-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/docker-explained/</guid>
      <pubDate>Thu, 20 Aug 2026 00:00:00 GMT</pubDate>
      <category>programming</category>
      <category>docker</category>
      <category>containers</category>
      <category>programming</category>
      <description>Docker explained: what containers are, how Docker Engine uses Linux namespaces and cgroups, how images, Dockerfiles, and Docker Compose fit together, and how Docker compares with virtual machines.</description>
      <content:encoded><![CDATA[<section><p>Docker is a set of products that uses operating system-level virtualization to deliver software in packages called containers. It automates the deployment of applications inside lightweight containers, so the same software runs consistently across different machines. The core software that runs and manages these containers is called Docker Engine, and it was first released as open source in 2013.</p></section>
<section><h2 id="what-a-container-is"><a href="#what-a-container-is" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What a container is</h2><p>A container is a standardized, encapsulated environment that runs an application. Containers are isolated from one another and bundle their own software, libraries, and configuration files; they can communicate with each other through well-defined channels. A program running inside a container can only see the container&#39;s contents and the devices assigned to it — not the whole host computer.</p>
<p>Containers are built on OS-level virtualization, a paradigm in which the operating system kernel allows multiple isolated user-space instances to exist on a single machine. On Linux, containers are based on the virtualization, isolation, and resource management mechanisms of the Linux kernel itself, notably Linux namespaces and cgroups. Namespaces isolate an application&#39;s view of the operating environment — process trees, network, user IDs, and mounted file systems — while cgroups limit the resources a container may use, such as memory and CPU.</p>
<p>Because all containers on a machine share the services of a single operating system kernel, they use fewer resources than virtual machines. Docker containers are lightweight: a single server or virtual machine can run several containers simultaneously. A 2018 analysis found that a typical Docker use case involves running eight containers per host, and that a quarter of analyzed organizations run 18 or more per host.</p>
</section>
<section><h2 id="docker-vs-virtual-machines"><a href="#docker-vs-virtual-machines" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Docker vs virtual machines</h2><p>Virtual machines (VMs) run a full guest operating system on top of a hypervisor such as VMware, Hyper-V, or QEMU. OS-level virtualization is different: containers share the host kernel and use the operating system&#39;s normal system call interface, so they avoid the overhead of emulation or an intermediate virtual machine.</p>
<table>
<thead>
<tr>
<th></th>
<th>Containers (Docker)</th>
<th>Virtual machines</th>
</tr>
</thead>
<tbody><tr>
<td>What is replicated</td>
<td>Application + dependencies, sharing the host kernel</td>
<td>Complete guest operating system</td>
</tr>
<tr>
<td>Isolation mechanism</td>
<td>Linux namespaces + cgroups (kernel features)</td>
<td>Hypervisor (VMware, Hyper-V, QEMU)</td>
</tr>
<tr>
<td>Performance overhead</td>
<td>Low — normal system calls, no emulation</td>
<td>Higher — full OS boot and system calls through a hypervisor</td>
</tr>
<tr>
<td>Flexibility</td>
<td>Shares the host kernel: cannot run a guest OS different from the host</td>
<td>Can run a different guest OS (e.g. Windows on Linux)</td>
</tr>
<tr>
<td>Startup time</td>
<td>Fast — process-level start</td>
<td>Slower — full OS boot</td>
</tr>
</tbody></table>
<p>The trade-off is flexibility: OS-level virtualization cannot host a guest operating system different from the host one. With Linux, different distributions work fine, but an OS such as Windows cannot be hosted as a container on a Linux kernel. Docker on macOS works around this by running a Linux virtual machine to host the containers. Windows, for its part, has native support for running Linux containers through the Windows Subsystem for Linux (WSL 2).</p>
</section>
<section><h2 id="the-history-of-docker"><a href="#the-history-of-docker" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The history of Docker</h2><p>Docker began as an internal project within dotCloud, a platform-as-a-service company. Solomon Hykes started the project in France; dotCloud itself was founded by Kamel Founadi, Hykes, and Sebastien Pahl during the Y Combinator Summer 2010 startup incubator, launched in 2011, and was renamed Docker Inc. in 2013.</p>
<p>Docker debuted to the public at PyCon in Santa Clara in 2013 and was released as open source in March 2013. At the time it used LXC as its default execution environment; one year later, with version 0.9, Docker replaced LXC with its own component, libcontainer, written in the Go programming language.</p>
<p>Adoption followed quickly. In September 2013 Red Hat and Docker announced a collaboration around Fedora and Red Hat Enterprise Linux; in October 2014 Microsoft announced the integration of the Docker engine into Windows Server; in June 2015 Docker and several other companies announced work on a vendor- and operating-system-independent standard for software containers, which became the Open Container Initiative. Docker for Mac and Windows betas arrived in March 2016, and native Docker use on Windows 10 followed in June 2016.</p>
</section>
<section><h2 id="how-docker-is-put-together-daemon-client-images-containers"><a href="#how-docker-is-put-together-daemon-client-images-containers" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How Docker is put together: daemon, client, images, containers</h2><p>Docker has three main parts, plus a registry:</p>
<ul>
<li><strong>Daemon</strong> — <code>dockerd</code> is a persistent process that manages Docker containers and handles container objects. It listens for requests sent via the Docker Engine API.</li>
<li><strong>Client</strong> — the <code>docker</code> command-line interface lets users interact with Docker daemons.</li>
<li><strong>Objects</strong> — the entities used to assemble an application: <em>images</em>, <em>containers</em>, and <em>services</em>. An image is a read-only template used to build containers; images are used to store and ship applications. A container is a running instance created from an image.</li>
<li><strong>Registries</strong> — a registry is a repository for images. Docker clients connect to registries to pull images for use or push images they have built. Registries can be public or private; the main public registry and Docker&#39;s default is Docker Hub.</li>
</ul>
<p>In day-to-day use the flow is: build an image from a Dockerfile, push it to a registry, then run containers from that image on any machine with Docker installed. Because the image already bundles the application and its dependencies, the same image runs the same way in development, staging, and production.</p>
</section>
<section><h2 id="dockerfiles-images-as-text"><a href="#dockerfiles-images-as-text" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Dockerfiles: images as text</h2><p>A Dockerfile is a text file that specifies what goes into an image: the base Linux distribution, installation commands for the programming language runtime and application source code. Because the image definition is plain text, it can be version-controlled and reviewed like any other code.</p>
<pre><code class="language-dockerfile">FROM ubuntu:24.04
COPY ./examplefile.txt /examplefile.txt
ENV MY_ENV_VARIABLE=&quot;example_value&quot;
RUN apt-get update
# Mount a directory from the Docker volume
VOLUME [&quot;/myvolume&quot;]
# Expose a port (22 for SSH)
EXPOSE 22
</code></pre>
<p>Each instruction creates a layer; Docker reuses unchanged layers from its local cache, which is why rebuilding an image after a small change is fast.</p>
</section>
<section><h2 id="docker-compose-multi-container-applications"><a href="#docker-compose-multi-container-applications" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Docker Compose: multi-container applications</h2><p>Docker Compose is a tool for defining and running multi-container Docker applications. It uses YAML files to configure the application&#39;s services and performs the creation and start-up of all the containers with a single command. The <code>docker-compose.yml</code> file declares services with options such as <code>build</code> (the Dockerfile path) and <code>command</code> (overriding default container commands).</p>
<p>The first public beta of Docker Compose was released on December 21, 2013; the first production-ready version 1.0 followed on October 16, 2014.</p>
</section>
<section><h2 id="orchestration-docker-swarm-and-beyond"><a href="#orchestration-docker-swarm-and-beyond" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Orchestration: Docker Swarm and beyond</h2><p>Docker Swarm provides native clustering functionality for Docker containers, turning a group of Docker engines into a single virtual Docker engine. Since Docker 1.12, Swarm mode is integrated with Docker Engine. Swarms are managed with the Raft consensus algorithm: for an update to be performed, the majority of swarm nodes need to agree on it.</p>
<p>Swarm is one of several orchestration tools. Kubernetes, originally developed by Google, became the dominant container orchestration platform and runs containers in pods across clusters of machines. Docker and Kubernetes are complementary in practice: Docker builds and runs the containers, Kubernetes schedules and manages them at scale.</p>
</section>
<section><h2 id="volumes-keeping-data-when-containers-die"><a href="#volumes-keeping-data-when-containers-die" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Volumes: keeping data when containers die</h2><p>Containers are ephemeral by design — files written inside a container disappear when it is removed. Docker Volumes solve this by separating data from the container lifecycle: a volume persists independently, so data remains even after the container is deleted or re-created.</p>
</section>
<section><h2 id="licensing-and-pricing"><a href="#licensing-and-pricing" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Licensing and pricing</h2><p>Docker Engine is licensed under the Apache License 2.0. Docker Desktop, the GUI application for macOS and Windows, distributes some components under the GNU General Public License and is not free for large enterprises: since August 2021, Docker Desktop requires a paid subscription for larger business customers, replacing the free plan with a Personal Plan. Docker Engine on Linux distributions remained free.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="is-docker-a-virtual-machine">Is Docker a virtual machine?</h3><p>No. Docker runs containers that share the host operating system kernel, using Linux namespaces and cgroups for isolation. Virtual machines run a complete guest operating system on a hypervisor, which costs more resources but can run a different OS.</p>
<h3 id="is-docker-free">Is Docker free?</h3><p>Docker Engine and the Docker CLI are open source under the Apache License 2.0 and free to use. Docker Desktop is free for individuals and small businesses (Personal Plan), but requires a paid subscription for larger enterprises.</p>
<h3 id="what-is-the-difference-between-an-image-and-a-container">What is the difference between an image and a container?</h3><p>An image is a read-only template — the packaged application and its dependencies. A container is a running instance created from that image. You can build one image and run many containers from it.</p>
<h3 id="what-is-docker-hub">What is Docker Hub?</h3><p>Docker Hub is the main public registry for Docker images: Docker&#39;s default place where images are pulled from and pushed to. Registries can also be private, for proprietary images.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>What are WebSockets? A plain explanation</title>
      <link>https://debod.id/en/posts/websocket-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/websocket-explained/</guid>
      <pubDate>Wed, 19 Aug 2026 00:00:00 GMT</pubDate>
      <category>networking</category>
      <category>networking</category>
      <category>protocols</category>
      <category>api</category>
      <description>WebSockets keep a two-way connection between browser and server open, so either side can send data any time. Here&apos;s how the handshake, frames, and close codes work — and when to use them.</description>
      <content:encoded><![CDATA[<section><p>WebSockets are a protocol for full-duplex communication over a single TCP connection: once connected, both the browser and the server can send messages any time, without the other side asking. The protocol was standardized by the IETF in December 2011 as RFC 6455, and it is what powers chat apps, live dashboards, multiplayer games and collaborative editing tools.</p></section>
<section><h2 id="the-problem-http-is-half-duplex"><a href="#the-problem-http-is-half-duplex" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The problem: HTTP is half-duplex</h2><p>Normal HTTP is request-response: the browser asks, the server answers, the connection is done. The server can never send something first. If a page needs live data — a chat message, a stock price, a game move — older sites had to fake it by polling: asking the server &quot;anything new?&quot; every few seconds, wasting requests even when nothing changed. More elaborate workarounds (long polling, Comet, Flash sockets) existed, but none was a clean standard.</p>
</section>
<section><h2 id="the-handshake-http-says-switch"><a href="#the-handshake-http-says-switch" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The handshake: HTTP says &quot;switch&quot;</h2><p>A WebSocket connection starts life as an ordinary HTTP request. The client sends a <code>GET</code> request with special headers:</p>
<ul>
<li><code>Upgrade: websocket</code> and <code>Connection: Upgrade</code> — asking to switch protocols</li>
<li><code>Sec-WebSocket-Key</code> — a base64-encoded 16-byte random value</li>
<li><code>Sec-WebSocket-Version: 13</code> — the protocol version</li>
<li><code>Origin</code> — the page&#39;s origin, sent by all browser clients</li>
</ul>
<p>If the server agrees, it answers with <code>HTTP/1.1 101 Switching Protocols</code> and a <code>Sec-WebSocket-Accept</code> header derived from the key. Any other status code means the handshake failed and no WebSocket connection exists. The protocol is designed to run over ports 80 and 443 and to work through HTTP proxies and intermediaries — which is why it can reach users behind firewalls that block everything else.</p>
</section>
<section><h2 id="the-connection-small-frames-both-directions"><a href="#the-connection-small-frames-both-directions" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The connection: small frames, both directions</h2><p>After the handshake, both sides talk in frames: a tiny header (FIN bit, opcode, masking bit, payload length) plus the payload. Text and binary are the two main frame types, and a message can be split across several frames. Two details matter in practice:</p>
<ul>
<li><strong>Masking</strong> — every frame a client sends to the server must be masked with a fresh random key. This protects web infrastructure: without it, a malicious script could craft data that looks like an HTTP request and poison caching proxies.</li>
<li><strong>Control frames</strong> — close, ping and pong frames are capped at 125 bytes of payload and never fragmented. A ping must be answered with a pong as soon as practical, which is how connections stay alive through idle periods and proxies.</li>
</ul>
</section>
<section><h2 id="closing-the-connection"><a href="#closing-the-connection" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Closing the connection</h2><p>Either side can end the conversation by sending a Close frame with a status code. The common ones:</p>
<table>
<thead>
<tr>
<th>Code</th>
<th>Meaning</th>
</tr>
</thead>
<tbody><tr>
<td>1000</td>
<td>Normal closure — purpose fulfilled</td>
</tr>
<tr>
<td>1001</td>
<td>Going away — server shutting down, page navigated away</td>
</tr>
<tr>
<td>1002</td>
<td>Protocol error</td>
</tr>
<tr>
<td>1003</td>
<td>Received data it cannot accept (e.g. binary to a text-only endpoint)</td>
</tr>
<tr>
<td>1009</td>
<td>Message too big to process</td>
</tr>
<tr>
<td>1011</td>
<td>Unexpected condition on the server</td>
</tr>
</tbody></table>
<p>Both sides exchange Close frames, then the TCP connection is closed — by the server first in the normal case, so the server (not the client) holds the <code>TIME_WAIT</code> state.</p>
</section>
<section><h2 id="security-wss-and-the-origin-check"><a href="#security-wss-and-the-origin-check" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Security: wss:// and the Origin check</h2><p>WebSocket URLs come in two flavors: <code>ws://</code> (plain) and <code>wss://</code> (encrypted, WebSocket over TLS — the same protection HTTPS gives HTTP). Use <code>wss://</code> anywhere the data matters.</p>
<p>Two safeguards are built into the protocol. First, the server should verify the <code>Origin</code> header and reject handshakes from origins it does not expect (a <code>403 Forbidden</code> reply). That blocks other websites from abusing the connection. Second, masking keeps scripts from weaponizing proxies. One caveat: non-browser clients can send any <code>Origin</code> they like, so a server must treat all incoming data as untrusted and validate it like any other input. Authentication itself is not part of the protocol — servers use the usual HTTP mechanisms: cookies, HTTP authentication, or TLS client certificates.</p>
</section>
<section><h2 id="when-to-use-websockets-and-when-not"><a href="#when-to-use-websockets-and-when-not" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>When to use WebSockets — and when not</h2><p>Use them when you need low-latency two-way exchange: chat, live sports scores or stock tickers, multiplayer games, real-time dashboards, collaborative editing. Don&#39;t reach for them when HTTP does the job — regular pages, occasional updates (caching works fine), or one-way server-to-client streams, where a simpler mechanism often fits better. WebSockets keep a connection open per session, which costs server resources and needs careful handling of disconnects and reconnects.</p>
</section>
<section><h2 id="a-bit-of-history"><a href="#a-bit-of-history" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>A bit of history</h2><p>The name &quot;WebSocket&quot; was coined by Ian Hickson and Michael Carter in 2008, and the protocol was authored for inclusion in the HTML5 specification. Google Chrome 4 was the first browser to ship it enabled by default in December 2009. Development moved to the IETF in February 2010, and RFC 6455 was finalized in December 2011 under Ian Fette. Early protocol drafts had security flaws and were disabled in Firefox 4 and 5 and Opera 11. On the server side, nginx added support in version 1.3.13 (2013), Apache in 2.4.5 (July 2013), and lighttpd in 1.4.46 (2017).</p>
</section>
<section><h2 id="summary"><a href="#summary" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Summary</h2><p>A WebSocket is a full-duplex channel over one TCP connection: an HTTP handshake upgrades the link, then small frames flow both ways until a Close frame ends it. It is the standard answer when a web page needs the server to push data live — and with <code>wss://</code>, origin checks, and masking, the protocol was built with the web&#39;s security model in mind.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-problem-do-websockets-solve-that-http-can-t">What problem do WebSockets solve that HTTP can&#39;t?</h3><p>Plain HTTP is request-response: the client asks, the server replies, then the connection ends — the server can never send first. WebSockets keep one connection open so either side can push data at any time, removing the need to fake &quot;live&quot; updates with polling.</p>
<h3 id="is-websocket-the-same-as-http">Is WebSocket the same as HTTP?</h3><p>No. A WebSocket connection <em>starts</em> as an HTTP request (the <code>101 Switching Protocols</code> handshake), then upgrades to a separate protocol that runs over the same TCP connection. After that, it is no longer HTTP — it sends small bidirectional frames instead of request/response pairs.</p>
<h3 id="is-wss-necessary">Is <code>wss://</code> necessary?</h3><p>Use <code>wss://</code> (WebSocket over TLS) whenever the data matters. Like HTTPS for HTTP, it encrypts the traffic so it cannot be read or tampered with on the wire. <code>ws://</code> is plain text and should only be used for throwaway, non-sensitive data.</p>
<h3 id="can-any-website-open-a-websocket-to-my-server">Can any website open a WebSocket to my server?</h3><p>Not freely. The server should check the <code>Origin</code> header and reject handshakes from origins it does not expect (a <code>403</code>). But non-browser clients can send any <code>Origin</code>, so the server must still validate every incoming message as untrusted input — the protocol itself does not authenticate the peer.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>localStorage vs sessionStorage vs cookies: which one to use?</title>
      <link>https://debod.id/en/posts/web-storage-vs-cookies/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/web-storage-vs-cookies/</guid>
      <pubDate>Wed, 19 Aug 2026 00:00:00 GMT</pubDate>
      <category>web</category>
      <category>web</category>
      <category>cookies</category>
      <category>javascript</category>
      <description>The practical differences between localStorage, sessionStorage and cookies — capacity, lifetime, security and when to pick each one.</description>
      <content:encoded><![CDATA[<section><p>For data that lives in the browser, there are three classic options: localStorage, sessionStorage and cookies. In short: localStorage for data that must survive restarts and tabs, sessionStorage for temporary per-tab data, cookies for data the server needs on every request. This article compares them side by side.</p></section>
<section><h2 id="the-quick-comparison"><a href="#the-quick-comparison" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The quick comparison</h2><table>
<thead>
<tr>
<th>Property</th>
<th>localStorage</th>
<th>sessionStorage</th>
<th>Cookies</th>
</tr>
</thead>
<tbody><tr>
<td>Lifetime</td>
<td>Until cleared</td>
<td>Until the tab closes</td>
<td>Until expiry (or the session ends)</td>
</tr>
<tr>
<td>Scope</td>
<td>Per origin, shared across tabs</td>
<td>Per tab + origin</td>
<td>Per domain (+ path)</td>
</tr>
<tr>
<td>Sent to the server automatically</td>
<td>No</td>
<td>No</td>
<td>Yes, with every request</td>
</tr>
<tr>
<td>Capacity</td>
<td>~5 MiB per origin</td>
<td>~5 MiB per origin</td>
<td>~4 KB per cookie</td>
</tr>
<tr>
<td>Accessible from JavaScript</td>
<td>Yes</td>
<td>Yes</td>
<td>Yes, unless <code>HttpOnly</code></td>
</tr>
<tr>
<td>Blocking behavior</td>
<td>Synchronous</td>
<td>Synchronous</td>
<td>n/a (HTTP headers)</td>
</tr>
</tbody></table>
<p>MDN documents the storage limits: browsers allow up to roughly 5 MiB of local storage and 5 MiB of session storage per origin, and total browser storage is capped around 10 MiB. When the limit is reached, writes throw a <code>QuotaExceededError</code>. Cookies, by contrast, are commonly limited to about 4 KB per cookie and a few hundred cookies per domain.</p>
</section>
<section><h2 id="what-each-one-is"><a href="#what-each-one-is" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What each one is</h2><h3 id="localstorage">localStorage</h3><p><code>localStorage</code> is a key/value store that belongs to one origin — the scheme, host and port of the page. All documents with the same origin share the same <code>localStorage</code> area, and the data survives closing the browser entirely. It is the right place for preferences, saved themes or cached data you want to reuse on the next visit.</p>
<p>Keys and values are always strings. To store objects or arrays, convert them with <code>JSON.stringify()</code> first:</p>
<pre><code class="language-js">localStorage.setItem(&quot;theme&quot;, &quot;dark&quot;);
const user = { name: &quot;Ari&quot; };
localStorage.setItem(&quot;user&quot;, JSON.stringify(user));
const saved = JSON.parse(localStorage.getItem(&quot;user&quot;));
</code></pre>
<h3 id="sessionstorage">sessionStorage</h3><p><code>sessionStorage</code> is identical in API, but it is partitioned per browser tab. When the tab closes, everything stored in that tab&#39;s <code>sessionStorage</code> is destroyed. Refresh the same tab and the data is still there — open the same page in a new tab and it starts empty. Use it for state that only matters while the user is working on the page: draft input, a collapse state, a temporary filter.</p>
<pre><code class="language-js">sessionStorage.setItem(&quot;draft&quot;, &quot;half-written comment&quot;);
</code></pre>
<h3 id="cookies">Cookies</h3><p>A cookie is a small piece of text the server sets with a <code>Set-Cookie</code> header, which the browser then sends back with every request to that domain. Unlike the storage APIs, cookies travel to the server automatically — that is their whole purpose: session IDs, authentication, tracking consent.</p>
<p>Cookies also have attributes that the storage APIs do not need: <code>Secure</code> (only send over HTTPS), <code>HttpOnly</code> (hide from JavaScript, for example to protect a session ID from cross-site scripting), <code>SameSite</code> (control when a cookie is sent on cross-site requests, which mitigates CSRF), and an expiry.</p>
</section>
<section><h2 id="which-one-should-you-use"><a href="#which-one-should-you-use" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Which one should you use?</h2><ul>
<li><strong>Data the server must read on each request</strong> (session ID, auth token, tracking consent) → cookie.</li>
<li><strong>Data that must survive restarts and be shared across tabs of the same site</strong> (settings, cached UI state) → <code>localStorage</code>.</li>
<li><strong>Temporary data that should die with the tab</strong> (form drafts, one-off UI state) → <code>sessionStorage</code>.</li>
</ul>
<p>A common pattern: a session cookie for authentication, <code>localStorage</code> for user preferences, <code>sessionStorage</code> for in-tab draft state.</p>
</section>
<section><h2 id="performance-and-security-notes"><a href="#performance-and-security-notes" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Performance and security notes</h2><ul>
<li>Every cookie is sent with every request to its domain, so large or numerous cookies add bytes to every page load. Keep them small.</li>
<li><code>localStorage</code> and <code>sessionStorage</code> are synchronous: a large read or write blocks the main thread and can make the page feel sluggish. Keep stored data small.</li>
<li>Never store sensitive data — passwords, access tokens, personal information — in <code>localStorage</code>: any script running on the page can read it (a single XSS vulnerability exposes everything). Sensitive server-side values belong in <code>HttpOnly</code> cookies.</li>
<li>Storage APIs are per-origin, so data is readable by any script on the same origin. Cookies can be scoped further with <code>Path</code> and are protected from JavaScript when <code>HttpOnly</code> is set.</li>
</ul>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="when-should-i-use-a-cookie-instead-of-localstorage-or-sessionstorage">When should I use a cookie instead of localStorage or sessionStorage?</h3><p>Use a cookie when the server must read the data on every request, because cookies are sent automatically with every HTTP request to their domain, while <code>localStorage</code> and <code>sessionStorage</code> are never sent to the server. Authentication session IDs, &quot;remember me&quot; tokens and consent flags are typical cookie use cases; UI preferences and cached app state are not.</p>
<h3 id="why-does-localstorage-survive-a-browser-restart-but-sessionstorage-does-not">Why does <code>localStorage</code> survive a browser restart but <code>sessionStorage</code> does not?</h3><p>The two APIs are partitioned differently. <code>localStorage</code> is scoped per origin and the data persists until the site or the user clears it, even across browser restarts and across all tabs of the same origin. <code>sessionStorage</code> is scoped per browser tab: closing the tab destroys everything stored in it, even if the same site is still open in another tab. Both APIs share the per-origin ~5 MiB quota and the ~10 MiB total browser storage cap.</p>
<h3 id="is-it-safe-to-store-a-session-token-in-localstorage">Is it safe to store a session token in <code>localStorage</code>?</h3><p>No. <code>localStorage</code> is readable by any JavaScript running on the same origin, so a single cross-site scripting (XSS) vulnerability lets an attacker read everything stored there and exfiltrate the token. Sensitive server-side values such as session IDs belong in cookies marked <code>HttpOnly</code>, which makes them invisible to JavaScript, and <code>Secure</code> so they are only sent over HTTPS, plus <code>SameSite</code> to mitigate cross-site request forgery (CSRF).</p>
<h3 id="are-localstorage-and-sessionstorage-synchronous">Are <code>localStorage</code> and <code>sessionStorage</code> synchronous?</h3><p>Yes. Both APIs block the main thread during reads and writes, so storing or retrieving large values can make the page feel sluggish and harm Core Web Vitals. Cookies, by contrast, travel as HTTP headers and do not block JavaScript. Keep stored values small, and if you need to store more than a few hundred kilobytes, prefer the asynchronous <code>IndexedDB</code> API instead.</p>
</section>
<section><h2 id="summary"><a href="#summary" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Summary</h2><p>Use cookies for anything the server must see on every request, <code>localStorage</code> for preferences that outlive the browser session, and <code>sessionStorage</code> for throwaway per-tab state. All three are capped — keep values small, keep secrets out of <code>localStorage</code>, and prefer <code>HttpOnly</code> cookies for authentication data.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>TLS versions explained: from SSL 3.0 to TLS 1.3</title>
      <link>https://debod.id/en/posts/tls-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/tls-explained/</guid>
      <pubDate>Wed, 19 Aug 2026 00:00:00 GMT</pubDate>
      <category>security</category>
      <category>tls</category>
      <category>https</category>
      <category>security</category>
      <category>encryption</category>
      <category>protocols</category>
      <description>Every TLS version from SSL 3.0 to TLS 1.3 explained — what changed, what broke, and why old versions are now disabled by default.</description>
      <content:encoded><![CDATA[<section><p>TLS (Transport Layer Security) is the cryptographic protocol that secures HTTPS connections. It has been through several versions over three decades, and each one fixed real, exploitable flaws in the one before. This article walks through them — what each version changed, what broke it, and where the protocol stands today.</p></section>
<section><h2 id="what-tls-actually-does"><a href="#what-tls-actually-does" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What TLS actually does</h2><p>TLS provides a secure channel between two peers with three properties: <strong>authentication</strong> (the server proves its identity), <strong>confidentiality</strong> (nobody in the middle can read the traffic), and <strong>integrity</strong> (data cannot be modified without detection). It has two main parts: a handshake protocol that authenticates the peers and negotiates encryption parameters, and a record protocol that protects the traffic that follows.</p>
</section>
<section><h2 id="the-ssl-era-1-0-2-0-3-0"><a href="#the-ssl-era-1-0-2-0-3-0" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The SSL era: 1.0, 2.0, 3.0</h2><p>Netscape built the original SSL protocols in the 1990s. SSL 1.0 was never publicly released because of serious security flaws. SSL 2.0 (February 1995) was quickly found broken too: it used the same keys for authentication and encryption, its MAC construction was vulnerable to length-extension attacks, and the handshake itself was unprotected. SSL 3.0 (1996) was a complete redesign that fixed these flaws, and it became the basis of everything that followed.</p>
<p>SSL 3.0 stayed in use for decades — too long. In October 2014, Google researchers published the POODLE attack, which exploited SSL 3.0&#39;s CBC-mode padding: on average, 256 requests revealed one byte of encrypted data. Because browsers downgraded to SSL 3.0 when newer versions failed, the flaw was practical. In June 2015, the IETF formally declared SSL 3.0 insecure (RFC 7568).</p>
</section>
<section><h2 id="tls-1-0-the-ssl-rename-1999"><a href="#tls-1-0-the-ssl-rename-1999" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>TLS 1.0 — the SSL rename (1999)</h2><p>TLS 1.0 (RFC 2246, January 1999) was an upgrade of SSL 3.0, standardized by the IETF and renamed from &quot;SSL&quot; to &quot;TLS&quot;. The changes were significant enough that TLS 1.0 and SSL 3.0 could not interoperate. Its weakness: the CBC cipher modes it used were vulnerable to a chosen-plaintext attack. That was demonstrated publicly in 2011 as the BEAST attack, which could decrypt cookies from a TLS 1.0 connection. The fix arrived with the next version.</p>
</section>
<section><h2 id="tls-1-1-patching-cbc-2006"><a href="#tls-1-1-patching-cbc-2006" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>TLS 1.1 — patching CBC (2006)</h2><p>TLS 1.1 (RFC 4346, April 2006) was a modest update: it replaced the implicit initialization vector with an explicit one and changed how padding errors were handled, closing the class of CBC attacks that BEAST later exploited. It never saw wide adoption on its own — most sites went from TLS 1.0 straight to TLS 1.2.</p>
</section>
<section><h2 id="tls-1-2-the-long-workhorse-2008"><a href="#tls-1-2-the-long-workhorse-2008" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>TLS 1.2 — the long workhorse (2008)</h2><p>TLS 1.2 (RFC 5246, August 2008) was the version that carried the web for a decade. It replaced the MD5/SHA-1 combination with SHA-256 in its key derivation, and — most importantly — added support for authenticated encryption (AEAD) ciphers such as AES-GCM. These ciphers encrypt and authenticate in one step, eliminating the padding-oracle weaknesses of CBC. TLS 1.2 is still the practical minimum today: RFC 8996 moved TLS 1.0 and 1.1 to Historic status, so servers are expected to support at least 1.2.</p>
</section>
<section><h2 id="tls-1-3-faster-and-safer-2018"><a href="#tls-1-3-faster-and-safer-2018" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>TLS 1.3 — faster and safer (2018)</h2><p>TLS 1.3 (RFC 8446, August 2018) was a major redesign. Key changes:</p>
<ul>
<li><strong>One round trip instead of two</strong> — a full handshake completes in 1-RTT, with a 0-RTT option for resuming clients.</li>
<li><strong>Forward secrecy is mandatory</strong> — static RSA and Diffie-Hellman key exchanges were removed; every key exchange now uses ephemeral keys, so compromising a server&#39;s long-term key cannot decrypt past sessions.</li>
<li><strong>Only AEAD ciphers remain</strong> — legacy algorithms, compression, renegotiation and non-AEAD ciphers were dropped. Every implementation must support <code>TLS_AES_128_GCM_SHA256</code>.</li>
<li><strong>The handshake is encrypted</strong> — all messages after the ServerHello are protected, so certificates and client data are no longer sent in the clear.</li>
</ul>
</section>
<section><h2 id="the-deprecation-timeline"><a href="#the-deprecation-timeline" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The deprecation timeline</h2><ul>
<li><strong>2015</strong> — SSL 3.0 declared insecure (RFC 7568).</li>
<li><strong>2016</strong> — DROWN shows SSLv2 support can break modern TLS connections; over 81,000 of the top 1 million websites were vulnerable.</li>
<li><strong>June 2018</strong> — PCI Council deadline to move off TLS 1.0.</li>
<li><strong>October 2018</strong> — Apple, Google, Microsoft and Mozilla jointly announce TLS 1.0/1.1 deprecation.</li>
<li><strong>March 2020</strong> — major browsers remove TLS 1.0 and 1.1 support.</li>
<li><strong>March 2021</strong> — IETF formally deprecates TLS 1.0 and 1.1 (RFC 8996).</li>
</ul>
</section>
<section><h2 id="version-table"><a href="#version-table" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Version table</h2><table>
<thead>
<tr>
<th>Version</th>
<th>Year</th>
<th>Key change</th>
<th>Status today</th>
</tr>
</thead>
<tbody><tr>
<td>SSL 2.0</td>
<td>1995</td>
<td>First public SSL</td>
<td>Insecure, removed</td>
</tr>
<tr>
<td>SSL 3.0</td>
<td>1996</td>
<td>Redesigned protocol</td>
<td>Insecure (RFC 7568)</td>
</tr>
<tr>
<td>TLS 1.0</td>
<td>1999</td>
<td>IETF standardization</td>
<td>Deprecated (RFC 8996)</td>
</tr>
<tr>
<td>TLS 1.1</td>
<td>2006</td>
<td>CBC protections</td>
<td>Deprecated (RFC 8996)</td>
</tr>
<tr>
<td>TLS 1.2</td>
<td>2008</td>
<td>SHA-256 + AEAD ciphers</td>
<td>Baseline minimum</td>
</tr>
<tr>
<td>TLS 1.3</td>
<td>2018</td>
<td>1-RTT, mandatory forward secrecy</td>
<td>Recommended</td>
</tr>
</tbody></table>
</section>
<section><h2 id="what-it-means-today"><a href="#what-it-means-today" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What it means today</h2><p>All major browsers support TLS 1.2 and 1.3 by default, and disable 1.0 and 1.1. If you run a server, the practical rule is: support TLS 1.2 and 1.3, prefer 1.3, and disable everything older. You can check which version a site uses in your browser&#39;s developer tools — the Security panel shows the TLS version of the current connection.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-is-the-difference-between-tls-and-ssl">What is the difference between TLS and SSL?</h3><p>SSL (Secure Sockets Layer) was the original protocol developed by Netscape in the 1990s; TLS (Transport Layer Security) is the IETF-standardized successor. TLS 1.0 (RFC 2246, 1999) was technically an upgrade of SSL 3.0 but renamed to mark the new stewardship. The name &#39;SSL&#39; stuck in everyday language — many people still say &#39;SSL certificate&#39; even when the protocol in use is TLS 1.2 or 1.3.</p>
<h3 id="is-tls-1-2-still-safe-to-use-in-2026">Is TLS 1.2 still safe to use in 2026?</h3><p>Yes. RFC 8996 moved TLS 1.0 and 1.1 to Historic status, so the practical minimum is TLS 1.2. TLS 1.2 with AEAD ciphers such as AES-GCM is still considered secure and is what most servers negotiate as a fallback. If you control a server, prefer TLS 1.3 but keep TLS 1.2 enabled for compatibility.</p>
<h3 id="why-is-tls-1-3-faster-than-tls-1-2">Why is TLS 1.3 faster than TLS 1.2?</h3><p>TLS 1.3 reduces the handshake from two round-trips to one (1-RTT), and supports 0-RTT for resumed sessions. It also encrypts all handshake messages after the ServerHello, so certificate and key material never travel in the clear. The result is a visibly faster first connection and a smaller attack surface during the handshake.</p>
<h3 id="what-does-it-mean-when-a-server-still-offers-tls-1-0-or-tls-1-1">What does it mean when a server still offers TLS 1.0 or TLS 1.1?</h3><p>It means the server has not been updated to follow RFC 8996 (March 2021), which deprecates both. Major browsers have removed support, so most users cannot connect anyway. If a service you depend on still negotiates these versions, treat it as a security signal and look for an alternative.</p>
</section>
<section><h2 id="summary"><a href="#summary" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Summary</h2><p>Each TLS version fixed the flaws of its predecessor: SSL 3.0 redesigned SSL 2.0, TLS 1.1 patched the CBC weaknesses of TLS 1.0, TLS 1.2 brought modern authenticated encryption, and TLS 1.3 made handshakes faster and forward secrecy mandatory. Old versions are gone for a reason — if a server still offers them, that is a sign to look elsewhere.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>JWT explained: what a JSON Web Token is and how it works</title>
      <link>https://debod.id/en/posts/jwt-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/jwt-explained/</guid>
      <pubDate>Wed, 19 Aug 2026 00:00:00 GMT</pubDate>
      <category>security</category>
      <category>jwt</category>
      <category>authentication</category>
      <category>security</category>
      <category>api</category>
      <description>What a JSON Web Token is, how the header.payload.signature structure works, which claims and algorithms exist, and how to validate JWTs safely.</description>
      <content:encoded><![CDATA[<section><p>A JSON Web Token (JWT, pronounced like the word &quot;jot&quot;) is a compact, URL-safe string that carries a set of claims — statements about a subject — between two parties, signed so the receiver can verify who wrote it. In practice you meet JWTs as the tokens your API returns after login: the client sends one back on every request, and the server accepts it because the signature proves it was issued by someone holding the right key.</p></section>
<section><h2 id="anatomy-three-parts-two-dots"><a href="#anatomy-three-parts-two-dots" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Anatomy: three parts, two dots</h2><p>A signed JWT uses the JWS Compact Serialization: three base64url-encoded parts joined by periods, in the order <code>header.payload.signature</code>. Base64url encoding is like base64 but uses <code>-</code> and <code>_</code> instead of <code>+</code> and <code>/</code>, and omits the padding <code>=</code> — which is what keeps the token safe to put in HTTP headers and URLs.</p>
<pre><code>&lt;header&gt;.&lt;payload&gt;.&lt;signature&gt;
</code></pre>
<table>
<thead>
<tr>
<th>Part</th>
<th>What it contains</th>
<th>Example (decoded)</th>
</tr>
</thead>
<tbody><tr>
<td>Header</td>
<td>The algorithm (<code>alg</code>) used to sign, plus an optional type (<code>typ</code>)</td>
<td><code>{&quot;alg&quot;:&quot;HS256&quot;,&quot;typ&quot;:&quot;JWT&quot;}</code></td>
</tr>
<tr>
<td>Payload</td>
<td>The claims — the actual data the token asserts</td>
<td><code>{&quot;iss&quot;:&quot;debod-id&quot;,&quot;sub&quot;:&quot;user-42&quot;,&quot;exp&quot;:1787091072}</code></td>
</tr>
<tr>
<td>Signature</td>
<td>The result of signing the encoded header + payload with the key</td>
<td>43-char base64url value (HS256)</td>
</tr>
</tbody></table>
<p>A real token looks like the example in RFC 7519 itself (sections abbreviated with <code>...</code> as in the RFC, and one line break shown for display only):</p>
<pre><code>eyJ0eX...NiJ9.eyJpc3...eGFt
cGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
</code></pre>
<p>The signature is computed over the <em>encoded</em> header and payload — the two strings joined with a <code>.</code> — not over the decoded JSON. Change one byte of the payload and every future signature check fails.</p>
</section>
<section><h2 id="the-payload-is-not-secret"><a href="#the-payload-is-not-secret" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The payload is not secret</h2><p>Base64url is encoding, not encryption. Anyone who can read the token can decode the payload and see every claim in plain text. The RFC is explicit on this: a JWT may contain privacy-sensitive information, and when it does, measures must be taken to prevent its disclosure. That is why sensitive data (passwords, personal identifiers) must never go into a plain signed JWT — use encrypted JWTs (JWE) for that.</p>
</section>
<section><h2 id="the-seven-registered-claims"><a href="#the-seven-registered-claims" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The seven registered claims</h2><p>The JWT specification defines seven standard claim names. None of them are mandatory in every case; applications decide which they use.</p>
<table>
<thead>
<tr>
<th>Claim</th>
<th>Name</th>
<th>Meaning</th>
</tr>
</thead>
<tbody><tr>
<td><code>iss</code></td>
<td>Issuer</td>
<td>The principal that issued the token</td>
</tr>
<tr>
<td><code>sub</code></td>
<td>Subject</td>
<td>The principal the claims are about</td>
</tr>
<tr>
<td><code>aud</code></td>
<td>Audience</td>
<td>The recipients the token is intended for</td>
</tr>
<tr>
<td><code>exp</code></td>
<td>Expiration time</td>
<td>Time on or after which the token must not be accepted</td>
</tr>
<tr>
<td><code>nbf</code></td>
<td>Not before</td>
<td>Time before which the token must not be accepted</td>
</tr>
<tr>
<td><code>iat</code></td>
<td>Issued at</td>
<td>When the token was issued</td>
</tr>
<tr>
<td><code>jti</code></td>
<td>JWT ID</td>
<td>Unique identifier, usable to prevent replay</td>
</tr>
</tbody></table>
<p>Two of these drive most real-world behavior. <code>exp</code> and <code>nbf</code> are timestamps as <em>NumericDate</em> values: seconds since 1970-01-01 00:00:00 UTC, ignoring leap seconds. <code>aud</code> is strict — a recipient that does not identify itself with a value in the audience claim must reject the token.</p>
</section>
<section><h2 id="which-algorithms-sign-the-token"><a href="#which-algorithms-sign-the-token" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Which algorithms sign the token</h2><p>The <code>alg</code> value in the header names the signing algorithm. The three you will meet most are defined in RFC 7518:</p>
<table>
<thead>
<tr>
<th><code>alg</code></th>
<th>Algorithm</th>
<th>Key requirement</th>
</tr>
</thead>
<tbody><tr>
<td><code>HS256</code></td>
<td>HMAC SHA-256</td>
<td>Shared secret, at least 256 bits</td>
</tr>
<tr>
<td><code>RS256</code></td>
<td>RSASSA-PKCS1-v1_5 with SHA-256</td>
<td>RSA private/public key pair, at least 2048 bits</td>
</tr>
<tr>
<td><code>ES256</code></td>
<td>ECDSA with P-256 and SHA-256</td>
<td>EC key pair</td>
</tr>
</tbody></table>
<p><code>HS256</code> is symmetric: the same secret both signs and verifies, so only one party (usually the server) should hold it. <code>RS256</code> and <code>ES256</code> are asymmetric: a private key signs, any holder of the matching public key verifies — which is why they fit multi-service setups where several services must check the same issuer&#39;s tokens without sharing a secret.</p>
</section>
<section><h2 id="how-a-jwt-should-be-validated"><a href="#how-a-jwt-should-be-validated" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How a JWT should be validated</h2><p>RFC 7519 defines the validation steps formally; the essentials for an application:</p>
<ol>
<li>Check the token has at least one <code>.</code> — split into header, payload, signature.</li>
<li>Decode and parse the header as valid JSON; confirm you understand and support the <code>alg</code> value.</li>
<li>Recompute the signature over the encoded header and payload with the correct key for <code>alg</code>, and compare — treat the token as invalid if they differ.</li>
<li>Decode and parse the payload as valid JSON.</li>
<li>Enforce the registered claims you care about: <code>exp</code> (reject expired), <code>nbf</code> (reject early), <code>aud</code> (reject if you are not an intended audience).</li>
<li>Reject if the algorithm used is not one your application explicitly allows.</li>
</ol>
<p>The last point is a security requirement, not a suggestion: which algorithms are acceptable is an application decision, and a token must be rejected unless the algorithms used in it are acceptable. The classic failure is accepting <code>alg: none</code> (an unsecured JWT with an empty signature) or swapping <code>RS256</code> for <code>HS256</code> to make the server verify an asymmetric signature with the public key as a shared secret. Library flaws of exactly this kind have been patched repeatedly — and new <code>alg: none</code> vulnerabilities still surface years later — so verification must never be driven by the header alone.</p>
</section>
<section><h2 id="where-jwts-are-used"><a href="#where-jwts-are-used" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Where JWTs are used</h2><ul>
<li><strong>API authentication</strong> — after login the server issues a token, the client sends it in the <code>Authorization: Bearer &lt;token&gt;</code> header, and the server trusts the signature. This is stateless: the server does not have to store session state, because the token is self-contained.</li>
<li><strong>Single sign-on</strong> — the token format was designed for browser SSO contexts, letting an identity provider hand a verified identity to a service provider.</li>
<li><strong>OAuth-style client assertions</strong> — a client can present a signed JWT to a token endpoint and receive an access token in return.</li>
</ul>
</section>
<section><h2 id="security-checklist"><a href="#security-checklist" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Security checklist</h2><ul>
<li>Store tokens in an <code>HttpOnly</code> cookie, not in <code>localStorage</code> or <code>sessionStorage</code> — client-side JavaScript (including browser extensions) can read those storage areas, which exposes the token to any XSS bug.</li>
<li>Never let the header alone drive verification: enforce an algorithm allowlist on the server.</li>
<li>Use a key of appropriate size for the algorithm (256 bits minimum for HS256, 2048 bits for RSA).</li>
<li>Keep <code>exp</code> short and allow only a little leeway (the RFC suggests no more than a few minutes) for clock skew.</li>
<li>Never put sensitive data in a plain signed JWT payload — it is readable by anyone.</li>
<li>Use <code>jti</code> when you need to stop a token from being replayed.</li>
</ul>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="is-the-content-of-a-jwt-secret">Is the content of a JWT secret?</h3><p>No. A JWT is signed, not encrypted: the header and payload are only base64url-encoded, so anyone who can read the token can decode and see every claim in plain text. That is why sensitive data like passwords must never go into a plain signed JWT — for that, use encrypted JWTs (JWE). What makes the token trustworthy is its signature, not the secrecy of its contents.</p>
<h3 id="what-is-the-difference-between-hs256-and-rs256">What is the difference between HS256 and RS256?</h3><p>HS256 is a symmetric algorithm: the same secret key signs and verifies, so only one party (usually the server) should hold it. RS256 is asymmetric: a private key signs, and anyone with the matching public key verifies — which is why it fits multi-service setups where several services must check the same issuer&#39;s tokens without sharing a secret. Both are defined in RFC 7518.</p>
<h3 id="what-happens-when-a-jwt-expires">What happens when a JWT expires?</h3><p>The <code>exp</code> (expiration time) claim states the time on or after which the token must not be accepted. Validation must reject a token past <code>exp</code> — and also reject tokens used before the <code>nbf</code> (not before) claim and recipients not identified in the <code>aud</code> (audience) claim. That is why the server checks <code>exp</code> on every JWT request: an expired token is treated as invalid.</p>
<h3 id="why-does-the-server-reject-a-jwt-with-an-unknown-algorithm">Why does the server reject a JWT with an unknown algorithm?</h3><p>Because which algorithms are acceptable is an application decision — a token must be rejected unless the algorithm it uses is on the allowlist. The classic failure is accepting <code>alg: none</code> (an unsigned JWT) or swapping RS256 for HS256 so the server verifies an asymmetric signature using the public key as a shared secret. Verification must never be driven by the header alone: check the signature, check the claims, then check the algorithm against the allowlist.</p>
</section>
<section><h2 id="summary"><a href="#summary" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Summary</h2><p>A JWT is a signed, compact container for claims: three base64url parts (header, payload, signature), seven standard claims, and a handful of common algorithms. It is not encryption — the payload is readable by anyone — and it is only as trustworthy as your validation: check the signature, check the claims, and check the algorithm against an allowlist. Used that way, JWTs give you stateless, verifiable authentication without a session store.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>What is HTTP/3? A plain explanation</title>
      <link>https://debod.id/en/posts/http3-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/http3-explained/</guid>
      <pubDate>Wed, 19 Aug 2026 00:00:00 GMT</pubDate>
      <category>networking</category>
      <category>http</category>
      <category>networking</category>
      <category>protocols</category>
      <category>performance</category>
      <description>The newest version of HTTP runs on QUIC, a transport built on UDP instead of TCP. Here&apos;s what changes — and why pages feel faster with it.</description>
      <content:encoded><![CDATA[<section><p>HTTP/3 is the third major version of the Hypertext Transfer Protocol. Instead of running over TCP like HTTP/1.1 and HTTP/2, it runs over QUIC — a secure, multiplexed transport protocol built on UDP. Its biggest practical win: a lost packet no longer stalls an entire page load.</p></section>
<section><h2 id="the-problem-http-2-head-of-line-blocking"><a href="#the-problem-http-2-head-of-line-blocking" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The problem: HTTP/2 head-of-line blocking</h2><p>HTTP/2 made pages faster by multiplexing — many requests and responses travel at the same time over one TCP connection. Sounds perfect, but TCP has a blind spot: its loss-recovery mechanisms cannot see the multiplexing above them. When one packet is lost, TCP waits for it to be retransmitted, and every stream sharing that connection stalls until it arrives. That is head-of-line blocking.</p>
</section>
<section><h2 id="what-http-3-changes-quic-instead-of-tcp"><a href="#what-http-3-changes-quic-instead-of-tcp" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What HTTP/3 changes: QUIC instead of TCP</h2><p>HTTP/3 keeps the HTTP semantics — the same request methods, status codes and header fields — but maps them onto a different transport. QUIC is a connection-oriented, secure transport protocol that provides stream-based multiplexing natively. Because multiplexing lives inside the transport itself, a lost packet only affects the streams that actually lost data. The other streams keep flowing.</p>
<p>QUIC also integrates the TLS handshake directly, using TLS 1.3. Encryption and connection setup happen together, and the handshake is designed so that application data can be exchanged as soon as possible — including a 0-RTT option for reconnects.</p>
</section>
<section><h2 id="what-else-quic-brings"><a href="#what-else-quic-brings" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What else QUIC brings</h2><ul>
<li><strong>Connection migration</strong> — a QUIC connection is identified by a connection ID rather than by the IP address, so the connection survives when a phone switches from Wi-Fi to mobile data.</li>
<li><strong>Faster reconnects</strong> — the 0-RTT option lets a client that connected before send data immediately on a new connection.</li>
<li><strong>Fewer round trips</strong> — the combined handshake replaces the separate TCP and TLS round trips of older versions.</li>
</ul>
</section>
<section><h2 id="same-http-new-transport"><a href="#same-http-new-transport" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Same HTTP, new transport</h2><p>Nothing about the web&#39;s model changes: browsers still use <code>https://</code> URLs, and servers still answer with HTTP status codes and headers. HTTP/3 is just a different way of carrying those messages. Clients discover an HTTP/3-capable server through mechanisms like the <code>Alt-Svc</code> header or the HTTPS DNS record, then open a QUIC connection.</p>
</section>
<section><h2 id="a-bit-of-history"><a href="#a-bit-of-history" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>A bit of history</h2><p>HTTP/3 started as an IETF draft called &quot;HTTP/2 Semantics Using The QUIC Transport Protocol&quot;. In November 2018 it was renamed HTTP/3, to make clear it is another binding of HTTP semantics to a wire protocol, separate from QUIC itself. The standard was published in June 2022 as RFC 9114, with QUIC defined in RFC 9000.</p>
</section>
<section><h2 id="who-supports-it"><a href="#who-supports-it" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Who supports it</h2><p>All major browsers ship HTTP/3 enabled by default: Chrome and Edge since 2020, Firefox since 2021, and Safari since version 16.4 (September 2024). On the server side, Caddy enabled it by default in its 2.6 release (2022), nginx supports it since 1.25 (2023), and LiteSpeed enabled it by default back in 2021. Your browser&#39;s developer tools show the protocol used by each request — look for <code>h3</code> in the network tab.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-is-http-3">What is HTTP/3?</h3><p>HTTP/3 is the third major version of HTTP. Unlike HTTP/1.1 and HTTP/2 which run over TCP, HTTP/3 runs over QUIC — a secure, multiplexed transport protocol built on UDP. This means a lost packet no longer stalls the entire page load.</p>
<h3 id="what-is-head-of-line-blocking-and-how-does-http-3-fix-it">What is head-of-line blocking and how does HTTP/3 fix it?</h3><p>In HTTP/2, multiplexing happens over a single TCP connection. When one packet is lost, TCP waits for retransmission and all streams on that connection stall — this is head-of-line blocking. HTTP/3 moves multiplexing into QUIC itself, so a lost packet only affects the streams that actually lost data; other streams keep flowing.</p>
<h3 id="what-are-the-main-benefits-of-quic">What are the main benefits of QUIC?</h3><p>QUIC brings three key improvements: (1) Connection migration — connections are identified by a connection ID, not IP address, so they survive network changes like switching from Wi-Fi to mobile data. (2) Faster reconnects — the 0-RTT option lets a previously connected client send data immediately on a new connection. (3) Fewer round trips — the combined TLS 1.3 handshake replaces the separate TCP and TLS handshakes of older versions.</p>
<h3 id="which-browsers-and-servers-support-http-3">Which browsers and servers support HTTP/3?</h3><p>All major browsers ship HTTP/3 enabled by default: Chrome and Edge since 2020, Firefox since 2021, and Safari since version 16.4 (September 2024). On the server side, Caddy enabled it by default in 2.6 (2022), nginx supports it since 1.25 (2023), and LiteSpeed has had it enabled by default since 2021. You can check the protocol in your browser&#39;s developer tools — look for <code>h3</code> in the network tab.</p>
</section>
<section><h2 id="summary"><a href="#summary" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Summary</h2><p>HTTP/3 is HTTP over QUIC: the same web protocol you already use, carried by a transport that multiplexes without head-of-line blocking, encrypts by default, and survives network changes. It is not a new language for the web — it is the same language, delivered more efficiently.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>HTTP request methods explained: GET, POST, PUT, DELETE &amp; more</title>
      <link>https://debod.id/en/posts/http-methods-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/http-methods-explained/</guid>
      <pubDate>Wed, 19 Aug 2026 00:00:00 GMT</pubDate>
      <category>web</category>
      <category>http</category>
      <category>web</category>
      <category>api</category>
      <category>basics</category>
      <description>What HTTP request methods are, what each one does, and the difference between GET, POST, PUT, PATCH, DELETE and the rest — with safe vs idempotent.</description>
      <content:encoded><![CDATA[<section><p>Every HTTP request does something — and the <em>method</em> is the verb that says what. HTTP defines a set of request methods to indicate the purpose of the request and what you expect if it succeeds. Methods are sometimes called &quot;HTTP verbs&quot;, though they can also be nouns. This article explains each one in plain terms.</p></section>
<section><h2 id="the-quick-list"><a href="#the-quick-list" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The quick list</h2><table>
<thead>
<tr>
<th>Method</th>
<th>Purpose</th>
</tr>
</thead>
<tbody><tr>
<td><code>GET</code></td>
<td>Request a representation of a resource (download it)</td>
</tr>
<tr>
<td><code>HEAD</code></td>
<td>Like <code>GET</code>, but no response body — just headers</td>
</tr>
<tr>
<td><code>POST</code></td>
<td>Send data that typically causes a change or side effect on the server</td>
</tr>
<tr>
<td><code>PUT</code></td>
<td>Replace all current representations of the target resource</td>
</tr>
<tr>
<td><code>PATCH</code></td>
<td>Apply a partial modification to a resource</td>
</tr>
<tr>
<td><code>DELETE</code></td>
<td>Delete the specified resource</td>
</tr>
<tr>
<td><code>OPTIONS</code></td>
<td>Ask which communication options (methods) the target supports</td>
</tr>
<tr>
<td><code>CONNECT</code></td>
<td>Establish a tunnel to the server (used by HTTPS through proxies)</td>
</tr>
<tr>
<td><code>TRACE</code></td>
<td>Run a loop-back test along the path to the target</td>
</tr>
</tbody></table>
<p>The ones you will use constantly are <code>GET</code>, <code>POST</code>, <code>PUT</code>, <code>PATCH</code> and <code>DELETE</code> — the everyday &quot;CRUD&quot; building blocks of web APIs.</p>
</section>
<section><h2 id="get-fetch-a-resource"><a href="#get-fetch-a-resource" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>GET: fetch a resource</h2><p><code>GET</code> requests a representation of the specified resource. It should only retrieve data and should not have a request body — sending one is undefined behavior that some servers reject with a client error.</p>
<p>A browser sends <code>GET</code> every time you open a page or image. Because it only reads, <code>GET</code> is safe, idempotent and cacheable: you can repeat it and cache the response.</p>
</section>
<section><h2 id="head-the-headers-not-the-content"><a href="#head-the-headers-not-the-content" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>HEAD: the headers, not the content</h2><p><code>HEAD</code> asks for a response identical to a <code>GET</code>, but without the body. It is the cheap version of <code>GET</code> — often used to check a resource before downloading it. For example, if a URL might produce a large download, a <code>HEAD</code> request can read the <code>Content-Length</code> header to check the file size first.</p>
</section>
<section><h2 id="post-send-data-that-changes-something"><a href="#post-send-data-that-changes-something" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>POST: send data that changes something</h2><p><code>POST</code> sends data to the server. The <code>Content-Type</code> header describes the body&#39;s type. HTML forms typically send data with <code>POST</code>, and this usually results in a change on the server.</p>
<p>The key difference from <code>PUT</code>: <code>POST</code> is <strong>not idempotent</strong>. Repeating the same <code>POST</code> may create the same order, the same message, or the same entry several times.</p>
</section>
<section><h2 id="put-create-or-replace-fully"><a href="#put-create-or-replace-fully" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>PUT: create or replace, fully</h2><p><code>PUT</code> creates a new resource or replaces the representation of the target resource with the request content — a complete replacement. It is <strong>idempotent</strong>: calling it once is no different from calling it several times. The fourth identical <code>PUT</code> leaves the resource exactly as the first did.</p>
</section>
<section><h2 id="patch-partial-modification"><a href="#patch-partial-modification" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>PATCH: partial modification</h2><p><code>PATCH</code> applies a partial modification to a resource. It is analogous to the &quot;update&quot; in CRUD — but note that HTTP is not the same as CRUD, and the two should not be confused.</p>
<p>Think of the difference this way: a <code>PUT</code> is a complete replacement, while a <code>PATCH</code> is a set of instructions for modification. A <code>PUT</code> is always idempotent. A <code>PATCH</code> may not be — for instance, if a resource has an auto-incrementing counter, a <code>PUT</code> overwrites the counter (it replaces the whole resource) and repeating it is stable, whereas a <code>PATCH</code> that increments a counter changes the value each time it is applied.</p>
</section>
<section><h2 id="delete-remove-a-resource"><a href="#delete-remove-a-resource" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>DELETE: remove a resource</h2><p><code>DELETE</code> asks the server to delete the specified resource. Like <code>GET</code>, it should not have a body. It is idempotent — deleting the same resource again is &quot;no different&quot; from deleting it once (the resource is gone).</p>
</section>
<section><h2 id="options-what-am-i-allowed-to-do"><a href="#options-what-am-i-allowed-to-do" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>OPTIONS: what am I allowed to do?</h2><p><code>OPTIONS</code> asks for the permitted communication options for a given URL or server. You can use it to test which HTTP methods a request allows, or whether a request would succeed. Browsers use it behind the scenes for CORS: a &quot;preflight&quot; <code>OPTIONS</code> request checks whether a cross-origin request is allowed before the real request is sent. A special form, <code>OPTIONS *</code>, refers to the entire server rather than one path.</p>
</section>
<section><h2 id="connect-and-trace"><a href="#connect-and-trace" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>CONNECT and TRACE</h2><p>Two you rarely see directly:</p>
<ul>
<li><strong><code>CONNECT</code></strong> establishes a tunnel to the server — this is how HTTPS traffic gets carried through an HTTP proxy.</li>
<li><strong><code>TRACE</code></strong> performs a message loop-back test along the path, letting the client see what the server received. For security reasons, many servers disable it.</li>
</ul>
</section>
<section><h2 id="safe-idempotent-cacheable-what-these-actually-mean"><a href="#safe-idempotent-cacheable-what-these-actually-mean" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Safe, idempotent, cacheable — what these actually mean</h2><p>These three properties decide whether it is safe to repeat or cache a request:</p>
<ul>
<li><strong>Safe</strong> — the method does not change the server state. A request using a safe method can be made repeatedly with no side effects. <code>GET</code>, <code>HEAD</code>, <code>OPTIONS</code> and <code>TRACE</code> are safe; <code>POST</code>, <code>PUT</code>, <code>DELETE</code>, <code>PATCH</code> and <code>CONNECT</code> are not.</li>
<li><strong>Idempotent</strong> — repeating the request produces the same result as doing it once. <code>GET</code>, <code>HEAD</code>, <code>PUT</code>, <code>DELETE</code>, <code>OPTIONS</code> and <code>TRACE</code> are idempotent; <code>POST</code> and <code>PATCH</code> are not guaranteed to be.</li>
<li><strong>Cacheable</strong> — the response may be stored and reused. <code>GET</code> and <code>HEAD</code> are cacheable. <code>POST</code> and <code>PATCH</code> are only cacheable when the response explicitly includes freshness information and a matching <code>Content-Location</code> header. The others are not cacheable.</li>
</ul>
<table>
<thead>
<tr>
<th>Method</th>
<th>Safe</th>
<th>Idempotent</th>
<th>Cacheable</th>
</tr>
</thead>
<tbody><tr>
<td><code>GET</code></td>
<td>Yes</td>
<td>Yes</td>
<td>Yes</td>
</tr>
<tr>
<td><code>HEAD</code></td>
<td>Yes</td>
<td>Yes</td>
<td>Yes</td>
</tr>
<tr>
<td><code>OPTIONS</code></td>
<td>Yes</td>
<td>Yes</td>
<td>No</td>
</tr>
<tr>
<td><code>TRACE</code></td>
<td>Yes</td>
<td>Yes</td>
<td>No</td>
</tr>
<tr>
<td><code>PUT</code></td>
<td>No</td>
<td>Yes</td>
<td>No</td>
</tr>
<tr>
<td><code>DELETE</code></td>
<td>No</td>
<td>Yes</td>
<td>No</td>
</tr>
<tr>
<td><code>POST</code></td>
<td>No</td>
<td>No</td>
<td>Conditional</td>
</tr>
<tr>
<td><code>PATCH</code></td>
<td>No</td>
<td>No</td>
<td>Conditional</td>
</tr>
<tr>
<td><code>CONNECT</code></td>
<td>No</td>
<td>No</td>
<td>No</td>
</tr>
</tbody></table>
</section>
<section><h2 id="when-to-use-which"><a href="#when-to-use-which" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>When to use which</h2><ul>
<li><strong>Reading</strong> a page, image or API data → <code>GET</code>.</li>
<li><strong>Checking</strong> something exists or its size without downloading it → <code>HEAD</code>.</li>
<li><strong>Creating</strong> an item in a collection → <code>POST</code> (or <code>PUT</code> when you supply the full resource).</li>
<li><strong>Updating/replacing</strong> a whole resource → <code>PUT</code>.</li>
<li><strong>Updating part</strong> of a resource → <code>PATCH</code>.</li>
<li><strong>Removing</strong> a resource → <code>DELETE</code>.</li>
</ul>
<p>Why idempotency matters in practice: retries. If a request times out and you are not sure it arrived, an idempotent method can be safely repeated. This is why payment and order APIs lean on idempotency — repeating a <code>POST</code> that creates a charge twice could double-charge someone.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-is-the-difference-between-get-and-post">What is the difference between GET and POST?</h3><p><code>GET</code> requests a representation of a resource and should only retrieve data — it is safe, idempotent and cacheable, so you can repeat it without side effects. <code>POST</code> sends data to the server and usually causes a change or side effect, and it is not idempotent: repeating the same <code>POST</code> can create the same order or entry several times. As a rule, read with <code>GET</code> and send data that changes something with <code>POST</code>.</p>
<h3 id="what-does-it-mean-when-an-http-method-is-idempotent">What does it mean when an HTTP method is idempotent?</h3><p>An idempotent method produces the same result whether you call it once or many times. For example <code>PUT</code> replaces the whole resource, so a fourth identical <code>PUT</code> leaves it exactly as the first did. This matters for retries: if a request times out and you are not sure it arrived, an idempotent method can be safely repeated — which is why payment and order APIs rely on idempotency, since repeating a <code>POST</code> that creates a charge could double-charge someone.</p>
<h3 id="what-is-the-difference-between-put-and-patch">What is the difference between PUT and PATCH?</h3><p><code>PUT</code> replaces the entire resource with the request content — a complete replacement, and always idempotent. <code>PATCH</code> applies a partial modification, like the &quot;update&quot; step of CRUD, and may not be idempotent. For example, if a resource has an auto-incrementing counter, <code>PUT</code> overwrites the counter (it replaces the whole resource) while a <code>PATCH</code> that increments the counter changes the value every time it runs.</p>
<h3 id="how-do-i-find-out-which-methods-a-server-allows">How do I find out which methods a server allows?</h3><p>Send an <code>OPTIONS</code> request. <code>OPTIONS</code> asks for the communication options the target supports, so you can test which HTTP methods a request allows or whether it would succeed. Browsers also use it for CORS: a &quot;preflight&quot; <code>OPTIONS</code> request checks whether a cross-origin request is allowed before the real request is sent.</p>
</section>
<section><h2 id="summary"><a href="#summary" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Summary</h2><p>HTTP request methods are the verbs of the web: <code>GET</code> fetches, <code>HEAD</code> checks, <code>POST</code> sends and creates, <code>PUT</code> replaces, <code>PATCH</code> updates partially, and <code>DELETE</code> removes. The properties safe, idempotent and cacheable tell you whether a method can be repeated or cached without consequences. Understand those three words and you can reason about any API, framework or server log you meet.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>How Wi-Fi works: a plain explanation</title>
      <link>https://debod.id/en/posts/wifi-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/wifi-explained/</guid>
      <pubDate>Tue, 18 Aug 2026 00:00:00 GMT</pubDate>
      <category>networking</category>
      <category>networking</category>
      <description>Radio waves, access points, bands and security — how Wi-Fi actually delivers internet to your devices.</description>
      <content:encoded><![CDATA[<section><p>Wi-Fi is wireless networking based on radio waves. This article explains the basics: radio, bands, access points, and security.</p></section>
<section><h2 id="it-is-radio-just-like-fm"><a href="#it-is-radio-just-like-fm" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>It is radio, just like FM</h2><p>Wi-Fi uses radio waves in unlicensed frequency bands. Your router (access point) transmits data as radio signals; your phone, laptop and TV listen and respond. It is the same physical idea as radio or television broadcasts, but two-way and digital.</p>
<p>Two bands matter in practice:</p>
<ul>
<li><strong>2.4 GHz</strong> — longer range, better at passing through walls, but crowded (microwaves, Bluetooth, older devices share it). More interference, lower top speed.</li>
<li><strong>5 GHz</strong> — shorter range, less interference, higher speeds.</li>
<li><strong>6 GHz (Wi-Fi 6E/7)</strong> — new band, very fast, but short range and requires recent devices.</li>
</ul>
</section>
<section><h2 id="the-parts-of-a-wi-fi-network"><a href="#the-parts-of-a-wi-fi-network" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The parts of a Wi-Fi network</h2><ul>
<li><strong>Access point / router</strong> — the device broadcasting the signal and connecting to your internet line.</li>
<li><strong>SSID</strong> — the network name you see when connecting.</li>
<li><strong>Clients</strong> — phones, laptops, smart devices: they authenticate and then exchange data with the access point.</li>
<li><strong>Channel</strong> — a frequency segment within a band. Nearby networks on the same channel interfere with each other.</li>
</ul>
</section>
<section><h2 id="what-happens-when-you-connect"><a href="#what-happens-when-you-connect" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What happens when you connect</h2><ol>
<li><strong>Your device scans</strong> for networks and sees the SSID and signal strength.</li>
<li><strong>You choose a network and enter the password.</strong> Modern networks use WPA2 or WPA3 authentication — the password proves you are allowed.</li>
<li><strong>Keys are established</strong>, then your device and the router exchange encrypted data frames over the radio link.</li>
<li><strong>The router forwards</strong> your traffic to the wired internet and back. Wi-Fi is only the last hop.</li>
</ol>
</section>
<section><h2 id="why-wi-fi-is-slower-than-cable"><a href="#why-wi-fi-is-slower-than-cable" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Why Wi-Fi is slower than cable</h2><p>Several reasons: radio waves share the medium (air), interference from neighbors and walls weakens the signal, and the access point serves everyone in range. Speed drops with distance. Wi-Fi 5/6/7 keep improving, but a wired connection is still more stable.</p>
</section>
<section><h2 id="security-basics"><a href="#security-basics" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Security basics</h2><ul>
<li><strong>Always use WPA2 or WPA3</strong> — WEP and open networks are trivial to intercept.</li>
<li><strong>Guest networks</strong> isolate visitors from your main devices.</li>
<li><strong>Keep router firmware updated</strong> — routers get security patches like any device.</li>
<li><strong>Change the default admin password</strong> on the router.</li>
</ul>
</section>
<section><h2 id="summary"><a href="#summary" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Summary</h2><p>Wi-Fi is two-way radio networking in the 2.4/5/6 GHz bands. An access point broadcasts the network; devices authenticate with WPA2/WPA3 and exchange encrypted traffic; the access point connects them to the wired internet. It is convenient, but slower and more fragile than cable — and only as secure as your password and firmware updates.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="is-wi-fi-the-same-as-internet">Is Wi-Fi the same as internet?</h3><p>No. Wi-Fi is only the wireless link between your device and the router — the last hop. The router still needs a wired connection to the internet (fiber, cable, DSL or mobile data) to reach the outside world. Wi-Fi can work locally even if that internet line is down.</p>
<h3 id="which-is-better-2-4-ghz-or-5-ghz">Which is better: 2.4 GHz or 5 GHz?</h3><p>Neither is universally better. 2.4 GHz travels farther and passes through walls better, but it is crowded and slower. 5 GHz is faster with less interference but shorter range. Practice: prefer 5 GHz for speed-critical devices near the router, 2.4 GHz for distance.</p>
<h3 id="is-a-wi-fi-network-secure-by-default">Is a Wi-Fi network secure by default?</h3><p>No — security depends on settings. Use WPA2 or WPA3 encryption (never WEP or an open network), a strong admin password, and keep the router firmware updated because routers receive security patches just like any device.</p>
<h3 id="why-is-my-wi-fi-slower-than-wired">Why is my Wi-Fi slower than wired?</h3><p>Radio waves share the air: walls and distance weaken the signal, neighbors&#39; networks cause interference, and the access point serves every device in range at once. A cable has a dedicated line, which is why it is steadier and faster at the same distance.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>What is the World Wide Web? How it&apos;s different from the internet</title>
      <link>https://debod.id/en/posts/web-vs-internet/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/web-vs-internet/</guid>
      <pubDate>Tue, 18 Aug 2026 00:00:00 GMT</pubDate>
      <category>web</category>
      <category>web</category>
      <category>basics</category>
      <description>The web and the internet are not the same thing. A plain look at what each is, and how they fit together.</description>
      <content:encoded><![CDATA[<section><p>&quot;The internet&quot; and &quot;the web&quot; are used as synonyms, but they are different layers. This article separates them.</p></section>
<section><h2 id="the-internet-the-network"><a href="#the-internet-the-network" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The internet: the network</h2><p>The internet is the physical and logical infrastructure that connects computers worldwide: cables, fiber, routers, switches, and the protocols (like IP and TCP) that move data between them.</p>
<p>Think of it as a global postal system — roads, trucks and sorting offices that can carry any kind of package.</p>
</section>
<section><h2 id="the-web-one-service-on-top"><a href="#the-web-one-service-on-top" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The web: one service on top</h2><p>The World Wide Web (WWW) is a service that runs on top of the internet. It is the collection of pages and sites you open in a browser, connected by links and fetched with HTTP/HTTPS.</p>
<p>Think of the web as one kind of package carried by the postal system — the one labeled &quot;HTML pages&quot;. The postal system carries many other things too.</p>
</section>
<section><h2 id="what-else-runs-on-the-internet"><a href="#what-else-runs-on-the-internet" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What else runs on the internet</h2><p>Plenty of things have nothing to do with the web:</p>
<ul>
<li><strong>Email</strong> — SMTP/IMAP/POP3.</li>
<li><strong>File transfers</strong> — FTP, torrents.</li>
<li><strong>Messaging</strong> — WhatsApp, Telegram, Signal (proprietary protocols over the internet).</li>
<li><strong>Gaming</strong> — multiplayer connections over UDP/TCP.</li>
<li><strong>Video calls</strong> — WebRTC and similar.</li>
<li><strong>IoT</strong> — smart devices talking to servers.</li>
</ul>
<p>None of these need a browser or the web at all.</p>
</section>
<section><h2 id="how-the-web-works-under-the-hood"><a href="#how-the-web-works-under-the-hood" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How the web works under the hood</h2><p>When you open a URL, a chain runs:</p>
<ol>
<li><strong>DNS</strong> translates the domain to an IP address.</li>
<li><strong>TCP</strong> establishes a connection to the server.</li>
<li><strong>TLS</strong> encrypts it (HTTPS).</li>
<li><strong>HTTP</strong> requests the page, the server answers with HTML and assets.</li>
<li><strong>The browser renders</strong> — parses HTML, runs CSS and JavaScript, displays the page.</li>
</ol>
<p>Every step is governed by standards from the W3C and IETF so that any browser works with any site.</p>
</section>
<section><h2 id="why-the-confusion-is-common"><a href="#why-the-confusion-is-common" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Why the confusion is common</h2><p>Most people&#39;s only experience of the internet is through a browser, so &quot;internet&quot; and &quot;web&quot; feel identical. It only matters when you troubleshoot: &quot;the Wi-Fi is down&quot; (internet layer) is different from &quot;the website keeps failing&quot; (web layer).</p>
</section>
<section><h2 id="summary"><a href="#summary" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Summary</h2><p>The internet is the infrastructure; the web is one application on it, alongside email, messaging and gaming. When you open a browser you use both — the internet moves the data, and the web formats it as pages you can read.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="are-the-internet-and-the-web-the-same-thing">Are the internet and the web the same thing?</h3><p>No. The internet is the global network of cables, routers and protocols that moves data. The web is one service on top of it — the pages you open in a browser. Email, messaging and gaming also run on the internet without using the web.</p>
<h3 id="do-i-need-the-web-to-use-the-internet">Do I need the web to use the internet?</h3><p>No. Plenty of internet traffic never touches the web: email (SMTP/IMAP), file transfers (FTP, torrents), messaging apps, video calls and online games all use the internet directly, with no browser or web page involved.</p>
<h3 id="is-the-web-the-same-as-a-browser">Is the web the same as a browser?</h3><p>No. A browser is the app that displays web pages; the web is the collection of those pages and sites, fetched with HTTP/HTTPS and linked together. The browser is the window, the web is what you see through it.</p>
<h3 id="why-does-my-wi-fi-work-but-a-website-doesn-t-load">Why does my Wi-Fi work but a website doesn&#39;t load?</h3><p>They are different layers. &quot;Wi-Fi down&quot; means the internet connection itself is broken, while &quot;website won&#39;t load&quot; means the connection is fine but a specific site or server is failing. Troubleshooting starts by telling them apart.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>What is a VPN and how does it work?</title>
      <link>https://debod.id/en/posts/vpn-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/vpn-explained/</guid>
      <pubDate>Tue, 18 Aug 2026 00:00:00 GMT</pubDate>
      <category>networking</category>
      <category>security</category>
      <category>networking</category>
      <category>privacy</category>
      <description>A plain explanation of VPNs — encryption, tunneling, what they protect, and what they don&apos;t.</description>
      <content:encoded><![CDATA[<section><p>A VPN — Virtual Private Network — routes your internet traffic through a private tunnel to a server run by the VPN provider. This article explains what that actually means, and what a VPN does and does not protect.</p></section>
<section><h2 id="the-problem-a-vpn-solves"><a href="#the-problem-a-vpn-solves" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The problem a VPN solves</h2><p>When you connect to a website, your traffic normally travels from your device to your internet provider (ISP), then across many routers to the destination. Your ISP can see which sites you visit. On public Wi-Fi, nearby devices can often spy on the traffic too.</p>
<p>A VPN fixes this by moving your traffic into an encrypted tunnel. The traffic is encrypted on your device and only decrypted at the other end — the VPN server. Your ISP sees only &quot;encrypted data to the VPN server&quot;, not the actual sites.</p>
</section>
<section><h2 id="how-it-works"><a href="#how-it-works" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How it works</h2><ol>
<li><strong>You start the VPN app</strong> — it connects to a VPN server somewhere (often in another country).</li>
<li><strong>The tunnel is created</strong> — your device and the server negotiate encryption keys, typically using protocols like WireGuard or IPsec.</li>
<li><strong>All traffic goes through the tunnel</strong> — websites, apps, everything. Each packet is encrypted, wrapped, and sent to the server.</li>
<li><strong>The VPN server forwards your traffic</strong> — it decrypts, sends the request to the website, receives the response, re-encrypts it, and sends it back.</li>
</ol>
<p>The website you visit sees the VPN server&#39;s IP address, not yours.</p>
</section>
<section><h2 id="what-a-vpn-protects"><a href="#what-a-vpn-protects" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What a VPN protects</h2><ul>
<li><strong>Privacy from your ISP</strong> — they see encrypted traffic to the VPN server, nothing more.</li>
<li><strong>Public Wi-Fi safety</strong> — nearby attackers cannot read your traffic, because it is encrypted.</li>
<li><strong>IP masking</strong> — websites see the VPN server&#39;s IP, so your home IP is hidden.</li>
</ul>
</section>
<section><h2 id="what-a-vpn-does-not-protect"><a href="#what-a-vpn-does-not-protect" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What a VPN does not protect</h2><ul>
<li><strong>Malware and phishing</strong> — a VPN does not stop you from visiting malicious sites or downloading infected files.</li>
<li><strong>Your accounts</strong> — if you enter your password on a fake login page, the VPN does not help. Passwords are still transmitted to that page.</li>
<li><strong>The VPN provider</strong> — the provider can see your traffic. Logging policies matter; a &quot;no-logs&quot; claim should be verified by an independent audit.</li>
<li><strong>Tracking within sites</strong> — cookies and trackers still work through a VPN.</li>
</ul>
</section>
<section><h2 id="common-uses"><a href="#common-uses" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Common uses</h2><p>People use VPNs when traveling, on public Wi-Fi, or to reach content available only in certain regions. Some companies require VPNs for remote work so employees access internal systems through an encrypted connection.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-does-a-vpn-actually-protect-against">What does a VPN actually protect against?</h3><p>A VPN encrypts your traffic so your ISP sees only encrypted data, not the sites you visit. On public Wi-Fi, it prevents nearby devices from reading your traffic. Websites see the VPN server&#39;s IP address instead of yours.</p>
<h3 id="can-a-vpn-make-you-completely-anonymous-online">Can a VPN make you completely anonymous online?</h3><p>No. While a VPN hides your traffic from your ISP and masks your IP, the VPN provider itself can see your traffic. Cookies, trackers, and browser fingerprinting still work through a VPN. &quot;No-logs&quot; claims should be verified by an independent security audit.</p>
<h3 id="what-s-the-difference-between-a-free-and-a-paid-vpn">What&#39;s the difference between a free and a paid VPN?</h3><p>Free VPNs often limit bandwidth, data, or server locations, and may log or sell your data to sustain free operation. Paid VPNs typically offer more server locations, faster speeds, stronger protocols (WireGuard, IPsec), and explicit no-logging policies backed by audits.</p>
<h3 id="is-using-a-vpn-legal">Is using a VPN legal?</h3><p>VPNs are legal in most countries for legitimate privacy and security purposes. Some countries (China, Russia, UAE, North Korea) restrict or ban VPN use without government approval. Business VPNs for remote work are common and legal in most jurisdictions.</p>
</section>
<section><h2 id="summary"><a href="#summary" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Summary</h2><p>A VPN encrypts your traffic and routes it through a server, hiding it from your ISP and making public Wi-Fi safer. It is not a security magic wand — malware, phishing and tracking are still possible. Pick a provider with transparent, audited logging practices. For most people, a VPN is one useful layer, not a replacement for the rest.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>What is a URL? The anatomy of a web address</title>
      <link>https://debod.id/en/posts/url-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/url-explained/</guid>
      <pubDate>Tue, 18 Aug 2026 00:00:00 GMT</pubDate>
      <category>web</category>
      <category>web</category>
      <category>basics</category>
      <description>Every web address is built from a few parts: scheme, domain, path, query and anchor. A plain breakdown of what each one does.</description>
      <content:encoded><![CDATA[<section><p>A URL (Uniform Resource Locator) is the address of a unique resource on the internet — the string a browser uses to fetch a page, image or other file. This article breaks it into its parts and explains what each one does.</p></section>
<section><h2 id="the-parts-of-a-url"><a href="#the-parts-of-a-url" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The parts of a URL</h2><p>Take this address:</p>
<p><code>https://example.com:443/path/to/page?q=url#intro</code></p>
<p>It contains five parts, each with a job:</p>
<ul>
<li><strong>Scheme</strong> — <code>https</code>. Tells the browser which protocol to use to request the resource.</li>
<li><strong>Authority</strong> — <code>example.com:443</code>. Which server, and which &quot;gate&quot; (port) on that server.</li>
<li><strong>Path</strong> — <code>/path/to/page</code>. Which resource on that server.</li>
<li><strong>Query</strong> — <code>?q=url</code>. Extra key/value parameters for the server.</li>
<li><strong>Anchor</strong> — <code>#intro</code>. A bookmark inside the resource itself.</li>
</ul>
<p>A handy way to remember them is the postal analogy: the scheme is the delivery service you choose, the domain is the city, the port is the zip code, the path is the building, the parameters are extra details like the apartment number, and the anchor is the specific person the mail is for.</p>
</section>
<section><h2 id="scheme"><a href="#scheme" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Scheme</h2><p>The first part of a URL indicates the protocol the browser must use. For websites this is almost always <strong>HTTPS</strong> (or its older, unsecured sibling <strong>HTTP</strong>). Browsers also handle other schemes: <code>mailto:</code> opens a mail client, and <code>data:</code> embeds small files directly in a document.</p>
</section>
<section><h2 id="authority-domain-and-port"><a href="#authority-domain-and-port" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Authority: domain and port</h2><p>The authority is separated from the scheme by <code>://</code>. It contains the <strong>domain</strong> (for example <code>example.com</code>, or rarely a raw IP address) and an optional <strong>port</strong>, separated by a colon.</p>
<p>The port is the technical &quot;gate&quot; used to reach resources on the server. It is usually omitted because web servers use the standard ports — 80 for HTTP and 443 for HTTPS — but it becomes mandatory when a server listens on a non-standard port.</p>
<p>Not every scheme uses an authority. <code>mailto:someone@example.com</code> has a scheme but no <code>//</code> and no authority — the colon alone separates the scheme from the address.</p>
</section>
<section><h2 id="path-to-the-resource"><a href="#path-to-the-resource" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Path to the resource</h2><p>The path tells the server which resource you want. In the early days of the web, a path like <code>/path/to/myfile.html</code> pointed at a physical file on the server. Today it is mostly an abstraction: the server decides what to return for a given path, and the path may not correspond to any real file.</p>
</section>
<section><h2 id="query-parameters"><a href="#query-parameters" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Query parameters</h2><p>After the path, a <code>?</code> starts the query string: a list of key/value pairs separated by <code>&amp;</code>, like <code>?key1=value1&amp;key2=value2</code>. The server can use these parameters to change what it returns — for example a search term or a filter. Every server has its own rules for handling them.</p>
</section>
<section><h2 id="anchor-fragment"><a href="#anchor-fragment" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Anchor (fragment)</h2><p>The <code>#</code> starts the anchor — a &quot;bookmark&quot; inside the resource. On an HTML page the browser scrolls to that spot; on a video it jumps to a time. Importantly, the fragment is <strong>never sent to the server</strong> with the request: it exists only inside the browser.</p>
</section>
<section><h2 id="absolute-vs-relative-urls"><a href="#absolute-vs-relative-urls" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Absolute vs relative URLs</h2><p>The URLs above are <strong>absolute</strong>: they contain everything needed to locate a resource on their own. A URL typed into the address bar must be absolute (though you can omit the protocol, which defaults to HTTP, and the port when it is standard).</p>
<p>Inside a document, URLs can be <strong>relative</strong>: the browser fills in the missing parts using the current document&#39;s own URL. Examples of relative URLs:</p>
<ul>
<li><code>//example.com/path</code> — scheme-relative: uses the same protocol as the current page.</li>
<li><code>/path/to/page</code> — domain-relative: same protocol and domain, path from the server root.</li>
<li><code>page</code> — a sub-resource relative to the current directory.</li>
<li><code>../page</code> — up one level in the directory tree.</li>
<li><code>#anchor</code> — anchor-only: a spot in the current document.</li>
</ul>
</section>
<section><h2 id="usernames-and-passwords-in-urls"><a href="#usernames-and-passwords-in-urls" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Usernames and passwords in URLs</h2><p>You may occasionally see URLs like <code>https://username:password@example.com/</code>. This form was used with HTTP authentication to sign in automatically. It is deprecated for security reasons — modern sites use other login mechanisms — and you should not build URLs this way.</p>
</section>
<section><h2 id="semantic-urls"><a href="#semantic-urls" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Semantic URLs</h2><p>URLs are read by humans as well as computers. A <strong>semantic URL</strong> uses words with inherent meaning (<code>/products/coffee-maker</code>) instead of random-looking strings. They are easier to share and remember, clearer for users about where they are, and some search engines use their wording to help classify the page.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-are-the-five-parts-of-a-url">What are the five parts of a URL?</h3><p>A URL is built from a scheme (the protocol, like <code>https</code>), an authority (the domain and optional port), a path to the resource, optional query parameters, and an optional anchor (fragment). For example, in <code>https://example.com:443/path/to/page?q=url#intro</code> those five parts are <code>https</code>, <code>example.com:443</code>, <code>/path/to/page</code>, <code>?q=url</code> and <code>#intro</code>.</p>
<h3 id="does-the-fragment-anchor-get-sent-to-the-server">Does the fragment (#anchor) get sent to the server?</h3><p>No. The anchor is a bookmark inside the resource that exists only in the browser — on an HTML page it scrolls to a spot, on a video it jumps to a time. It is never sent to the server with the request.</p>
<h3 id="what-is-the-difference-between-an-absolute-and-a-relative-url">What is the difference between an absolute and a relative URL?</h3><p>An absolute URL contains everything needed to locate a resource on its own, which is why an address typed into the browser bar must be absolute. A relative URL fills in the missing parts using the current document&#39;s URL — for example <code>//example.com/path</code> reuses the page&#39;s protocol, and <code>../page</code> goes one level up in the directory tree.</p>
<h3 id="is-putting-a-password-in-a-url-still-safe-to-use">Is putting a password in a URL still safe to use?</h3><p>No. The <code>https://username:password@example.com/</code> form was used with HTTP authentication, but it is deprecated for security reasons. Modern sites use other login mechanisms, so you should not build URLs this way.</p>
</section>
<section><h2 id="summary"><a href="#summary" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Summary</h2><p>A URL is the address of a resource on the internet, built from a scheme, an authority (domain and port), a path, optional query parameters and an optional anchor. Absolute URLs work anywhere; relative URLs lean on the current page. The fragment never reaches the server, and the parts are few enough that anyone can learn to read them at a glance.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>TCP vs UDP: what&apos;s the difference?</title>
      <link>https://debod.id/en/posts/tcp-vs-udp/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/tcp-vs-udp/</guid>
      <pubDate>Tue, 18 Aug 2026 00:00:00 GMT</pubDate>
      <category>networking</category>
      <category>networking</category>
      <category>protocols</category>
      <description>TCP and UDP are the two main ways data travels over the internet. TCP is reliable but slower; UDP is fast but lossy. Here&apos;s when each one is used.</description>
      <content:encoded><![CDATA[<section><p>TCP and UDP are the two main transport protocols of the internet. TCP (Transmission Control Protocol) guarantees that data arrives complete and in order — at the cost of speed. UDP (User Datagram Protocol) sends data fast without guarantees — packets can be lost, reordered, or duplicated. Web pages, email, and file transfers use TCP. Video calls, online games, and DNS lookups use UDP.</p></section>
<section><h2 id="what-they-have-in-common"><a href="#what-they-have-in-common" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What they have in common</h2><p>Both protocols run on top of IP (Internet Protocol). IP delivers packets to the right address, but it does not handle packet ordering or error checking — that is exactly the gap TCP and UDP fill differently. Both also use <strong>port numbers</strong> to tell a computer which application a packet belongs to, so a server can run many services at once.</p>
</section>
<section><h2 id="how-tcp-works-the-reliable-option"><a href="#how-tcp-works-the-reliable-option" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How TCP works: the reliable option</h2><p>TCP lets two hosts connect and exchange a stream of data. Before anything is sent, the two computers establish a connection with a <strong>three-way handshake</strong>:</p>
<ol>
<li>The source sends a SYN packet to start the dialogue.</li>
<li>The target replies with SYN-ACK to agree.</li>
<li>The source sends ACK to confirm — after which the data can flow.</li>
</ol>
<p>TCP then guarantees the delivery of packets in the same order they were sent. If a packet goes missing, TCP asks for it to be re-sent. It also performs <strong>congestion control</strong>: transfers start small and grow until they reach what the network can support. This reliability is why email, web browsing, and file downloads use TCP — a corrupted download is useless.</p>
</section>
<section><h2 id="how-udp-works-the-fast-option"><a href="#how-udp-works-the-fast-option" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How UDP works: the fast option</h2><p>UDP is a connectionless protocol. There is no handshake, no connection setup: one computer simply starts sending data. UDP packets are called <strong>datagrams</strong>, and each one is addressed and sent independently. UDP provides checksums for data integrity and port numbers for addressing, but it does not guarantee delivery, ordering, or duplicate protection — that burden moves to the application.</p>
<p>The trade-off is speed. Without handshakes, retransmissions, and ordering bookkeeping, UDP is much faster and adds far less overhead. That makes it ideal for <strong>time-sensitive</strong> traffic, where dropping a packet is better than waiting for a delayed retransmission.</p>
</section>
<section><h2 id="comparison-table"><a href="#comparison-table" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Comparison table</h2><table>
<thead>
<tr>
<th></th>
<th>TCP</th>
<th>UDP</th>
</tr>
</thead>
<tbody><tr>
<td>Connection</td>
<td>Connection-oriented (handshake first)</td>
<td>Connectionless (send immediately)</td>
</tr>
<tr>
<td>Handshake</td>
<td>Yes — 3-way (SYN, SYN-ACK, ACK)</td>
<td>No</td>
</tr>
<tr>
<td>Delivery guarantee</td>
<td>Yes — packets arrive complete, in order</td>
<td>No — loss, reordering, duplication possible</td>
</tr>
<tr>
<td>Retransmission</td>
<td>Lost packets are re-sent</td>
<td>Lost packets stay lost</td>
</tr>
<tr>
<td>Congestion control</td>
<td>Yes — starts small, grows</td>
<td>No</td>
</tr>
<tr>
<td>Speed / overhead</td>
<td>Slower, more overhead</td>
<td>Faster, minimal overhead</td>
</tr>
<tr>
<td>Typical use</td>
<td>Web, email, file transfer</td>
<td>VoIP, video, gaming, DNS</td>
</tr>
</tbody></table>
</section>
<section><h2 id="when-to-use-tcp"><a href="#when-to-use-tcp" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>When to use TCP</h2><p>Use TCP whenever the data must arrive <strong>complete and in the right order</strong>:</p>
<ul>
<li>Web pages (HTTP/HTTPS)</li>
<li>Email (SMTP, IMAP)</li>
<li>File transfers</li>
<li>Anything where a partial result is useless</li>
</ul>
</section>
<section><h2 id="when-to-use-udp"><a href="#when-to-use-udp" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>When to use UDP</h2><p>Use UDP whenever <strong>speed matters more than perfection</strong>, and the application can tolerate some loss:</p>
<ul>
<li>VoIP phone calls — a slightly static-y call beats a crystal-clear but delayed one</li>
<li>Video streaming and conferencing</li>
<li>Online gaming</li>
<li>DNS lookups — they need to be fast and efficient</li>
</ul>
</section>
<section><h2 id="a-real-world-example"><a href="#a-real-world-example" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>A real-world example</h2><p>Loading a web page uses TCP: the page&#39;s bytes must arrive intact, in order, or the page is broken. A video call uses UDP: frames travel as fast as possible, and the occasional dropped frame just produces a tiny flicker — nobody wants the call to pause while a missing frame is re-sent.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="which-is-faster-tcp-or-udp">Which is faster, TCP or UDP?</h3><p>UDP is faster. It has no handshake, no retransmission, and no ordering bookkeeping, so it adds far less overhead than TCP. That raw speed is why UDP is chosen for time-sensitive traffic where a dropped packet is better than waiting for a delayed retransmission.</p>
<h3 id="can-tcp-and-udp-use-the-same-port-number">Can TCP and UDP use the same port number?</h3><p>Yes. Both protocols use port numbers to tell a computer which application a packet belongs to, and a server can run many services at once. Because TCP and UDP are separate protocols, a TCP connection and a UDP socket can use the same port number simultaneously without conflicting.</p>
<h3 id="why-is-udp-used-for-video-calls-and-online-games">Why is UDP used for video calls and online games?</h3><p>Because those applications are time-sensitive. Video calls, conferencing, and online games care more about low latency than about perfect delivery — an occasional lost frame or packet just causes a small flicker, whereas waiting for a delayed retransmission would make the call or game feel unresponsive.</p>
<h3 id="which-is-more-reliable-tcp-or-udp">Which is more reliable, TCP or UDP?</h3><p>TCP. It establishes a connection with a three-way handshake, guarantees packets arrive complete and in order, and re-sends anything that goes missing. UDP provides no such guarantees — packets can be lost, reordered, or duplicated — which is why web pages, email, and file transfers use TCP.</p>
</section>
<section><h2 id="summary"><a href="#summary" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Summary</h2><p>TCP and UDP solve the same problem — moving data over IP — with opposite priorities. TCP guarantees delivery and order, and pays for it with handshakes, retransmissions, and overhead. UDP trades all guarantees for raw speed. Pick TCP when completeness matters; pick UDP when latency matters more.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>What is a proxy server and how does it work?</title>
      <link>https://debod.id/en/posts/proxy-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/proxy-explained/</guid>
      <pubDate>Tue, 18 Aug 2026 00:00:00 GMT</pubDate>
      <category>networking</category>
      <category>proxy</category>
      <category>networking</category>
      <category>security</category>
      <category>web</category>
      <description>A plain explanation of proxy servers — forward vs reverse, what they do, the headers they add, and what they cannot protect.</description>
      <content:encoded><![CDATA[<section><p>A proxy server is a computer that sits between your device and the websites you visit, forwarding requests on your behalf. It can live on your own machine or anywhere between you and the destination server. This article explains the two main types — forward and reverse proxies — and what each one actually does.</p></section>
<section><h2 id="forward-proxies"><a href="#forward-proxies" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Forward proxies</h2><p>A forward proxy acts on behalf of clients. When you send a request through one, the proxy forwards it to the destination and returns the response to you. The website sees the proxy&#39;s IP address instead of yours.</p>
<p>Forward proxies are used to:</p>
<ul>
<li><strong>Hide your IP address</strong> — the destination server sees the proxy&#39;s address, not your device&#39;s.</li>
<li><strong>Control and filter traffic</strong> — organizations and schools route traffic through a proxy to enforce acceptable-use policies, with URL and content filtering.</li>
<li><strong>Reduce bandwidth</strong> — a shared proxy can store and forward frequently requested content for a group of clients.</li>
</ul>
<p>Open proxies — forward proxies accessible to anyone on the Internet — are common; security researchers estimated &quot;hundreds of thousands&quot; of them years ago. An anonymous proxy reveals that it is a proxy but still hides your original IP.</p>
</section>
<section><h2 id="reverse-proxies"><a href="#reverse-proxies" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Reverse proxies</h2><p>A reverse proxy does the opposite: it acts on behalf of servers, not clients. It sits in front of one or more web servers, and all traffic headed to those servers goes through it first. Clients may not even know the proxy exists — responses look like they came from the server directly.</p>
<p>Common jobs for a reverse proxy:</p>
<ul>
<li><strong>Load balancing</strong> — distribute requests across several backend servers.</li>
<li><strong>Caching static content</strong> — serve images and files from the proxy, offloading the web servers.</li>
<li><strong>Compression</strong> — compress and optimize responses to speed up load time.</li>
<li><strong>TLS termination</strong> — handle HTTPS encryption at the proxy, centralizing certificate management.</li>
<li><strong>Extra defense layer</strong> — filter some attacks before they reach the application server.</li>
</ul>
</section>
<section><h2 id="forward-vs-reverse"><a href="#forward-vs-reverse" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Forward vs reverse</h2><table>
<thead>
<tr>
<th></th>
<th>Forward proxy</th>
<th>Reverse proxy</th>
</tr>
</thead>
<tbody><tr>
<td>Acts on behalf of</td>
<td>Clients (users)</td>
<td>Servers (websites)</td>
</tr>
<tr>
<td>Hides the identity of</td>
<td>The client</td>
<td>The server</td>
</tr>
<tr>
<td>Typical location</td>
<td>Near the client (or on it)</td>
<td>In front of web servers</td>
</tr>
<tr>
<td>Common uses</td>
<td>Anonymity, filtering, caching for users</td>
<td>Load balancing, caching, TLS, protection</td>
</tr>
</tbody></table>
</section>
<section><h2 id="how-a-proxy-forwards-traffic"><a href="#how-a-proxy-forwards-traffic" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How a proxy forwards traffic</h2><p>When you browse through a proxy, your browser sends the request to the proxy instead of the destination. The proxy forwards it, receives the response, and sends it back to you.</p>
<p>For HTTPS traffic, many proxies use the HTTP <code>CONNECT</code> method, which opens a two-way tunnel between your browser and the destination through the proxy. Not all proxies support <code>CONNECT</code> — some limit it to port 443 only.</p>
<p>Proxies can also pass on information about the original client using headers:</p>
<ul>
<li><strong><code>Forwarded</code></strong> — the standardized header for client-facing proxy information.</li>
<li><strong><code>X-Forwarded-For</code></strong> — the de-facto standard identifying the original client IP.</li>
<li><strong><code>Via</code></strong> — added by the proxy itself, showing a request passed through it.</li>
</ul>
<p>These headers are not inherently trustworthy — they can be missing, modified, or added by several proxies in a row.</p>
</section>
<section><h2 id="what-a-proxy-cannot-do"><a href="#what-a-proxy-cannot-do" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What a proxy cannot do</h2><p>A proxy is not a security magic wand:</p>
<ul>
<li><strong>It can see your traffic</strong> — the proxy operator (or anyone who controls it) can read or log what passes through, unless it is encrypted end-to-end.</li>
<li><strong>It does not fix the website</strong> — a reverse proxy adds a defense layer but does not protect against attacks on the web application itself.</li>
<li><strong>It does not make headers honest</strong> — a proxy that hides your IP can also add fake forwarding headers.</li>
</ul>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-is-the-difference-between-a-forward-proxy-and-a-reverse-proxy">What is the difference between a forward proxy and a reverse proxy?</h3><p>A forward proxy acts on behalf of clients: it hides the identity of the user, and the destination server only sees the proxy&#39;s IP address. A reverse proxy acts on behalf of servers: it sits in front of one or more web servers, so clients may not even know it exists while it handles load balancing, caching, and TLS termination.</p>
<h3 id="can-a-proxy-hide-your-ip-address">Can a proxy hide your IP address?</h3><p>Yes — that is the main job of a forward proxy. The destination server sees the proxy&#39;s address instead of your device&#39;s. But a proxy is not a privacy guarantee: whoever controls the proxy can read or log everything that passes through it unless the traffic is encrypted end-to-end.</p>
<h3 id="what-is-the-http-connect-method-used-for">What is the HTTP CONNECT method used for?</h3><p>For HTTPS traffic, many proxies use the HTTP <code>CONNECT</code> method, which opens a two-way tunnel between your browser and the destination through the proxy. Not all proxies support <code>CONNECT</code> — some limit it to port 443 only.</p>
<h3 id="can-you-trust-the-x-forwarded-for-header">Can you trust the X-Forwarded-For header?</h3><p>Not by itself. Headers like <code>Forwarded</code>, <code>X-Forwarded-For</code>, and <code>Via</code> are added by proxies, but they are not inherently trustworthy — they can be missing, modified, or added by several proxies in a row.</p>
</section>
<section><h2 id="summary"><a href="#summary" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Summary</h2><p>A forward proxy works for you, hiding your IP and filtering your traffic; a reverse proxy works for servers, balancing load, caching, and handling TLS. Both forward traffic between two points, and both see the traffic they carry. When you use a proxy — or build a site behind one — the question is always: who controls it, and what do they see?</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>IP addresses: IPv4 vs IPv6 explained</title>
      <link>https://debod.id/en/posts/ipv4-vs-ipv6/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/ipv4-vs-ipv6/</guid>
      <pubDate>Tue, 18 Aug 2026 00:00:00 GMT</pubDate>
      <category>networking</category>
      <category>networking</category>
      <description>What an IP address is, why IPv4 ran out, and how IPv6 fixes it — including shorthand and practical differences.</description>
      <content:encoded><![CDATA[<section><p>Every device on the internet needs an address — an IP address — so data knows where to go. This article explains IP addresses and why two versions exist.</p></section>
<section><h2 id="what-an-ip-address-is"><a href="#what-an-ip-address-is" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What an IP address is</h2><p>An IP address identifies a device (or a network interface) on the internet, like a street address for data. When you request a page, the request is addressed to the server&#39;s IP; the reply is addressed to yours.</p>
<p>Two versions exist today: IPv4 (the old one, still everywhere) and IPv6 (the replacement).</p>
</section>
<section><h2 id="ipv4-the-address-that-ran-out"><a href="#ipv4-the-address-that-ran-out" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>IPv4: the address that ran out</h2><p>IPv4 addresses look like <code>192.168.1.1</code> — four numbers from 0 to 255, separated by dots. That gives 2^32 addresses: about 4.3 billion.</p>
<p>That sounds like a lot, but the internet has more devices than that. Phones, laptops, smart TVs, doorbells — each needs an address. IPv4 ran out in the 2010s, which is why things got complicated: addresses are reused (NAT — many devices share one public IP), and older networks became scarce and expensive.</p>
</section>
<section><h2 id="ipv6-the-replacement"><a href="#ipv6-the-replacement" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>IPv6: the replacement</h2><p>IPv6 uses 128-bit addresses: 2^128 — enough for every device on Earth many times over. IPv6 addresses look like:</p>
<pre><code>2606:4700::6810:84e5
</code></pre>
<p>They are written as eight groups of hex digits, separated by colons. Two shortcuts make them shorter:</p>
<ul>
<li><strong>Leading zeros are dropped</strong> — <code>06810</code> becomes <code>6810</code>.</li>
<li><strong><code>::</code> replaces one run of zero groups</strong> — <code>2606:4700:0000:0000:0000:0000:6810:84e5</code> becomes <code>2606:4700::6810:84e5</code>. A IPv6 address may contain <code>::</code> only once.</li>
</ul>
</section>
<section><h2 id="practical-differences"><a href="#practical-differences" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Practical differences</h2><table>
<thead>
<tr>
<th></th>
<th>IPv4</th>
<th>IPv6</th>
</tr>
</thead>
<tbody><tr>
<td>Size</td>
<td>32-bit (4.3B)</td>
<td>128-bit (virtually unlimited)</td>
</tr>
<tr>
<td>Format</td>
<td>4 decimal groups</td>
<td>8 hex groups</td>
</tr>
<tr>
<td>NAT needed</td>
<td>Usually</td>
<td>No (each device has its own)</td>
</tr>
<tr>
<td>Auto-config</td>
<td>DHCP</td>
<td>SLAAC (stateless)</td>
</tr>
<tr>
<td>Security (IPsec)</td>
<td>Optional</td>
<td>Built in</td>
</tr>
</tbody></table>
</section>
<section><h2 id="why-both-still-exist"><a href="#why-both-still-exist" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Why both still exist</h2><p>The internet is enormous and IPv4 infrastructure is everywhere — routers, servers, firewalls, enterprise networks. Moving everything to IPv6 takes time. Today most networks run <strong>dual-stack</strong>: both protocols operate in parallel, and the system prefers IPv6 when available.</p>
</section>
<section><h2 id="should-you-care"><a href="#should-you-care" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Should you care?</h2><p>For everyday users, mostly not — the network handles it. But it matters when debugging connections, reading server logs, or configuring networks. A site is IPv6-ready when your device can reach it over a <code>2606:...</code> address without a translation layer.</p>
</section>
<section><h2 id="summary"><a href="#summary" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Summary</h2><p>IPv4 ran out of addresses, so IPv6 was created with a vastly larger space and better features. Both exist side by side thanks to dual-stack. Understanding the formats and the shorthand helps when reading logs and configuring networks — even if the internet hides most of it from you.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-is-the-main-difference-between-ipv4-and-ipv6">What is the main difference between IPv4 and IPv6?</h3><p>IPv4 uses 32-bit addresses (about 4.3 billion total) written as four decimal groups like <code>192.168.1.1</code>. IPv6 uses 128-bit addresses (virtually unlimited) written as eight hexadecimal groups like <code>2606:4700::6810:84e5</code>. IPv6 also includes built-in IPsec and stateless auto-configuration (SLAAC).</p>
<h3 id="why-do-we-still-use-ipv4-if-ipv6-is-better">Why do we still use IPv4 if IPv6 is better?</h3><p>The internet&#39;s IPv4 infrastructure is everywhere — routers, servers, firewalls, enterprise networks. Moving everything takes time. Most networks run <strong>dual-stack</strong>: both protocols in parallel, preferring IPv6 when available.</p>
<h3 id="what-is-nat-and-why-does-ipv6-not-need-it">What is NAT and why does IPv6 not need it?</h3><p>NAT (Network Address Translation) lets many devices share one public IPv4 address because IPv4 addresses ran out. IPv6 has enough addresses for every device to have its own public address, so NAT is unnecessary.</p>
<h3 id="how-do-you-write-ipv6-addresses-in-shorthand">How do you write IPv6 addresses in shorthand?</h3><p>Two shortcuts: <strong>drop leading zeros</strong> in each group (<code>06810</code> → <code>6810</code>) and use <strong><code>::</code> to replace one run of zero groups</strong> (<code>2606:4700:0000:0000:0000:0000:6810:84e5</code> → <code>2606:4700::6810:84e5</code>). An address may contain <code>::</code> only once.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>How HTTPS works: the lock icon explained</title>
      <link>https://debod.id/en/posts/https-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/https-explained/</guid>
      <pubDate>Tue, 18 Aug 2026 00:00:00 GMT</pubDate>
      <category>networking</category>
      <category>https</category>
      <category>security</category>
      <category>tls</category>
      <category>networking</category>
      <description>What actually happens when you see the padlock? Encryption, certificates and why every website should use HTTPS.</description>
      <content:encoded><![CDATA[<section><p>The padlock in your browser means the connection to the site is encrypted. This article explains the piece behind that icon — HTTPS — in plain terms.</p></section>
<section><h2 id="http-versus-https"><a href="#http-versus-https" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>HTTP versus HTTPS</h2><p>HTTP is the basic protocol your browser uses to ask a server for a page. The problem: HTTP sends everything in plain text. Anyone in the middle — your Wi-Fi router, your ISP, a coffee-shop attacker — can read the request and the response.</p>
<p>HTTPS is simply HTTP wrapped in an encryption layer called TLS (Transport Layer Security). The data is still HTTP, but it is encrypted before it leaves your device and decrypted at the server.</p>
</section>
<section><h2 id="the-two-jobs-of-https"><a href="#the-two-jobs-of-https" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The two jobs of HTTPS</h2><ol>
<li><strong>Encryption</strong> — nobody in the middle can read the traffic. This protects passwords, bank details and private messages.</li>
<li><strong>Authentication</strong> — the server proves it is really who it claims to be, using a certificate. This is what stops a fake site from impersonating your bank.</li>
</ol>
</section>
<section><h2 id="what-happens-when-you-open-an-https-site"><a href="#what-happens-when-you-open-an-https-site" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What happens when you open an HTTPS site</h2><ol>
<li><strong>Your browser connects to the site</strong>, which presents its TLS certificate.</li>
<li><strong>The browser checks the certificate</strong> — issued by a trusted Certificate Authority (CA), valid dates, matching domain. This is the authentication step.</li>
<li><strong>A shared secret is negotiated</strong> — using a key exchange (in TLS 1.3, usually ECDHE). Both sides derive the same encryption keys without sending them over the wire.</li>
<li><strong>Encrypted communication begins</strong> — every request and response is now encrypted with symmetric keys, which are fast.</li>
</ol>
</section>
<section><h2 id="certificates-and-certificate-authorities"><a href="#certificates-and-certificate-authorities" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Certificates and Certificate Authorities</h2><p>A certificate is a digital document saying &quot;this domain belongs to this organization&quot;. It is digitally signed by a CA — a trusted third party like Let&#39;s Encrypt, DigiCert or Google Trust Services. Browsers ship with a list of trusted CAs. When a certificate is not trusted (expired, self-signed, or for the wrong domain), the browser shows a warning instead of a padlock.</p>
</section>
<section><h2 id="why-the-whole-web-went-https"><a href="#why-the-whole-web-went-https" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Why the whole web went HTTPS</h2><p>Modern browsers mark plain HTTP sites as &quot;Not secure&quot;. HTTPS also enables HTTP/2 and HTTP/3, which are faster. Search engines rank HTTPS sites higher. Certificates from Let&#39;s Encrypt are free, so there is little reason left to run a site without HTTPS.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="is-https-the-same-as-http">Is HTTPS the same as HTTP?</h3><p>No. HTTP sends requests and responses in plain text, so anyone in the middle — a Wi-Fi router, an ISP, an attacker on a shared network — can read them. HTTPS is the same HTTP data wrapped in an encryption layer called TLS: the traffic is encrypted before it leaves your device and decrypted at the server. Everything you can do over HTTP you can also do over HTTPS, just encrypted.</p>
<h3 id="what-does-the-padlock-icon-actually-mean">What does the padlock icon actually mean?</h3><p>The padlock means two things are true: the connection is encrypted (nobody in the middle can read the traffic), and the server has authenticated itself with a valid certificate (a fake site cannot impersonate your bank). It does not mean the site is safe or reputable — only that the connection itself is private and that you are talking to the domain you asked for.</p>
<h3 id="how-does-the-browser-know-a-certificate-is-trustworthy">How does the browser know a certificate is trustworthy?</h3><p>The certificate is digitally signed by a Certificate Authority (CA) — a trusted third party such as Let&#39;s Encrypt, DigiCert or Google Trust Services. Browsers ship with a list of trusted CAs. When you open an HTTPS site, the browser checks that the certificate was issued by a trusted CA, that its dates are valid, and that it matches the domain you requested. If all three hold, you get the padlock.</p>
<h3 id="why-does-a-site-show-a-certificate-warning">Why does a site show a certificate warning?</h3><p>The browser shows a warning instead of a padlock when the certificate is not trusted — for example it has expired, is self-signed, or was issued for a different domain. This usually means the connection is not what it claims to be, so you should treat the site with suspicion and avoid entering passwords or payment details.</p>
</section>
<section><h2 id="summary"><a href="#summary" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Summary</h2><p>HTTPS encrypts your traffic with TLS and proves the server&#39;s identity with a CA-signed certificate. It protects passwords, payment details and private content from prying eyes. If a site shows a certificate warning, treat it with suspicion — the connection is not what it claims to be.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>HTTP vs HTTPS: what&apos;s the difference?</title>
      <link>https://debod.id/en/posts/http-vs-https/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/http-vs-https/</guid>
      <pubDate>Tue, 18 Aug 2026 00:00:00 GMT</pubDate>
      <category>networking</category>
      <category>http</category>
      <category>https</category>
      <category>security</category>
      <category>web</category>
      <description>The practical differences between HTTP and HTTPS — security, performance, SEO — explained simply.</description>
      <content:encoded><![CDATA[<section><p>HTTP and HTTPS look almost the same, but the extra &quot;S&quot; changes the whole nature of the connection. This article compares them in practical terms.</p></section>
<section><h2 id="what-each-one-is"><a href="#what-each-one-is" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What each one is</h2><ul>
<li><strong>HTTP</strong> (Hypertext Transfer Protocol) — the original protocol for fetching web pages. Simple, fast, completely open: everything travels in plain text.</li>
<li><strong>HTTPS</strong> (HTTP Secure) — the same protocol, wrapped in TLS encryption. Same requests and responses, but encrypted in transit.</li>
</ul>
</section>
<section><h2 id="security-comparison"><a href="#security-comparison" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Security comparison</h2><table>
<thead>
<tr>
<th>Property</th>
<th>HTTP</th>
<th>HTTPS</th>
</tr>
</thead>
<tbody><tr>
<td>Traffic readable in transit</td>
<td>Yes</td>
<td>No (encrypted)</td>
</tr>
<tr>
<td>Server identity verified</td>
<td>No</td>
<td>Yes (certificate)</td>
</tr>
<tr>
<td>Safe for passwords/forms</td>
<td>No</td>
<td>Yes</td>
</tr>
<tr>
<td>Browser markers</td>
<td>&quot;Not secure&quot; warning</td>
<td>Padlock</td>
</tr>
</tbody></table>
<p>With HTTP, a password typed into a form can be read by anyone along the path. With HTTPS it is encrypted end to end. HTTPS also authenticates the server, so an attacker cannot easily impersonate the site.</p>
</section>
<section><h2 id="performance-comparison"><a href="#performance-comparison" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Performance comparison</h2><p>HTTPS is not slower anymore. Modern HTTP/2 and HTTP/3 require HTTPS, and they are noticeably faster than plain HTTP/1.1 — multiplexed connections, less overhead, better compression. The TLS handshake cost is tiny and often cached.</p>
</section>
<section><h2 id="seo-and-rankings"><a href="#seo-and-rankings" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>SEO and rankings</h2><p>Search engines treat HTTPS as a ranking signal. HTTPS is also required for many modern features: service workers, push notifications, geolocation APIs, secure cookies. A plain HTTP site cannot use them.</p>
</section>
<section><h2 id="certificates-not-as-scary-as-they-sound"><a href="#certificates-not-as-scary-as-they-sound" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Certificates: not as scary as they sound</h2><p>HTTPS needs a certificate, but free options exist. Let&#39;s Encrypt issues certificates automatically and they renew themselves. Hosting platforms like Cloudflare Pages or Vercel provide them for free out of the box.</p>
</section>
<section><h2 id="which-one-should-you-use"><a href="#which-one-should-you-use" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Which one should you use?</h2><p>Always HTTPS. There is essentially no reason to run a public site over plain HTTP in 2026 — free certificates, better performance, better rankings, and security for your visitors.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-is-the-main-difference-between-http-and-https">What is the main difference between HTTP and HTTPS?</h3><p>HTTP (Hypertext Transfer Protocol) sends data in plain text, while HTTPS (HTTP Secure) wraps the same protocol in TLS encryption to protect data in transit. With HTTPS, all communication between your browser and the server is encrypted, preventing eavesdropping and tampering.</p>
<h3 id="is-https-slower-than-http">Is HTTPS slower than HTTP?</h3><p>No, HTTPS is not slower anymore. Modern HTTP/2 and HTTP/3 protocols actually require HTTPS and provide performance benefits over HTTP/1.1 through multiplexed connections, reduced overhead, and better compression. The TLS handshake adds minimal overhead and is often cached.</p>
<h3 id="do-i-need-to-pay-for-an-https-certificate">Do I need to pay for an HTTPS certificate?</h3><p>No, you don&#39;t need to pay for an HTTPS certificate. Free certificate authorities like Let&#39;s Encrypt provide SSL/TLS certificates that are automatically issued and renewed. Many hosting platforms including Cloudflare Pages and Vercel offer free HTTPS by default.</p>
<h3 id="should-i-use-http-or-https-for-my-website-in-2026">Should I use HTTP or HTTPS for my website in 2026?</h3><p>You should always use HTTPS for your website in 2026. There is essentially no reason to use plain HTTP for a public site — free certificates are available, HTTPS provides better performance and search rankings, and it&#39;s required for modern web features like service workers and geolocation APIs.</p>
</section>
<section><h2 id="summary"><a href="#summary" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Summary</h2><p>HTTP is open and unsafe for sensitive data; HTTPS encrypts the connection and verifies the server. HTTPS is now also faster and required for modern web features. The padlock is not a nice-to-have — it is the baseline.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>HTTP status codes explained: every code you&apos;ll actually see</title>
      <link>https://debod.id/en/posts/http-status-codes/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/http-status-codes/</guid>
      <pubDate>Tue, 18 Aug 2026 00:00:00 GMT</pubDate>
      <category>web</category>
      <category>http</category>
      <category>web</category>
      <category>basics</category>
      <description>What HTTP status codes mean, grouped by class — 1xx to 5xx — with the codes you&apos;ll meet in browsers, APIs, and server logs.</description>
      <content:encoded><![CDATA[<section><p>An HTTP status code is a three-digit number a server returns with every response, telling the client whether the request succeeded — and if not, why. Codes are grouped into five classes, and once you know the class, you already know most of the story.</p></section>
<section><h2 id="the-five-classes"><a href="#the-five-classes" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The five classes</h2><table>
<thead>
<tr>
<th>Range</th>
<th>Class</th>
<th>Meaning</th>
</tr>
</thead>
<tbody><tr>
<td>100–199</td>
<td>Informational</td>
<td>Request received, still in progress</td>
</tr>
<tr>
<td>200–299</td>
<td>Successful</td>
<td>Request understood and accepted</td>
</tr>
<tr>
<td>300–399</td>
<td>Redirection</td>
<td>Further action needed to complete the request</td>
</tr>
<tr>
<td>400–499</td>
<td>Client error</td>
<td>The request was bad — fix it and retry</td>
</tr>
<tr>
<td>500–599</td>
<td>Server error</td>
<td>The server failed to fulfill a valid request</td>
</tr>
</tbody></table>
</section>
<section><h2 id="2xx-it-worked"><a href="#2xx-it-worked" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>2xx: it worked</h2><ul>
<li><strong>200 OK</strong> — the request succeeded. For <code>GET</code>, the resource is in the response body; for <code>POST</code>/<code>PUT</code>, the result of the action is transmitted.</li>
<li><strong>201 Created</strong> — a new resource was created, typically after a <code>POST</code> or some <code>PUT</code> requests.</li>
<li><strong>202 Accepted</strong> — the request was received but not yet acted upon; useful when another process or server handles it, or for batch processing.</li>
<li><strong>204 No Content</strong> — there is no content to send, but the headers are useful; the client may update its cached headers.</li>
</ul>
</section>
<section><h2 id="3xx-look-over-here"><a href="#3xx-look-over-here" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>3xx: look over here</h2><ul>
<li><strong>301 Moved Permanently</strong> — the URL changed permanently; the new URL is in the response. Browsers and search engines update their links.</li>
<li><strong>302 Found</strong> — the URI changed temporarily; the client should keep using the same URI for future requests.</li>
<li><strong>304 Not Modified</strong> — used for caching: the response has not changed, so the client can keep using its cached version.</li>
<li><strong>307 Temporary Redirect</strong> — like 302, but the method must not change: a <code>POST</code> stays a <code>POST</code>.</li>
<li><strong>308 Permanent Redirect</strong> — like 301, but the method must not change either.</li>
</ul>
</section>
<section><h2 id="4xx-your-fault-mostly"><a href="#4xx-your-fault-mostly" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>4xx: your fault (mostly)</h2><ul>
<li><strong>400 Bad Request</strong> — the server cannot process the request due to a client error, like malformed syntax or invalid framing.</li>
<li><strong>401 Unauthorized</strong> — the name is misleading: semantically it means <em>unauthenticated</em>. The client must prove who it is.</li>
<li><strong>403 Forbidden</strong> — the client is known, but has no right to access the content.</li>
<li><strong>404 Not Found</strong> — the server cannot find the requested resource. In a browser this means the URL is not recognized; in an API the endpoint can be valid while the resource does not exist. Servers may also send 404 instead of 403 to hide a resource&#39;s existence.</li>
<li><strong>405 Method Not Allowed</strong> — the method is known but not supported by the target resource, e.g. an API that does not allow <code>DELETE</code> on a resource.</li>
<li><strong>408 Request Timeout</strong> — an idle connection the server wants to shut down.</li>
<li><strong>409 Conflict</strong> — the request conflicts with the current state of the server.</li>
<li><strong>410 Gone</strong> — the content was permanently deleted with no forwarding address; clients should drop their caches and links. Intended for limited-time, promotional resources — APIs need not use it for every deleted resource.</li>
<li><strong>418 I&#39;m a teapot</strong> — the server refuses to brew coffee with a teapot. Yes, this is a real, standardized code, kept for its humor and history.</li>
<li><strong>429 Too Many Requests</strong> — rate limiting: too many requests in a given time.</li>
</ul>
</section>
<section><h2 id="5xx-the-server-s-fault"><a href="#5xx-the-server-s-fault" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>5xx: the server&#39;s fault</h2><ul>
<li><strong>500 Internal Server Error</strong> — the server hit a situation it does not know how to handle; the generic code used when nothing more specific fits.</li>
<li><strong>501 Not Implemented</strong> — the request method is not supported by the server. <code>GET</code> and <code>HEAD</code> are the only methods servers must support.</li>
<li><strong>502 Bad Gateway</strong> — a server acting as a gateway got an invalid response from the upstream server.</li>
<li><strong>503 Service Unavailable</strong> — the server is not ready: down for maintenance or overloaded. A user-friendly page should be sent along, and the temporary condition should usually not be cached.</li>
<li><strong>504 Gateway Timeout</strong> — a server acting as a gateway could not get a response in time.</li>
<li><strong>511 Network Authentication Required</strong> — the client must authenticate to gain network access (think captive portals).</li>
</ul>
</section>
<section><h2 id="how-to-read-a-code-you-ve-never-seen"><a href="#how-to-read-a-code-you-ve-never-seen" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How to read a code you&#39;ve never seen</h2><p>If a response is not in the standard list, it is non-standard, possibly custom to the server&#39;s software. The class digit still tells you the shape of the story: 1xx means keep waiting, 2xx means it worked, 3xx means redirected, 4xx means the client should fix something, 5xx means the server is failing. That rule of thumb survives even the oddest custom code.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-does-a-404-not-found-mean">What does a 404 Not Found mean?</h3><p>A 404 means the server cannot find the requested resource. In a browser it means the URL is not recognized; in an API the endpoint can be valid while the resource does not exist. Servers may also send 404 instead of 403 to hide a resource&#39;s existence.</p>
<h3 id="what-is-the-difference-between-301-and-308">What is the difference between 301 and 308?</h3><p>A 301 Moved Permanently means the URL changed permanently and the new URL is in the response, with browsers and search engines updating their links. A 308 Permanent Redirect works like 301, but keeps the HTTP method unchanged: a <code>POST</code> stays a <code>POST</code>.</p>
<h3 id="what-is-the-difference-between-401-unauthorized-and-403-forbidden">What is the difference between 401 Unauthorized and 403 Forbidden?</h3><p>A 401 means the client is not authenticated — it has to prove who it is (the name is misleading). A 403 means the client is known, but has no right to access the content. In short: 401 is about missing identity, 403 about missing permission.</p>
<h3 id="why-am-i-seeing-a-503-service-unavailable">Why am I seeing a 503 Service Unavailable?</h3><p>A 503 means the server is not ready to handle the request, typically because it is down for maintenance or overloaded. A user-friendly page should be sent along, and the temporary condition should usually not be cached.</p>
</section>
<section><h2 id="summary"><a href="#summary" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Summary</h2><p>Status codes are the HTTP way of answering &quot;how did that go?&quot; — five classes covering every outcome from &quot;still working&quot; to &quot;server on fire&quot;. The codes you will actually meet are few: 200, 201, 301, 304, 404, 429, 500, 502, 503, 504. Recognize those and you can read any API response or server log with confidence.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>What is a firewall? Types and how they work</title>
      <link>https://debod.id/en/posts/firewall-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/firewall-explained/</guid>
      <pubDate>Tue, 18 Aug 2026 00:00:00 GMT</pubDate>
      <category>security</category>
      <category>networking</category>
      <category>security</category>
      <description>A plain explanation of firewalls — what they filter, the main types, and what they cannot protect.</description>
      <content:encoded><![CDATA[<section><p>A firewall is a filter for network traffic. It sits between a network and the rest of the internet, deciding what is allowed through based on rules. This article explains the main types and their limits.</p></section>
<section><h2 id="the-core-idea"><a href="#the-core-idea" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The core idea</h2><p>Every device has thousands of data ports, each used for a kind of traffic — web on 443, mail on 25, and so on. A firewall examines incoming and outgoing data and applies rules: allow, block, or ask. The goal is to keep unwanted traffic out while letting legitimate traffic through.</p>
</section>
<section><h2 id="main-types"><a href="#main-types" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Main types</h2><h3 id="packet-filtering-firewalls">Packet-filtering firewalls</h3><p>The oldest and simplest type. It inspects each packet&#39;s header — source IP, destination IP, port, protocol — and decides based on a rule table. Fast and cheap, but it does not look inside the packet&#39;s content.</p>
<h3 id="stateful-firewalls">Stateful firewalls</h3><p>These track the state of connections. If your computer opened a connection to a server, return traffic on that connection is allowed automatically; an unsolicited packet from outside is not. This is the standard for modern home and office firewalls.</p>
<h3 id="application-proxy-firewalls">Application (proxy) firewalls</h3><p>These work at the application layer and can understand the protocol itself (HTTP, SMTP...). They can block specific behaviors — for example, a URL category or a malformed request — not just ports. Slower, but much more precise.</p>
<h3 id="next-generation-firewalls-ngfw">Next-generation firewalls (NGFW)</h3><p>An evolution that combines stateful filtering with application awareness, intrusion prevention (IPS), and sometimes malware scanning. They inspect traffic deeply and are common in enterprises.</p>
</section>
<section><h2 id="what-a-firewall-protects-against"><a href="#what-a-firewall-protects-against" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What a firewall protects against</h2><ul>
<li><strong>Unauthorized access</strong> — attackers probing open ports cannot reach services you did not open.</li>
<li><strong>Malware callbacks</strong> — some malware phones home; outbound rules can block it.</li>
<li><strong>Some network-based attacks</strong> — port scans, connection floods within rule limits.</li>
</ul>
</section>
<section><h2 id="what-a-firewall-cannot-do"><a href="#what-a-firewall-cannot-do" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What a firewall cannot do</h2><ul>
<li><strong>Block phishing</strong> — a firewall does not judge whether a page is fake once HTTPS traffic is allowed.</li>
<li><strong>Catch malware inside files</strong> — downloaded files and attachments are usually passed unless inspected by an additional layer.</li>
<li><strong>Stop human mistakes</strong> — an employee (or user) clicking a malicious link or granting access is outside firewall scope.</li>
<li><strong>Protect after compromise</strong> — sophisticated attackers can tunnel through allowed channels.</li>
</ul>
</section>
<section><h2 id="home-versus-enterprise"><a href="#home-versus-enterprise" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Home versus enterprise</h2><p>Home routers ship with a basic firewall enabled by default (often stateful). For most home users that is enough. Enterprise networks add NGFWs, network segmentation and centralized logging because the threats and compliance needs are greater.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-is-a-firewall-and-how-does-it-work">What is a firewall and how does it work?</h3><p>A firewall is a filter for network traffic that sits between a network and the internet, deciding what traffic is allowed through based on predefined rules. It examines incoming and outgoing data packets and applies rules to allow, block, or request authentication for traffic, aiming to keep unwanted traffic out while letting legitimate traffic through.</p>
<h3 id="what-are-the-main-types-of-firewalls">What are the main types of firewalls?</h3><p>The main types are packet-filtering firewalls (which inspect packet headers), stateful firewalls (which track connection states), application (proxy) firewalls (which work at the application layer and understand protocols), and next-generation firewalls (NGFW) that combine stateful filtering with application awareness and intrusion prevention.</p>
<h3 id="what-can-a-firewall-protect-against">What can a firewall protect against?</h3><p>A firewall can protect against unauthorized access to services, malware callbacks (by blocking outbound connections to malicious servers), and some network-based attacks like port scans and connection floods when they occur within rule limits.</p>
<h3 id="what-are-the-limitations-of-a-firewall">What are the limitations of a firewall?</h3><p>A firewall cannot block phishing attempts (since it doesn&#39;t evaluate page content once HTTPS is allowed), catch malware inside downloaded files (unless paired with additional inspection layers), prevent human mistakes like clicking malicious links, or protect systems after they have been compromised (as attackers can tunnel through allowed channels).</p>
</section>
<section><h2 id="summary"><a href="#summary" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Summary</h2><p>A firewall filters network traffic based on rules, blocking what should not pass while allowing legitimate use. It is a necessary first layer of security, but it is not complete protection — phishing, malware and human error still need other defenses. Used together, they make a network much harder to break into.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>How email works: SMTP, IMAP and POP3 explained</title>
      <link>https://debod.id/en/posts/email-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/email-explained/</guid>
      <pubDate>Tue, 18 Aug 2026 00:00:00 GMT</pubDate>
      <category>web</category>
      <category>networking</category>
      <description>What happens between pressing send and the message arriving — servers, protocols, and why IMAP beat POP3.</description>
      <content:encoded><![CDATA[<section><p>Email looks instant, but the message actually travels through several servers using different protocols. This article walks through the journey of one email.</p></section>
<section><h2 id="the-big-picture-three-protocols"><a href="#the-big-picture-three-protocols" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The big picture: three protocols</h2><p>Email uses three protocols, each with a job:</p>
<ul>
<li><strong>SMTP</strong> — sending. Moves messages between mail servers, and from your device to your server.</li>
<li><strong>IMAP</strong> — reading and managing messages on the server. Your devices stay in sync.</li>
<li><strong>POP3</strong> — older way to read: downloads messages and removes them from the server.</li>
</ul>
</section>
<section><h2 id="the-journey-of-a-message"><a href="#the-journey-of-a-message" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The journey of a message</h2><p>You write to <code>friend@example.com</code> and press send:</p>
<ol>
<li><strong>Your email app talks SMTP to your provider&#39;s server</strong> (say, your Gmail or Outlook server). It hands over the message: envelope (sender, recipient), headers, body.</li>
<li><strong>Your server looks up the destination.</strong> It does an MX (mail exchange) lookup for <code>example.com</code> — DNS tells it which server accepts mail for that domain.</li>
<li><strong>The servers talk SMTP.</strong> Your server connects to the destination&#39;s mail server and hands the message over. Servers retry if the destination is temporarily unavailable — that is why email rarely fails permanently; it queues.</li>
<li><strong>The destination server delivers to a mailbox</strong> — a store on disk.</li>
<li><strong>Your friend reads it</strong> — their app uses IMAP to fetch and display the message, keeping it on the server.</li>
</ol>
</section>
<section><h2 id="smtp-details"><a href="#smtp-details" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>SMTP details</h2><p>SMTP is a simple text protocol: the client sends commands like <code>MAIL FROM</code>, <code>RCPT TO</code>, <code>DATA</code>, and the server replies with status codes. Encryption came later — modern SMTP uses STARTTLS to encrypt the connection between servers. Note: SMTP historically only guarantees delivery to the mailbox, not encryption of the content — that is why end-to-end encryption (S/MIME or PGP) remains a separate layer.</p>
</section>
<section><h2 id="imap-versus-pop3-why-imap-won"><a href="#imap-versus-pop3-why-imap-won" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>IMAP versus POP3 — why IMAP won</h2><ul>
<li><strong>POP3</strong> downloads mail to one device. A message read on your laptop is gone from the server — your phone will not see it. Very limited for multiple devices.</li>
<li><strong>IMAP</strong> keeps everything on the server. Folders, read flags, drafts stay in sync across all devices. That is why IMAP is the default in modern email apps.</li>
</ul>
</section>
<section><h2 id="spam-and-security"><a href="#spam-and-security" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Spam and security</h2><p>Because SMTP is easy to abuse, extra checks were added:</p>
<ul>
<li><strong>SPF</strong> — the domain declares which servers may send its mail.</li>
<li><strong>DKIM</strong> — the sending server digitally signs messages.</li>
<li><strong>DMARC</strong> — tells receiving servers what to do when SPF/DKIM fail.</li>
</ul>
<p>These are published as DNS records. They do not stop every spam message, but they make forging a domain much harder.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-does-smtp-do">What does SMTP do?</h3><p>SMTP (Simple Mail Transfer Protocol) moves messages between mail servers and from your device to your server. It is a simple text protocol: the client sends commands like <code>MAIL FROM</code>, <code>RCPT TO</code> and <code>DATA</code>, and the server replies with status codes. Modern SMTP uses STARTTLS to encrypt the connection between servers.</p>
<h3 id="what-is-the-difference-between-imap-and-pop3">What is the difference between IMAP and POP3?</h3><p>POP3 downloads mail to one device and removes it from the server — a message read on your laptop will not appear on your phone. IMAP keeps everything on the server, so folders, read flags and drafts stay in sync across all devices. That is why IMAP is the default in modern email apps.</p>
<h3 id="why-can-email-delivery-take-a-few-minutes">Why can email delivery take a few minutes?</h3><p>Because mail servers retry when the destination is temporarily unavailable. Your server connects to the destination&#39;s mail server and hands the message over; if the destination cannot be reached, the message is queued and delivery is attempted again. That is why email rarely fails permanently.</p>
<h3 id="what-are-spf-dkim-and-dmarc">What are SPF, DKIM and DMARC?</h3><p>They are DNS-based checks against email forgery. SPF declares which servers may send mail for a domain. DKIM makes the sending server digitally sign messages. DMARC tells receiving servers what to do when SPF or DKIM fail. They do not stop every spam message, but they make forging a domain much harder.</p>
</section>
<section><h2 id="summary"><a href="#summary" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Summary</h2><p>Email is moved by SMTP between servers, stored in mailboxes, and read with IMAP (or POP3 on old setups). DNS decides where mail for a domain goes, and SPF/DKIM/DMARC protect against forgery. The next time you send a message, it silently travels through at least two or three servers to reach its mailbox.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>Domain name vs hosting: what&apos;s the difference?</title>
      <link>https://debod.id/en/posts/domain-vs-hosting/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/domain-vs-hosting/</guid>
      <pubDate>Tue, 18 Aug 2026 00:00:00 GMT</pubDate>
      <category>web</category>
      <category>domain</category>
      <category>hosting</category>
      <category>web</category>
      <description>An address versus a house — what a domain name is, what hosting is, and why you need both for a website.</description>
      <content:encoded><![CDATA[<section><p>When people start a website, two terms come up immediately: domain name and hosting. They are different things that work together. This article explains both, with a simple analogy.</p></section>
<section><h2 id="the-analogy-address-versus-house"><a href="#the-analogy-address-versus-house" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The analogy: address versus house</h2><ul>
<li>A <strong>domain name</strong> is the address of your website: <code>example.com</code>. It tells people where to find you.</li>
<li><strong>Hosting</strong> is the house at that address — the server that stores your files and delivers them when someone visits.</li>
</ul>
<p>You need both. An address without a house points nowhere; a house with no address is impossible to find.</p>
</section>
<section><h2 id="what-a-domain-name-is"><a href="#what-a-domain-name-is" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What a domain name is</h2><p>A domain is a human-readable name that maps to one or more IP addresses through DNS. You do not buy a domain outright — you <strong>register</strong> it for a period (usually one year, renewable) from a registrar. In exchange for a yearly fee, you control where that name points.</p>
<p>The pieces:</p>
<ul>
<li><strong>Registrar</strong> — the company that manages the registration (Cloudflare, Namecheap, GoDaddy...).</li>
<li><strong>DNS records</strong> — settings that connect the domain to services: <code>A</code> records for web servers, <code>MX</code> for mail, etc.</li>
<li><strong>Renewal</strong> — if you let a domain expire, someone else can register it.</li>
</ul>
</section>
<section><h2 id="what-hosting-is"><a href="#what-hosting-is" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What hosting is</h2><p>Hosting is a server that runs your website. Different scales:</p>
<ul>
<li><strong>Shared hosting</strong> — your site shares a server with others. Cheap, good for small sites.</li>
<li><strong>VPS (Virtual Private Server)</strong> — a slice of a server with dedicated resources. More control.</li>
<li><strong>Dedicated server</strong> — an entire machine for you. Maximum performance, maximum cost.</li>
<li><strong>Cloud/static hosting</strong> — files served from a global network (Cloudflare Pages, Netlify, Vercel). Often free for static sites — this site is an example.</li>
</ul>
<p>Hosting companies also offer extras: domains registration, email, databases, backups, support.</p>
</section>
<section><h2 id="how-they-connect"><a href="#how-they-connect" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How they connect</h2><p>When someone visits <code>example.com</code>:</p>
<ol>
<li><strong>DNS resolves the domain</strong> to the IP of your hosting server.</li>
<li><strong>The browser reaches the server</strong>, which serves your files.</li>
<li>The address (domain) and the house (hosting) finish the job together.</li>
</ol>
<p>You can change hosting without changing your domain — just point the DNS records at the new server. And you can keep a domain without hosting at all (parked), but then it serves no website.</p>
</section>
<section><h2 id="common-beginner-mistakes"><a href="#common-beginner-mistakes" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Common beginner mistakes</h2><ul>
<li><strong>Buying hosting before a domain</strong> — fine, but you cannot launch until you have both.</li>
<li><strong>Confusing renewal fees</strong> — domains and hosting are billed separately, both yearly or monthly.</li>
<li><strong>Ignoring DNS</strong> — after migrating hosts, updates take time (TTL) to propagate.</li>
</ul>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-is-the-difference-between-a-domain-name-and-hosting">What is the difference between a domain name and hosting?</h3><p>A domain name is the address of your website — a human-readable name like <code>example.com</code> that maps to IP addresses through DNS, and which you register yearly from a registrar. Hosting is the server that stores your website&#39;s files and delivers them when someone visits. You need both: an address without a house points nowhere, and a house without an address is impossible to find.</p>
<h3 id="do-i-need-both-a-domain-and-hosting-to-launch-a-website">Do I need both a domain and hosting to launch a website?</h3><p>Yes. The domain is what people type to reach you, and hosting is what actually serves your files. You can buy them from different companies — you just point the domain&#39;s DNS records at your hosting server. Buying hosting before a domain is fine, but you cannot launch until you have both.</p>
<h3 id="can-i-change-hosting-without-losing-my-domain">Can I change hosting without losing my domain?</h3><p>Yes. Hosting and domains are managed separately. To move, you point your domain&#39;s DNS records at the new hosting company&#39;s server. Because the domain and the hosting are separate, changing one does not affect the other — though after switching hosts you should remember that DNS changes take time (TTL) to propagate.</p>
<h3 id="is-a-domain-a-one-time-purchase-or-a-yearly-fee">Is a domain a one-time purchase or a yearly fee?</h3><p>A domain is not bought outright — you register it for a period, usually one year, and renew it yearly from a registrar. If you let the registration expire, someone else can register that name. Hosting is billed separately, yearly or monthly, depending on the plan.</p>
</section>
<section><h2 id="summary"><a href="#summary" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Summary</h2><p>A domain is your website&#39;s address, registered yearly from a registrar. Hosting is the server that stores and delivers the site. They work together through DNS, and you manage them separately — which means you can change one without losing the other.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>How browser cookies work: a plain guide</title>
      <link>https://debod.id/en/posts/cookies-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/cookies-explained/</guid>
      <pubDate>Tue, 18 Aug 2026 00:00:00 GMT</pubDate>
      <category>web</category>
      <category>cookies</category>
      <category>privacy</category>
      <category>web</category>
      <category>browser</category>
      <description>What cookies are, how session and tracking cookies differ, and what &apos;accept all cookies&apos; actually means.</description>
      <content:encoded><![CDATA[<section><p>Every website seems to ask about cookies. This article explains what cookies actually are, what they store, and why the popup exists.</p></section>
<section><h2 id="what-a-cookie-is"><a href="#what-a-cookie-is" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What a cookie is</h2><p>A cookie is a small text file that a website stores in your browser. When you visit the site again, the browser sends the cookie back. This is how a site remembers you between visits.</p>
<p>Cookies were invented to solve a real problem: HTTP has no memory. Each request is independent. Without cookies, every page load would be a brand-new anonymous visitor, and &quot;log me in&quot; could not work.</p>
</section>
<section><h2 id="what-cookies-store"><a href="#what-cookies-store" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What cookies store</h2><p>A cookie holds a small amount of data: an identifier, a preference, an expiration time. Cookies cannot run code and cannot read your hard drive. They contain only what the site put in them — usually a randomly generated session ID.</p>
</section>
<section><h2 id="session-cookies-versus-persistent-cookies"><a href="#session-cookies-versus-persistent-cookies" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Session cookies versus persistent cookies</h2><ul>
<li><strong>Session cookies</strong> — live only while the browser is open. They keep you logged in as you move between pages; they disappear when you close the browser.</li>
<li><strong>Persistent cookies</strong> — have an expiry date and survive restart. &quot;Remember me&quot; checkboxes create persistent cookies.</li>
</ul>
</section>
<section><h2 id="first-party-versus-third-party-cookies"><a href="#first-party-versus-third-party-cookies" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>First-party versus third-party cookies</h2><ul>
<li><strong>First-party</strong> — set by the site you are visiting. They power logins, shopping carts and preferences.</li>
<li><strong>Third-party</strong> — set by a different domain, usually through embedded content (ads, analytics, videos). These are the tracking cookies used to build a profile of your browsing across sites.</li>
</ul>
</section>
<section><h2 id="the-consent-popup"><a href="#the-consent-popup" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The consent popup</h2><p>The popup exists because of privacy laws — in Europe the ePrivacy directive and GDPR require sites to ask before setting non-essential cookies. &quot;Accept all&quot; typically allows advertising and analytics cookies. &quot;Reject&quot; or &quot;necessary only&quot; keeps only the cookies needed for the site to function.</p>
</section>
<section><h2 id="how-to-control-cookies"><a href="#how-to-control-cookies" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How to control cookies</h2><ul>
<li><strong>Browser settings</strong> — block third-party cookies, or clear cookies when you close the browser.</li>
<li><strong>Site settings</strong> — most browsers let you inspect and delete cookies per site.</li>
<li><strong>Privacy modes</strong> — incognito/private windows discard cookies when closed.</li>
</ul>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-is-the-difference-between-a-session-cookie-and-a-persistent-cookie">What is the difference between a session cookie and a persistent cookie?</h3><p>A session cookie lives only while the browser is open and disappears when you close it — it keeps you logged in as you move between pages. A persistent cookie has an expiry date set by the site and survives browser restarts, which is how &quot;remember me&quot; checkboxes keep you signed in. The practical difference is lifetime: one ends with the browser session, the other with a date.</p>
<h3 id="what-is-the-difference-between-first-party-and-third-party-cookies">What is the difference between first-party and third-party cookies?</h3><p>A first-party cookie is set by the site you are visiting and powers logins, carts and preferences. A third-party cookie is set by a different domain, usually through embedded ads, analytics or videos, and is used to build a profile of your browsing across sites. The first makes the web usable; the second is what privacy controls clamp down on.</p>
<h3 id="why-does-every-site-show-a-cookie-consent-popup">Why does every site show a cookie consent popup?</h3><p>Privacy laws require it — in Europe the ePrivacy directive and the GDPR demand that sites ask before setting non-essential cookies. &quot;Accept all&quot; typically allows advertising and analytics cookies; &quot;Reject&quot; or &quot;only necessary&quot; keeps only the cookies the site needs to function. The popup is the site asking for permission it is legally required to request.</p>
<h3 id="can-cookies-read-my-hard-drive-or-run-programs">Can cookies read my hard drive or run programs?</h3><p>No. A cookie is just a small text file with the data the site put in it — usually a random session ID. Cookies cannot execute code and cannot read your hard drive; they contain only what the site stored. They are not programs and cannot act on their own.</p>
</section>
<section><h2 id="summary"><a href="#summary" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Summary</h2><p>Cookies are small files that give websites memory — they power logins, preferences and carts. The debates around them are mainly about third-party tracking. Understanding the difference between first- and third-party cookies is the practical part: the first makes the web usable, the second is what privacy controls clamp down on.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>What is a CDN? A plain explanation</title>
      <link>https://debod.id/en/posts/cdn-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/cdn-explained/</guid>
      <pubDate>Tue, 18 Aug 2026 00:00:00 GMT</pubDate>
      <category>networking</category>
      <category>cdn</category>
      <category>networking</category>
      <category>web</category>
      <category>performance</category>
      <description>Why web pages load fast everywhere: how content delivery networks cache files at the edge, route visitors to the nearest server, and protect sites from attacks.</description>
      <content:encoded><![CDATA[<section><p>A content delivery network (CDN) is a geographically distributed network of servers that delivers web content by caching it close to users. Instead of every visitor downloading files from your single origin server, most of them are served from a nearby edge server — which is why a site on a CDN feels fast on every continent.</p></section>
<section><h2 id="the-problem-cdns-solve"><a href="#the-problem-cdns-solve" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The problem CDNs solve</h2><p>In the early internet, content lived on one server in one city. A visitor on the other side of the planet had to cross many network hops to reach it, and the server had to answer every request itself. As the internet grew in the late 1990s, this became a performance bottleneck, and the first CDNs appeared to fix it.</p>
<p>Today, a large portion of internet content — text, images, video, software downloads — is delivered through CDNs.</p>
</section>
<section><h2 id="how-a-cdn-works"><a href="#how-a-cdn-works" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How a CDN works</h2><p>A CDN is a network of servers linked together, with the goal of delivering content as quickly, cheaply and reliably as possible. The pieces:</p>
<ul>
<li><strong>Origin server</strong> — the &quot;source of truth&quot;, the server that holds the original copy of your site or app.</li>
<li><strong>Edge servers</strong> — thousands of cache servers placed around the world, often in data centers called points of presence (PoPs).</li>
<li><strong>Routing</strong> — when a visitor requests your site, the CDN connects them to the nearest edge location.</li>
</ul>
<p>What happens on a typical request:</p>
<ol>
<li><strong>Your browser asks for a file</strong> — an image, a stylesheet, a page.</li>
<li><strong>The nearest edge server checks its cache.</strong> If it has the file, that is a <em>cache hit</em>: the answer comes back in milliseconds, and the origin server is never contacted.</li>
<li><strong>On a <em>cache miss</em> the edge fetches the file from the origin</strong>, stores a copy, and serves it.</li>
<li><strong>The next visitor</strong> who asks for the same file gets the cached copy.</li>
</ol>
<p>The origin only sees requests it cannot avoid — which dramatically reduces its load.</p>
</section>
<section><h2 id="what-a-cdn-caches"><a href="#what-a-cdn-caches" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What a CDN caches</h2><p>Caching works because most web content is static: it does not change for every visitor. Files are stored at the edge for a limited time, controlled by HTTP caching rules (for example <code>Cache-Control</code> headers and a TTL). When the copy expires, the next request refreshes it from the origin.</p>
<p>Dynamic content — personalized pages, shopping carts — is harder to cache. CDNs handle it by passing it through to the origin, or by running code at the edge that builds the response there.</p>
</section>
<section><h2 id="why-sites-use-a-cdn"><a href="#why-sites-use-a-cdn" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Why sites use a CDN</h2><ul>
<li><strong>Speed</strong> — files travel a shorter distance, so pages load faster, which also helps search rankings and conversion.</li>
<li><strong>Availability</strong> — if one data center or the origin fails, traffic is rerouted to other edge locations; the site keeps working.</li>
<li><strong>Resilience and security</strong> — CDNs absorb large traffic spikes, mitigate DDoS attacks, and often manage TLS certificates and a web application firewall (WAF).</li>
<li><strong>Cost</strong> — the origin serves fewer requests, so origin bandwidth and infrastructure costs drop.</li>
</ul>
</section>
<section><h2 id="cdns-and-dns"><a href="#cdns-and-dns" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>CDNs and DNS</h2><p>A CDN relies on DNS to do its job. When your browser resolves your domain, the CDN&#39;s DNS answers with the address of the edge server that is best for your location. This happens on every connection, so the same domain can point to different servers for different visitors — while the domain itself never changes.</p>
</section>
<section><h2 id="who-runs-cdns"><a href="#who-runs-cdns" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Who runs CDNs</h2><p>Most large websites use a CDN, and the providers are household names in tech: Akamai, Cloudflare, Fastly, Amazon CloudFront and Google Cloud CDN, among others. Hosting platforms often include a CDN by default — this site, for example, is served through the Cloudflare network.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-is-the-difference-between-a-cache-hit-and-a-cache-miss">What is the difference between a cache hit and a cache miss?</h3><p>A cache hit happens when the nearest edge server already has the requested file, so the answer comes back in milliseconds and the origin server is never contacted. A cache miss means the file is not at the edge, so the server fetches it from the origin, stores a copy, and serves it — the next visitor asking for the same file gets the cached copy.</p>
<h3 id="can-a-cdn-cache-dynamic-content-like-personalized-pages">Can a CDN cache dynamic content like personalized pages?</h3><p>Dynamic content — personalized pages, shopping carts — is harder to cache because it changes per visitor. CDNs handle it by passing it through to the origin, or by running code at the edge that builds the response there. Static content, controlled by HTTP caching rules like <code>Cache-Control</code> and a TTL, is what gets stored at the edge for a limited time.</p>
<h3 id="how-does-a-cdn-make-my-website-faster-and-cheaper-to-run">How does a CDN make my website faster and cheaper to run?</h3><p>Files travel a shorter distance because most visitors are served from a nearby edge server, so pages load faster. At the same time the origin only sees requests it cannot avoid, which dramatically reduces its load — fewer requests means lower origin bandwidth and infrastructure costs.</p>
<h3 id="how-does-a-cdn-protect-a-site-from-attacks">How does a CDN protect a site from attacks?</h3><p>CDNs absorb large traffic spikes and mitigate DDoS attacks by spreading requests across thousands of edge servers around the world, so no single server is overwhelmed. They also often manage TLS certificates and a web application firewall (WAF).</p>
</section>
<section><h2 id="summary"><a href="#summary" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Summary</h2><p>A CDN is a world-wide network of caching servers that brings content closer to visitors. It makes sites faster and cheaper to run, and it protects them from traffic spikes and attacks. When a site &quot;loads fast everywhere&quot;, a CDN is usually the reason.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>What is a browser cache? How it speeds up websites</title>
      <link>https://debod.id/en/posts/browser-cache-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/browser-cache-explained/</guid>
      <pubDate>Tue, 18 Aug 2026 00:00:00 GMT</pubDate>
      <category>web</category>
      <category>cache</category>
      <category>web</category>
      <category>performance</category>
      <category>browser</category>
      <description>Where your browser stores copies of images, scripts and fonts, why cached pages load faster, and how to clear the cache or force a fresh copy.</description>
      <content:encoded><![CDATA[<section><p>A browser cache is the storage on your device where your browser keeps copies of the files it has downloaded — images, stylesheets, scripts and fonts. When you visit a site again, the browser can reuse those local copies instead of downloading everything over the network again, which is why repeat visits feel almost instant.</p></section>
<section><h2 id="how-the-browser-cache-works"><a href="#how-the-browser-cache-works" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How the browser cache works</h2><p>Every request your browser makes is first routed to the cache to check for a valid stored response. If there is a match, the resource is read from the cache: no network round-trip, no data usage, no waiting. The browser only contacts the server when the cached copy is missing or out of date.</p>
<p>Each stored response has a lifetime. A <em>fresh</em> entry can be used without contacting the server at all. Once a response becomes <em>stale</em>, the browser must validate it: it sends a small conditional request to the server, asking &quot;has this file changed?&quot;. If the file is unchanged, the server replies <code>304 Not Modified</code> and the cached copy is reused without downloading the file again. That validation round-trip is far cheaper than re-downloading the whole resource.</p>
</section>
<section><h2 id="what-gets-stored-in-the-cache"><a href="#what-gets-stored-in-the-cache" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What gets stored in the cache</h2><p>The typical cache contents are static assets that rarely change:</p>
<ul>
<li>Images (JPEG, PNG, WebP, SVG)</li>
<li>Stylesheets (CSS)</li>
<li>JavaScript files</li>
<li>Fonts</li>
<li>Sometimes HTML pages, when the server allows it</li>
</ul>
<p>Websites use cache headers to control what is stored and for how long. The two most common mechanisms:</p>
<ul>
<li><strong><code>Cache-Control</code></strong> — the server states how long a file may be reused, for example <code>max-age=31536000</code> (one year). The <code>no-store</code> directive forbids caching, <code>no-cache</code> means &quot;store it, but always check with the server before reusing&quot;.</li>
<li><strong><code>ETag</code> / <code>Last-Modified</code></strong> — validation tokens. <code>ETag</code> is a fingerprint of the file content; when the browser validates, the server compares tokens and answers <code>304 Not Modified</code> if nothing changed.</li>
</ul>
<p>Because the browser cache is private to your device, it can also store personalized responses (for example a page tailored to your account) without leaking them to other users.</p>
</section>
<section><h2 id="why-a-cached-page-can-look-outdated"><a href="#why-a-cached-page-can-look-outdated" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Why a cached page can look outdated</h2><p>If a website releases a new design but a visitor still sees the old one, a stale cached file is usually the cause. The fix is a hard reload, which bypasses the cache and re-downloads everything from the server:</p>
<ul>
<li><strong>Windows / Linux:</strong> <code>Ctrl + F5</code> (or <code>Ctrl + Shift + R</code>)</li>
<li><strong>macOS:</strong> <code>Cmd + Shift + R</code></li>
</ul>
<p>Developers avoid this problem with <em>cache busting</em>: they change the file name whenever content changes (for example <code>styles-a1b2.css</code> instead of <code>styles.css</code>), so browsers treat it as a new file.</p>
</section>
<section><h2 id="how-to-clear-the-browser-cache"><a href="#how-to-clear-the-browser-cache" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How to clear the browser cache</h2><p>Clearing the cache removes the stored copies, not your passwords or logged-in sessions:</p>
<ol>
<li>Open the browser settings (or the history menu).</li>
<li>Find &quot;Clear browsing data&quot; (Chrome, Edge), &quot;Clear history and website data&quot; (Firefox), or the equivalent in Safari.</li>
<li>Select &quot;Cached images and files&quot; (leave cookies and site data alone unless you want to log out).</li>
</ol>
<p>You can also set the time range to &quot;All time&quot; for a full wipe. If a site still misbehaves after clearing, a hard reload usually finishes the job.</p>
</section>
<section><h2 id="browser-cache-vs-cookies"><a href="#browser-cache-vs-cookies" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Browser cache vs cookies</h2><p>The cache and cookies are different systems that often get mixed up. The cache stores <em>files</em> (images, scripts) and makes sites load faster. Cookies store <em>small pieces of data about you</em> (logins, preferences) and are sent to the server with each request. Clearing the cache does not log you out or delete your preferences; clearing cookies does. That is why &quot;clear the cache&quot; fixes broken-looking pages, while &quot;clear cookies&quot; fixes login and personalization problems.</p>
</section>
<section><h2 id="browser-cache-vs-cdn-cache"><a href="#browser-cache-vs-cdn-cache" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Browser cache vs CDN cache</h2><p>The browser cache runs on your device. A CDN runs a similar idea at a much larger scale: shared caches on servers around the world that serve the same files to many users. The two work together — the CDN makes the first visit fast from anywhere, and the browser cache makes repeat visits fast on your own device.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-is-the-difference-between-no-cache-and-no-store">What is the difference between <code>no-cache</code> and <code>no-store</code>?</h3><p><code>no-store</code> tells the browser and any intermediate cache not to store the response at all — every request goes back to the server. <code>no-cache</code> looks similar but actually means &quot;you may store it, but you must revalidate with the server before reusing the stored copy&quot;. With <code>no-cache</code> you still save the file; you just do not reuse it without checking. That is why <code>no-store</code> is the right choice for sensitive data, while <code>no-cache</code> is the right choice for content that changes often.</p>
<h3 id="does-clearing-the-browser-cache-also-log-me-out-of-websites">Does clearing the browser cache also log me out of websites?</h3><p>No, not by itself. Logins live in cookies, not in the cache. Clearing only &quot;Cached images and files&quot; keeps cookies and site data intact, so you stay signed in. You only get logged out when you also clear cookies, site data, or active sessions. This separation is exactly why &quot;clear the cache&quot; fixes broken-looking pages while &quot;clear cookies&quot; fixes login and personalization problems.</p>
<h3 id="what-is-the-difference-between-a-hard-reload-and-clearing-the-cache">What is the difference between a hard reload and clearing the cache?</h3><p>A hard reload (<code>Ctrl + F5</code> / <code>Cmd + Shift + R</code>) bypasses the cache for the current page only and forces the browser to fetch every file again. Clearing the cache removes the stored files for all sites and stays in effect until they are downloaded again. Use a hard reload when one page looks stale; use &quot;clear browsing data&quot; when the problem affects many sites or persists across reloads.</p>
<h3 id="why-does-my-browser-still-load-a-file-even-though-the-server-set-a-long-max-age">Why does my browser still load a file even though the server set a long <code>max-age</code>?</h3><p>Because the cache is a best-effort store. Browsers may evict entries to reclaim disk space, private browsing windows often use a separate cache that is wiped on close, and users can clear the cache at any time. A long <code>max-age</code> makes the file last as long as the browser keeps it, not forever — that is why servers pair <code>Cache-Control</code> with an <code>ETag</code> or <code>Last-Modified</code> so a re-downloaded copy can still be validated cheaply.</p>
</section>
<section><h2 id="summary"><a href="#summary" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Summary</h2><p>The browser cache is a local copy of the files a website needs, reused on later visits to save time and bandwidth. Servers control it with <code>Cache-Control</code> and validation headers, developers bust stale versions with changing file names, and you can always force a fresh copy with a hard reload or by clearing the cache in your browser settings.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>What is an API? A plain-language guide to how APIs work</title>
      <link>https://debod.id/en/posts/api-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/api-explained/</guid>
      <pubDate>Tue, 18 Aug 2026 00:00:00 GMT</pubDate>
      <category>programming</category>
      <category>api</category>
      <category>programming</category>
      <category>web</category>
      <category>http</category>
      <description>What an API is, how REST APIs work over HTTP, the methods you&apos;ll actually meet — GET, POST, PUT, PATCH, DELETE — and how to read a response.</description>
      <content:encoded><![CDATA[<section><p>An API (application programming interface) is a way for one program to talk to another — a contract that says: &quot;send me a request in this shape, and I&#39;ll answer in that shape.&quot; It is a connection between computers or between computer programs, offering a service to other pieces of software. When a weather app shows today&#39;s forecast, it is almost always reading an API.</p></section>
<section><h2 id="what-an-api-actually-is"><a href="#what-an-api-actually-is" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>What an API actually is</h2><p>An API is a software interface: a documented set of rules that describes how to build a connection between programs. A system that follows those rules is said to &quot;expose&quot; an API. The term can refer either to the specification (the document that describes the interface) or to the implementation (the running service).</p>
<p>The point of an API is that you never need to know what happens inside. The service keeps its internal logic private and publishes only the &quot;doors&quot; other programs can knock on. That separation is why a company can change its internals without breaking every app that depends on it.</p>
</section>
<section><h2 id="how-an-api-call-works-over-http"><a href="#how-an-api-call-works-over-http" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>How an API call works over HTTP</h2><p>Most web APIs today are HTTP APIs, and HTTP is a client-server protocol: one entity — the user-agent, often a browser but sometimes another program — sends a request, and the server answers. A request has three parts:</p>
<ul>
<li><strong>A URL</strong> that identifies what you want (for example, <code>https://api.example.com/v1/users</code>)</li>
<li><strong>A method</strong> that says what you want to do with it</li>
<li><strong>Headers</strong> that carry extra information, like an API key or a content type</li>
</ul>
<p>The server replies with a response: a <strong>status code</strong> that says whether it worked, and usually a <strong>body</strong> containing the data.</p>
</section>
<section><h2 id="the-http-methods-you-ll-actually-meet"><a href="#the-http-methods-you-ll-actually-meet" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The HTTP methods you&#39;ll actually meet</h2><table>
<thead>
<tr>
<th>Method</th>
<th>What it does</th>
<th>Idempotent?</th>
</tr>
</thead>
<tbody><tr>
<td>GET</td>
<td>Requests a representation of the specified resource — reads data</td>
<td>Yes (safe and cacheable)</td>
</tr>
<tr>
<td>POST</td>
<td>Sends data to the server, typically to create something</td>
<td>No</td>
</tr>
<tr>
<td>PUT</td>
<td>Creates a new resource or replaces the target resource</td>
<td>Yes</td>
</tr>
<tr>
<td>PATCH</td>
<td>Applies partial modifications to a resource</td>
<td>Not necessarily</td>
</tr>
<tr>
<td>DELETE</td>
<td>Asks the server to delete the specified resource</td>
<td>Yes</td>
</tr>
</tbody></table>
<p>A quick note on &quot;idempotent&quot;: it means calling the same request once or several times gives the same result, with no extra side effects. PUT and DELETE are idempotent; POST is not — which is why accidentally submitting the same order twice can create the order twice.</p>
</section>
<section><h2 id="reading-an-api-response"><a href="#reading-an-api-response" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Reading an API response</h2><p>The status code tells you most of the story:</p>
<ul>
<li><strong>200 OK</strong> — the request succeeded; for GET, the resource is in the response</li>
<li><strong>201 Created</strong> — a new resource was created, typically after a POST request</li>
<li><strong>204 No Content</strong> — success, but there is nothing to send back</li>
<li><strong>400 Bad Request</strong> — the server can&#39;t process the request; something is wrong with what you sent</li>
<li><strong>401 Unauthorized</strong> — semantically &quot;unauthenticated&quot;: you didn&#39;t prove who you are (missing or invalid API key)</li>
<li><strong>404 Not Found</strong> — the resource doesn&#39;t exist</li>
<li><strong>429 Too Many Requests</strong> — you hit a rate limit</li>
<li><strong>500 Internal Server Error</strong> — the server failed on its side</li>
</ul>
<p>The body is usually JSON these days. A typical response might look like:</p>
<pre><code class="language-json">{ &quot;id&quot;: 42, &quot;name&quot;: &quot;Debod&quot;, &quot;plan&quot;: &quot;free&quot; }
</code></pre>
</section>
<section><h2 id="rest-the-style-behind-most-web-apis"><a href="#rest-the-style-behind-most-web-apis" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>REST: the style behind most web APIs</h2><p>REST is a software architectural style that was created to describe and guide the architecture of the World Wide Web. It emphasizes uniform interfaces, independent deployment of components, scalability of interactions, and a layered architecture that promotes caching. Most public APIs call themselves &quot;REST APIs&quot; even when they follow the style loosely. The practical takeaway: resources identified by URLs, manipulated with HTTP methods, and exchanged as JSON.</p>
</section>
<section><h2 id="why-apis-matter"><a href="#why-apis-matter" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Why APIs matter</h2><ul>
<li><strong>Integration:</strong> apps don&#39;t need to reinvent payments, maps, or login — they call someone else&#39;s API.</li>
<li><strong>Automation:</strong> scripts and tools can act on your behalf through APIs — deploying, sending messages, pulling reports.</li>
<li><strong>Separation of concerns:</strong> frontend and backend can evolve independently as long as the contract holds.</li>
</ul>
</section>
<section><h2 id="api-keys-and-safety"><a href="#api-keys-and-safety" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>API keys and safety</h2><p>Most APIs use an <strong>API key</strong> — a secret string that identifies you — sent in a request header. Treat keys like passwords: never put them in client-side code or public repositories. The server uses them to authenticate you and to apply rate limits; the 401 and 429 codes above are exactly what you see when authentication fails or you exceed a limit.</p>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-is-the-difference-between-an-api-and-a-web-service">What is the difference between an API and a web service?</h3><p>The terms overlap, but they are not the same. An API is any documented contract that lets one program call another. A web service is a specific kind of API — one that talks over the network, usually using HTTP. So every web service is an API, but not every API is a web service: an operating system also exposes APIs (system calls), and a library exposes APIs (its public functions) without any HTTP involved.</p>
<h3 id="are-get-post-put-patch-and-delete-always-supported">Are GET, POST, PUT, PATCH, and DELETE always supported?</h3><p>Not always. The standard says what each method <em>means</em>, but a specific API is free to only implement the methods it actually needs. The most common public pattern is to expose GET (read) and POST (create or trigger an action), and to overload POST for updates instead of offering PUT or PATCH. The verbs are a style, not a rule.</p>
<h3 id="what-does-it-mean-when-an-api-is-restful">What does it mean when an API is &quot;RESTful&quot;?</h3><p>A RESTful API is one that follows the REST architectural style: resources are identified by URLs, manipulated with standard HTTP methods, and exchanged in a stateless way. In practice, very few public APIs are 100% REST — most call themselves &quot;REST&quot; loosely. The practical signal: the same data can usually be reached with a URL like <code>/users/42</code> and modified with GET, POST, PUT/PATCH, or DELETE.</p>
<h3 id="why-do-i-get-a-401-instead-of-a-403-when-my-api-key-is-wrong">Why do I get a 401 instead of a 403 when my API key is wrong?</h3><p>A 401 means &quot;you have not proved who you are&quot; — the server does not know your identity, usually because the API key is missing or invalid. A 403 means &quot;I know who you are, but you are not allowed to do this.&quot; With a wrong key, the server cannot identify you at all, so it answers 401. Once the key is valid but lacks permission for that resource, the same server would answer 403.</p>
</section>]]></content:encoded>
    </item>
    <item>
      <title>How DNS works: a plain explanation</title>
      <link>https://debod.id/en/posts/dns-explained/</link>
      <guid isPermaLink="true">https://debod.id/en/posts/dns-explained/</guid>
      <pubDate>Mon, 17 Aug 2026 00:00:00 GMT</pubDate>
      <category>networking</category>
      <category>dns</category>
      <category>networking</category>
      <category>web</category>
      <description>What happens when you type a web address? A simple walkthrough of DNS — domain hierarchy, resolvers, caching and TTL.</description>
      <content:encoded><![CDATA[<section><p>Type a web address, press Enter, and a page appears. Behind that moment is the Domain Name System (DNS) — the phonebook of the internet. This article explains how it works without jargon.</p></section>
<section><h2 id="why-dns-exists"><a href="#why-dns-exists" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Why DNS exists</h2><p>Computers talk to each other using IP addresses like <code>192.0.2.1</code> or <code>2606:4700::6810:84e5</code>. Humans remember names like <code>example.com</code> instead. DNS translates names into addresses.</p>
<p>Every device connected to the internet uses DNS. It is all around you, and it runs on a strict hierarchy.</p>
</section>
<section><h2 id="the-domain-hierarchy"><a href="#the-domain-hierarchy" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The domain hierarchy</h2><p>A domain name is read from right to left:</p>
<ul>
<li><code>.</code> — the root. The topmost level; every name hangs below it.</li>
<li><code>com</code> (or <code>org</code>, <code>id</code>, <code>de</code>...) — the top-level domain (TLD).</li>
<li><code>example</code> — the second-level domain you register from a registrar.</li>
<li><code>www</code> — a subdomain, often used to point at the main site.</li>
</ul>
<p>Each level delegates authority to the next. Your registrar&#39;s DNS provider holds the records for the domain you own.</p>
</section>
<section><h2 id="the-steps-behind-the-scene"><a href="#the-steps-behind-the-scene" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>The steps behind the scene</h2><p>When your browser needs <code>example.com</code>, this happens:</p>
<ol>
<li><strong>Your device asks a recursive resolver.</strong> This is usually run by your ISP or a public service like Cloudflare 1.1.1.1 or Google 8.8.8.8.</li>
<li><strong>The resolver walks the hierarchy.</strong> It asks the root servers for <code>com</code>, then the <code>com</code> servers for <code>example.com</code>.</li>
<li><strong>The authoritative server answers.</strong> The DNS provider that hosts your domain&#39;s records returns the final answer — for example an <code>A</code> record with an IPv4 address, or an <code>AAAA</code> record with IPv6.</li>
<li><strong>Your browser connects</strong> to that IP address and loads the site.</li>
</ol>
<p>This whole journey usually takes milliseconds because of caching.</p>
</section>
<section><h2 id="caching-and-ttl"><a href="#caching-and-ttl" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Caching and TTL</h2><p>Answers are stored in memory for a limited time — the <strong>Time To Live (TTL)</strong>. Your device caches the answer, and so does the recursive resolver. A shorter TTL means changes spread faster (useful when you move a site to a new server). A longer TTL means fewer queries and faster responses.</p>
<p>Because of caching, changing DNS records takes time to propagate — that is why people say &quot;DNS changes can take up to 24-48 hours&quot;, even though the underlying TTL is often just minutes or hours.</p>
</section>
<section><h2 id="common-record-types"><a href="#common-record-types" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Common record types</h2><ul>
<li><strong>A</strong> — maps a name to an IPv4 address.</li>
<li><strong>AAAA</strong> — maps a name to an IPv6 address.</li>
<li><strong>CNAME</strong> — an alias pointing at another name.</li>
<li><strong>MX</strong> — mail exchange, tells email where to be delivered.</li>
<li><strong>TXT</strong> — arbitrary text, used for verification and security (SPF, DKIM).</li>
<li><strong>NS</strong> — name server, states which servers are authoritative for a domain.</li>
</ul>
</section>
<section><h2 id="faq"><a href="#faq" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>FAQ</h2><h3 id="what-does-dns-stand-for-and-what-does-it-do">What does DNS stand for and what does it do?</h3><p>DNS stands for Domain Name System. It is the phonebook of the internet: it translates human-friendly domain names like <code>example.com</code> into the IP addresses that computers use to route traffic. Without DNS you would have to remember numbers like <code>93.184.216.34</code> instead of names.</p>
<h3 id="why-do-dns-changes-sometimes-take-so-long-to-appear">Why do DNS changes sometimes take so long to appear?</h3><p>Because of caching. Answers are stored for a limited time called the Time To Live (TTL), on both your device and the recursive resolver. A short TTL spreads changes fast; a long TTL means fewer queries but slower propagation. That is why changes can take up to 24–48 hours even when the underlying TTL is only minutes or hours.</p>
<h3 id="what-is-the-difference-between-an-a-record-and-a-cname-record">What is the difference between an A record and a CNAME record?</h3><p>An A record maps a name directly to an IPv4 address, so it carries the actual server address. A CNAME record is an alias that points a name to another name instead of an address. Because it points to another name, a CNAME cannot be combined with other records such as an MX record on the same host.</p>
<h3 id="what-is-a-recursive-resolver">What is a recursive resolver?</h3><p>A recursive resolver is a server that performs the full DNS lookup on behalf of your device. It asks the root servers for the top-level domain, then the TLD servers for the specific domain, and finally gets the authoritative answer. Your ISP or a public service such as Cloudflare 1.1.1.1 or Google 8.8.8.8 runs one for you.</p>
</section>
<section><h2 id="summary"><a href="#summary" aria-label="Link to this section" class="anchor-link absolute -ml-5 mt-1 h-5 w-5 items-center justify-center text-muted no-underline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4"><path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414 2 2 0 002.828 0l3-3a2 2 0 000-2.828z"/><path d="M7.414 14.414a2 2 0 11-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 1 1 0 00-1.414-1.414 2 2 0 010-2.828l3-3a2 2 0 012.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 1 1 0 001.414 1.414z"/></svg></a>Summary</h2><p>DNS is a distributed directory that turns names into IP addresses. It works through a hierarchy of root, TLD and authoritative servers, with recursive resolvers doing the lookup and caches making it fast. Understanding TTL explains why record changes take time to appear.</p>
<p>It is one of the oldest parts of the internet, and still one of the most important.</p>
</section>]]></content:encoded>
    </item>
  </channel>
</rss>
