<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: horlartundhey</title>
    <description>The latest articles on DEV Community by horlartundhey (@horlartundhey).</description>
    <link>https://dev.to/horlartundhey</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F894489%2F18de470f-3167-4c13-8e1e-ffc794aea4b7.PNG</url>
      <title>DEV Community: horlartundhey</title>
      <link>https://dev.to/horlartundhey</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/horlartundhey"/>
    <language>en</language>
    <item>
      <title>Password Reset Without a Database Table: Stateless OTPs in Laravel</title>
      <dc:creator>horlartundhey</dc:creator>
      <pubDate>Thu, 30 Jul 2026 19:56:58 +0000</pubDate>
      <link>https://dev.to/horlartundhey/password-reset-without-a-database-table-stateless-otps-in-laravel-1b1k</link>
      <guid>https://dev.to/horlartundhey/password-reset-without-a-database-table-stateless-otps-in-laravel-1b1k</guid>
      <description>&lt;p&gt;&lt;em&gt;How an HMAC and a rounded clock replaced &lt;code&gt;password_reset_tokens&lt;/code&gt; — and the trade-offs nobody mentions.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgubxvpzff8h2rhtlmf72.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgubxvpzff8h2rhtlmf72.png" alt="Laravel logo" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Every Laravel developer knows the password reset flow. You run the migration, you get a &lt;code&gt;password_reset_tokens&lt;/code&gt; table, you call &lt;code&gt;Password::sendResetLink()&lt;/code&gt;, and the framework handles the rest. It works. It's battle-tested. It's the right answer most of the time.&lt;/p&gt;

&lt;p&gt;I recently built one that stores nothing at all — no table, no cache key, no session. The whole thing is an HMAC and a rounded clock.&lt;/p&gt;

&lt;p&gt;This is a walkthrough of how it works, why it was worth doing, and — the part most posts skip — an honest accounting of what it costs you.&lt;/p&gt;




&lt;h2&gt;
  
  
  The problem with storing reset tokens
&lt;/h2&gt;

&lt;p&gt;Laravel's default is a stateful design. A reset request writes a row:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;email                 | token (hashed)      | created_at
alice@example.com     | $2y$10$abc...       | 2026-07-24 09:00:00
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verification reads that row, compares the token, checks &lt;code&gt;created_at&lt;/code&gt; against &lt;code&gt;config('auth.passwords.users.expire')&lt;/code&gt;, and deletes it.&lt;/p&gt;

&lt;p&gt;Nothing is wrong with this. But it carries a tail of small obligations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A migration&lt;/strong&gt;, against a schema that may already be live.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rows that outlive their usefulness.&lt;/strong&gt; Expired tokens sit there until something prunes them. Laravel ships &lt;code&gt;auth:clear-resets&lt;/code&gt; — a scheduled command that many teams never actually wire up.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A write on every request.&lt;/strong&gt; Password reset is a low-volume endpoint, so this rarely matters — but it is a database write in your unauthenticated attack surface, which is a thing an attacker can make you do a lot of.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shared state across nodes.&lt;/strong&gt; Fine with one database. Something to think about with read replicas and replication lag, where a token written to the primary may not be visible to the replica the verification request lands on.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these are dealbreakers. They're just &lt;em&gt;there&lt;/em&gt;. And I had a codebase where the pattern for avoiding all of them already existed.&lt;/p&gt;




&lt;h2&gt;
  
  
  The insight: a token that verifies itself
&lt;/h2&gt;

&lt;p&gt;The passwordless-login flow in this application already did something clever, inline, in a controller:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$otp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;999999&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nv"&gt;$minuteBlock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$now&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;minute&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$lastBlockStart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$now&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$now&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;hour&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$minuteBlock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toDateTimeString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="s1"&gt;'nonce_key'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;hash_hmac&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'sha256'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$otp&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$lastBlockStart&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'app.key'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The idea underneath: &lt;strong&gt;don't store the code — store a proof that you issued it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The server generates a 6-digit OTP and emails it. It also computes an HMAC over &lt;code&gt;email + otp + time_block&lt;/code&gt;, keyed with the application secret, and hands &lt;em&gt;that&lt;/em&gt; back to the client as a &lt;code&gt;nonce_key&lt;/code&gt;. Then it forgets both.&lt;/p&gt;

&lt;p&gt;When the user submits their code, they send back three things: the email, the OTP from their inbox, and the &lt;code&gt;nonce_key&lt;/code&gt; their browser has been holding. The server recomputes the HMAC and compares.&lt;/p&gt;

&lt;p&gt;If it matches, the code is genuine — because only the server knows &lt;code&gt;APP_KEY&lt;/code&gt;, and therefore only the server could have produced that HMAC over that exact triple. &lt;strong&gt;The token carries its own proof of authenticity.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The pattern was worth promoting out of the controller and into something reusable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Support&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OtpNonce&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$identifier&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$otp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nf"&gt;app&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'production'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s1"&gt;'123456'&lt;/span&gt;
            &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nb"&gt;random_int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;999999&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'otp'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$otp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'nonce_key'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$identifier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$otp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;currentBlock&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
        &lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$identifier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$otp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$nonceKey&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;currentBlock&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;currentBlock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;hash_equals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$identifier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$otp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$block&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nv"&gt;$nonceKey&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$identifier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$otp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$block&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;hash_hmac&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'sha256'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$identifier&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;$otp&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;$block&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'app.key'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;currentBlock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$offsetMinutes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;addMinutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$offsetMinutes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$minuteBlock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$now&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;minute&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$now&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$now&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;hour&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;$minuteBlock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toDateTimeString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Forty lines. That's the whole mechanism.&lt;/p&gt;




&lt;h2&gt;
  
  
  Three details that carry the weight
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Expiry that requires no cleanup
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;currentBlock()&lt;/code&gt; floors the clock to a 5-minute boundary. &lt;code&gt;09:07:41&lt;/code&gt; becomes &lt;code&gt;09:05:00&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That timestamp goes &lt;em&gt;inside&lt;/em&gt; the hash. Which means: once the wall clock moves past the accepted window, the same &lt;code&gt;email + otp&lt;/code&gt; pair hashes to a different value, and the old &lt;code&gt;nonce_key&lt;/code&gt; simply stops matching.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The token expires because time moved, not because anything deleted it.&lt;/strong&gt; There is no cron job. There is no TTL to configure. There is nothing to garbage-collect.&lt;/p&gt;

&lt;p&gt;But quantised time has a sharp edge. A code issued at &lt;code&gt;09:04:59&lt;/code&gt; is bound to block &lt;code&gt;09:00&lt;/code&gt; — and one second later, &lt;code&gt;currentBlock()&lt;/code&gt; returns &lt;code&gt;09:05&lt;/code&gt;. One second of life.&lt;/p&gt;

&lt;p&gt;The fix is to accept the previous window too:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;currentBlock&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;currentBlock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now every code lives &lt;strong&gt;at least 5 minutes and at most 10&lt;/strong&gt;, depending on where in the window it was issued. It's the same trade-off TOTP authenticator apps make when they accept the adjacent time step to tolerate clock skew: a little precision traded for a lot of usability.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;hash_equals&lt;/code&gt;, always
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;hash_equals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$identifier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$otp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$block&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nv"&gt;$nonceKey&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PHP's &lt;code&gt;===&lt;/code&gt; short-circuits at the first differing byte. Comparing a secret with it leaks, through response latency, &lt;em&gt;how many leading bytes you got right&lt;/em&gt;. An attacker who can measure that reconstructs the expected value byte by byte — turning 2²⁵⁶ guesses into about 8,000.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;hash_equals()&lt;/code&gt; compares in constant time. It costs nothing. Use it every single time you compare something secret.&lt;/p&gt;

&lt;h3&gt;
  
  
  Binding is what makes it a token
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nb"&gt;hash_hmac&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'sha256'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$identifier&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;$otp&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;$block&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'app.key'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every input inside the hash is a property the token now enforces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;identifier&lt;/code&gt; inside → a nonce minted for &lt;code&gt;alice@&lt;/code&gt; will never validate for &lt;code&gt;bob@&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;otp&lt;/code&gt; inside → you can't pair a stolen nonce with a guessed code.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;block&lt;/code&gt; inside → it expires.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;APP_KEY&lt;/code&gt; as the key → nobody outside the server can forge one.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This last point deserves emphasis, because it's the one people get wrong. If you use a plain &lt;code&gt;hash('sha256', ...)&lt;/code&gt; over public inputs, &lt;strong&gt;anyone can compute it&lt;/strong&gt; and the token proves precisely nothing. HMAC keys the hash with a server secret. That's the entire difference between a token and a checksum.&lt;/p&gt;

&lt;p&gt;A useful side effect: rotating &lt;code&gt;APP_KEY&lt;/code&gt; instantly invalidates every outstanding code. Handy during an incident. Slightly annoying during a routine rotation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Splitting knowledge across two channels
&lt;/h2&gt;

&lt;p&gt;The controller side is where the design's real security property shows up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;forgotPassword&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'email'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'required'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'email:rfc,dns'&lt;/span&gt;&lt;span class="p"&gt;]]);&lt;/span&gt;

    &lt;span class="nv"&gt;$user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'email'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'email'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$otp&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OtpNonce&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'email'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;UserForgotPasswordOtp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$otp&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'otp'&lt;/span&gt;&lt;span class="p"&gt;]));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="s1"&gt;'status'&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'message'&lt;/span&gt;   &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'If that email is registered, a reset code has been sent.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'nonce_key'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$otp&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'nonce_key'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things are deliberate here.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First: the OTP is generated unconditionally.&lt;/strong&gt; Look at the ordering — &lt;code&gt;generate()&lt;/code&gt; runs whether or not the account exists. Only the &lt;em&gt;email send&lt;/em&gt; is conditional. So a registered address and an unregistered one produce byte-identical responses: same status, same message, same shape, a real &lt;code&gt;nonce_key&lt;/code&gt; either way.&lt;/p&gt;

&lt;p&gt;That closes &lt;strong&gt;user enumeration&lt;/strong&gt;. An attacker can't use this endpoint to work out which addresses have accounts. The deliberately hedged copy — &lt;em&gt;"If that email is registered…"&lt;/em&gt; — exists for the same reason, and is why GitHub, Stripe, and Laravel Fortify all word theirs the same way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Second: the two secrets travel over different channels.&lt;/strong&gt; The OTP goes to the inbox. The &lt;code&gt;nonce_key&lt;/code&gt; goes to the browser. Verification needs both.&lt;/p&gt;

&lt;p&gt;An attacker who compromises the email but not the session has a code and no nonce. One who intercepts the HTTP response but not the inbox has a nonce and no code. Neither half is sufficient.&lt;/p&gt;

&lt;p&gt;Verification is then trivial:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nf"&gt;abort_unless&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;OtpNonce&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;otp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;nonce_key&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HTTP_UNPROCESSABLE_ENTITY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'Invalid or expired code'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'email'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nf"&gt;abort_unless&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HTTP_UNPROCESSABLE_ENTITY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Invalid or expired code'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The HMAC check runs first — it's pure CPU, while the lookup is a query, so forged requests never cost a round-trip. And both failures return the &lt;em&gt;identical&lt;/em&gt; message. "Wrong code" and "no such user" are indistinguishable from outside. The repetition is the point.&lt;/p&gt;




&lt;h2&gt;
  
  
  Now the part that matters: what this costs you
&lt;/h2&gt;

&lt;p&gt;Here's where most posts about a clever pattern stop. They shouldn't.&lt;/p&gt;

&lt;h3&gt;
  
  
  Statelessness means you cannot enforce single use
&lt;/h3&gt;

&lt;p&gt;This is the big one, and it follows directly from the design rather than from any mistake in it.&lt;/p&gt;

&lt;p&gt;With nothing stored, &lt;strong&gt;nothing can be marked as used.&lt;/strong&gt; A code that successfully resets a password stays valid for the remainder of its window and can reset it again. A stateful token gets deleted on use. This one can't be.&lt;/p&gt;

&lt;p&gt;Any fix reintroduces exactly the state you were avoiding:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$fingerprint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'otp_used:'&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'sha256'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$nonceKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;abort_if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fingerprint&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;422&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Invalid or expired code'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fingerprint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;addMinutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's a legitimate middle ground — a 10-minute cache key is a far lighter obligation than a permanent table. But be clear-eyed: you are now stateful. Make it a decision, not an accident.&lt;/p&gt;

&lt;h3&gt;
  
  
  Statelessness also means you can't count attempts
&lt;/h3&gt;

&lt;p&gt;And this is where it gets genuinely dangerous.&lt;/p&gt;

&lt;p&gt;The routes as first written carried no &lt;code&gt;throttle&lt;/code&gt; middleware — while the email-verification routes directly beside them in the same file used &lt;code&gt;throttle:6,1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The attack is short:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;POST /forgot-password&lt;/code&gt; with the victim's email. &lt;strong&gt;You get a valid &lt;code&gt;nonce_key&lt;/code&gt; in the response.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Brute-force &lt;code&gt;POST /reset-password&lt;/code&gt; across the 900,000-code space.&lt;/li&gt;
&lt;li&gt;You never needed the victim's inbox at all.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Nothing burns attempts. Nothing locks out. Nothing alerts. The split-knowledge property I described above is exactly what collapses, because the endpoint &lt;em&gt;hands you&lt;/em&gt; one of the two halves for free.&lt;/p&gt;

&lt;p&gt;A stateful implementation gets a natural attempt counter — the row is right there. A stateless one has nowhere to put it, so the throttle has to be external:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'guest'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'throttle:5,10'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'reset-password'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;...&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'guest'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'throttle:3,10'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'forgot-password'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;...&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Throttling on email &lt;em&gt;and&lt;/em&gt; IP is stronger. But the honest framing is this: &lt;strong&gt;statelessness didn't remove the need for state — it moved it into the rate limiter.&lt;/strong&gt; If you skip that step, you have built a very elegant account-takeover endpoint.&lt;/p&gt;

&lt;h3&gt;
  
  
  Environment gates are sharper than they look
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$otp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nf"&gt;app&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'production'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s1"&gt;'123456'&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;random_int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;999999&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A fixed dev code is genuinely good DX — you test the flow in Postman without opening a mail catcher every time.&lt;/p&gt;

&lt;p&gt;But read the condition carefully. It says &lt;em&gt;"not production."&lt;/em&gt; Which means &lt;code&gt;staging&lt;/code&gt;, &lt;code&gt;testing&lt;/code&gt;, &lt;code&gt;demo&lt;/code&gt;, and any host with a typo'd &lt;code&gt;APP_ENV&lt;/code&gt; all issue &lt;code&gt;123456&lt;/code&gt;. Combined with the &lt;code&gt;nonce_key&lt;/code&gt; coming back in the response body, &lt;strong&gt;anyone can reset any account on such a host with zero email access.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Laravel defaults &lt;code&gt;APP_ENV&lt;/code&gt; to &lt;code&gt;production&lt;/code&gt; when unset, which is the right direction to fail. But deny-lists fail open by nature. Allow-list instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$fixed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;app&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'local'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'testing'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'auth.otp.fixed_code'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Your existing tokens don't care that the password changed
&lt;/h3&gt;

&lt;p&gt;This one isn't specific to stateless OTPs, but it's specific to JWT auth, and the two ship together often enough to be worth stating.&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;jwt-auth&lt;/code&gt;, tokens are self-contained and valid until they expire. Changing the password &lt;strong&gt;does not invalidate them.&lt;/strong&gt; An attacker holding a token keeps their access after the victim resets — which defeats the single most common reason a user resets a password under duress.&lt;/p&gt;

&lt;p&gt;You need a &lt;code&gt;token_version&lt;/code&gt; claim you can bump, or a blacklist entry on reset. Not optional if the reset flow is meant to be a recovery mechanism.&lt;/p&gt;




&lt;h2&gt;
  
  
  So: was it worth it?
&lt;/h2&gt;

&lt;p&gt;For this codebase, yes — with the throttle added.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What you get:&lt;/strong&gt; no migration on a live schema, no cleanup job, no shared state between nodes, and a verification path that's pure computation. Any app server can verify any code with no coordination whatsoever. In a horizontally-scaled deployment, that's a genuinely nice property, and it comes from forty lines with no infrastructure attached.&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>password</category>
      <category>backenddevelopment</category>
      <category>security</category>
    </item>
    <item>
      <title>DevOps Week 04: From Git Commits to a Live EC2 Server: Deploying CodeTrack with Nginx</title>
      <dc:creator>horlartundhey</dc:creator>
      <pubDate>Sat, 25 Jul 2026 00:14:37 +0000</pubDate>
      <link>https://dev.to/horlartundhey/devops-week-04-from-git-commits-to-a-live-ec2-server-deploying-codetrack-with-nginx-3p9a</link>
      <guid>https://dev.to/horlartundhey/devops-week-04-from-git-commits-to-a-live-ec2-server-deploying-codetrack-with-nginx-3p9a</guid>
      <description>&lt;h1&gt;
  
  
  From Git Commits to a Live EC2 Server: Deploying CodeTrack with Nginx
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr3n4tska7cphcgynkh4b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr3n4tska7cphcgynkh4b.png" alt=" " width="799" height="423"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;One of the most important lessons I am learning in my DevOps journey is that writing code is only one part of building software.&lt;/p&gt;

&lt;p&gt;A project also needs to be tracked, versioned, transferred, deployed, served, and verified.&lt;/p&gt;

&lt;p&gt;For this project, I worked on &lt;strong&gt;CodeTrack&lt;/strong&gt;, a static website that I first managed locally with Git and then deployed to a live &lt;strong&gt;AWS EC2&lt;/strong&gt; instance running &lt;strong&gt;Ubuntu and Nginx&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The goal was to simulate a basic real-world deployment workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Local Development
        ↓
Git Tracking
        ↓
Git Staging
        ↓
Meaningful Commits
        ↓
Secure File Transfer
        ↓
AWS EC2
        ↓
Nginx
        ↓
Live Website
        ↓
Deployment Verification
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This exercise helped me connect concepts that I had previously learned separately: Git, Linux, AWS EC2, SSH, SCP, Nginx, HTTP, and basic deployment validation.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;The project was a static website called &lt;strong&gt;CodeTrack&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The deployment workflow involved:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creating and managing the project files locally&lt;/li&gt;
&lt;li&gt;Tracking the files with Git&lt;/li&gt;
&lt;li&gt;Staging files intentionally&lt;/li&gt;
&lt;li&gt;Creating a clean initial commit&lt;/li&gt;
&lt;li&gt;Making a controlled homepage update&lt;/li&gt;
&lt;li&gt;Creating a second meaningful commit&lt;/li&gt;
&lt;li&gt;Transferring the project to an AWS EC2 instance using SCP&lt;/li&gt;
&lt;li&gt;Serving the website using Nginx&lt;/li&gt;
&lt;li&gt;Validating the deployment using &lt;code&gt;curl&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Confirming the application was accessible through the EC2 public IP&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The assignment was designed to connect local version-control practices with a basic manual deployment workflow used in DevOps environments.&lt;/p&gt;




&lt;h1&gt;
  
  
  Part 1: Verifying the Git Repository
&lt;/h1&gt;

&lt;p&gt;Before making any changes, I first verified that Git was installed and that I was working inside the correct project directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This confirmed that Git was available on the system.&lt;/p&gt;

&lt;p&gt;I then navigated into the CodeTrack project directory and checked the repository status:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This step is simple, but important.&lt;/p&gt;

&lt;p&gt;Running commands in the wrong directory is a common mistake when working with Git. Before modifying files or creating commits, it is important to know exactly where you are and whether the directory is actually a Git repository.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;git status&lt;/code&gt; command provides information about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The current branch&lt;/li&gt;
&lt;li&gt;Untracked files&lt;/li&gt;
&lt;li&gt;Modified files&lt;/li&gt;
&lt;li&gt;Staged files&lt;/li&gt;
&lt;li&gt;Changes waiting to be committed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This gave me a clear starting point before making any changes.&lt;/p&gt;




&lt;h1&gt;
  
  
  Part 2: Creating the Starter Files
&lt;/h1&gt;

&lt;p&gt;The project required two basic files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;index.html
style.css
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I created them using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;touch &lt;/span&gt;index.html style.css
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I confirmed that they existed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ls&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, the project had its basic structure, but the files still needed their actual HTML and CSS content.&lt;/p&gt;

&lt;p&gt;I then populated the files with the provided starter content.&lt;/p&gt;

&lt;p&gt;This represents a common development workflow: first creating the basic project structure, then adding the implementation.&lt;/p&gt;




&lt;h1&gt;
  
  
  Part 3: Understanding Git Tracking and Staging
&lt;/h1&gt;

&lt;p&gt;After creating the files, I checked the repository status again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because the files had not yet been added to Git, they appeared as untracked files.&lt;/p&gt;

&lt;p&gt;This distinction is important.&lt;/p&gt;

&lt;p&gt;A file can exist in the project directory without being tracked by Git.&lt;/p&gt;

&lt;p&gt;The basic Git workflow looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Working Directory
        ↓
git add
        ↓
Staging Area
        ↓
git commit
        ↓
Repository History
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I staged the files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add index.html
git add style.css
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I checked the status again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The files were now listed under:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Changes to be committed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This confirmed that Git was ready to include them in the next commit.&lt;/p&gt;

&lt;p&gt;One thing I learned from this process is that staging provides control over what goes into a commit. Instead of automatically committing every change in the project, I can deliberately choose which files should be included.&lt;/p&gt;




&lt;h1&gt;
  
  
  Part 4: Creating the First Commit
&lt;/h1&gt;

&lt;p&gt;Once the files were staged, I created the initial commit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Initial UI scaffold: add index.html and style.css"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This created the first snapshot of the project.&lt;/p&gt;

&lt;p&gt;A commit is essentially a saved point in the project's history.&lt;/p&gt;

&lt;p&gt;The first commit represented the initial UI scaffold:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Initial UI Scaffold
├── index.html
└── style.css
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I then verified the commit history:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git log &lt;span class="nt"&gt;--oneline&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The purpose of using a meaningful commit message is to make the project history easier to understand.&lt;/p&gt;

&lt;p&gt;Instead of seeing a history full of vague messages such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;update
changes
fix
new
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;a message like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Initial UI scaffold: add index.html and style.css
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;immediately explains what changed.&lt;/p&gt;

&lt;p&gt;Good commit messages become a useful work log for developers and teams.&lt;/p&gt;




&lt;h1&gt;
  
  
  Part 5: Making a Controlled Change
&lt;/h1&gt;

&lt;p&gt;After creating the initial commit, I made a controlled update to the homepage.&lt;/p&gt;

&lt;p&gt;The changes included updating the homepage content, including the student information and group details required by the assignment.&lt;/p&gt;

&lt;p&gt;After modifying &lt;code&gt;index.html&lt;/code&gt;, I checked the repository status:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Git detected that the file had been modified.&lt;/p&gt;

&lt;p&gt;This is different from the earlier untracked-file situation.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;index.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;was a file Git had never seen before.&lt;/p&gt;

&lt;p&gt;Now, Git already knew about the file because it had been included in the first commit.&lt;/p&gt;

&lt;p&gt;Therefore, Git could compare the current version with the version stored in the repository and detect the changes.&lt;/p&gt;

&lt;p&gt;I then staged only the modified file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add index.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After confirming the staging area, I created a second commit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Update homepage content: heading, tagline, CTA button"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I then checked the commit history again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git log &lt;span class="nt"&gt;--oneline&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The history now contained two separate commits:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Update homepage content: heading, tagline, CTA button

Initial UI scaffold: add index.html and style.css
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This separation is useful because the project history now clearly shows two different stages of development:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The initial UI was created.&lt;/li&gt;
&lt;li&gt;The homepage content was later updated.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the second change introduced a problem, it could be reviewed or reverted separately from the original project scaffold.&lt;/p&gt;

&lt;p&gt;This is one of the practical benefits of maintaining meaningful commits.&lt;/p&gt;




&lt;h1&gt;
  
  
  Part 6: Preparing the AWS EC2 Server
&lt;/h1&gt;

&lt;p&gt;After completing the local Git workflow, the next step was to deploy the website to a live server.&lt;/p&gt;

&lt;p&gt;The deployment target was an AWS EC2 instance running Ubuntu.&lt;/p&gt;

&lt;p&gt;Before deploying, the server needed to have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A running EC2 instance&lt;/li&gt;
&lt;li&gt;SSH access&lt;/li&gt;
&lt;li&gt;HTTP access through port 80&lt;/li&gt;
&lt;li&gt;Nginx installed and running&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The basic deployment architecture looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser
   │
   │ HTTP Request
   ▼
EC2 Public IP
   │
   ▼
Nginx
   │
   ▼
/var/www/html
   │
   ├── index.html
   └── style.css
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The EC2 Security Group needed to allow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SSH traffic on port &lt;code&gt;22&lt;/code&gt; from the appropriate IP address&lt;/li&gt;
&lt;li&gt;HTTP traffic on port &lt;code&gt;80&lt;/code&gt; for testing the website&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is an important security consideration.&lt;/p&gt;

&lt;p&gt;SSH is necessary for server administration, but it should not be exposed more broadly than necessary.&lt;/p&gt;

&lt;p&gt;HTTP, on the other hand, needs to be accessible to users who want to visit the website.&lt;/p&gt;




&lt;h1&gt;
  
  
  Part 7: Verifying Nginx
&lt;/h1&gt;

&lt;p&gt;Before deploying the CodeTrack files, I verified the state of Nginx.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl status nginx &lt;span class="nt"&gt;--no-pager&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The expected result was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Active: active (running)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This confirmed that the Nginx service was running.&lt;/p&gt;

&lt;p&gt;I also validated the Nginx configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nginx &lt;span class="nt"&gt;-t&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A successful result looked like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nginx: configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is an important habit when working with Nginx.&lt;/p&gt;

&lt;p&gt;Before restarting or reloading the service after configuration changes, it is safer to validate the configuration first.&lt;/p&gt;

&lt;p&gt;A configuration error can cause Nginx to fail to restart.&lt;/p&gt;

&lt;p&gt;If Nginx fails to restart in production, the website may become unavailable because Nginx is responsible for serving the HTTP traffic.&lt;/p&gt;

&lt;p&gt;A basic recovery process would be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nginx &lt;span class="nt"&gt;-t&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, if there is a problem:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl status nginx &lt;span class="nt"&gt;--no-pager&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;journalctl &lt;span class="nt"&gt;-u&lt;/span&gt; nginx &lt;span class="nt"&gt;--no-pager&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; 50
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These commands help identify the cause of the failure.&lt;/p&gt;

&lt;p&gt;If a recent configuration change caused the issue, the basic rollback plan would be to restore the last known-good configuration, validate it again, and restart Nginx.&lt;/p&gt;




&lt;h1&gt;
  
  
  Part 8: Transferring the Website to EC2 with SCP
&lt;/h1&gt;

&lt;p&gt;The CodeTrack files existed locally, but Nginx could only serve files that existed on the EC2 server.&lt;/p&gt;

&lt;p&gt;To transfer the files, I used SCP.&lt;/p&gt;

&lt;p&gt;SCP stands for &lt;strong&gt;Secure Copy Protocol&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It allows files to be transferred between computers over an SSH connection.&lt;/p&gt;

&lt;p&gt;The basic idea was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Local Computer
      │
      │ SCP over SSH
      ▼
AWS EC2 Instance
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The files were transferred to the server using a command similar to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;scp &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"your-key.pem"&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; CodeTrack ubuntu@&amp;lt;EC2_PUBLIC_IP&amp;gt;:~/CodeTrack
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important parts of this command are:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;The secure copy command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-i "your-key.pem"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Specifies the SSH private key used for authentication.&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Means recursive, which is required when copying a directory and its contents.&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;The local project directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ubuntu@&amp;lt;EC2_PUBLIC_IP&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The remote server username and address.&lt;/p&gt;

&lt;p&gt;The files were then transferred to the EC2 instance.&lt;/p&gt;

&lt;p&gt;This was an important distinction for me:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Having a project committed to Git does not automatically make it available on a server.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The files still need to be transferred or retrieved by the deployment system.&lt;/p&gt;

&lt;p&gt;In a manual deployment, SCP can be used.&lt;/p&gt;

&lt;p&gt;In a more advanced CI/CD workflow, a pipeline could automatically build and deploy the application after changes are pushed to a repository.&lt;/p&gt;




&lt;h1&gt;
  
  
  Part 9: Performing a Pre-Deployment Check
&lt;/h1&gt;

&lt;p&gt;Before replacing the existing content in the Nginx web root, I checked the current state of the server.&lt;/p&gt;

&lt;p&gt;The Nginx web root was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/var/www/html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I checked the current Nginx status:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl status nginx &lt;span class="nt"&gt;--no-pager&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I validated the configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nginx &lt;span class="nt"&gt;-t&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I inspected the existing web root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; /var/www/html/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And tested the current HTTP response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-I&lt;/span&gt; http://localhost
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The expected response included:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP/1.1 200 OK
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This pre-deployment check created a known baseline.&lt;/p&gt;

&lt;p&gt;Before making a change, I knew:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Nginx was running&lt;/li&gt;
&lt;li&gt;The configuration was valid&lt;/li&gt;
&lt;li&gt;The web root contained existing files&lt;/li&gt;
&lt;li&gt;The server was responding to HTTP requests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a useful operational habit.&lt;/p&gt;

&lt;p&gt;Before making a potentially destructive change, understand the current state of the system first.&lt;/p&gt;




&lt;h1&gt;
  
  
  Part 10: Deploying CodeTrack to the Nginx Web Root
&lt;/h1&gt;

&lt;p&gt;The files needed to be placed inside the directory Nginx was configured to serve.&lt;/p&gt;

&lt;p&gt;The target directory was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/var/www/html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The old content was removed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/www/html/&lt;span class="k"&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The CodeTrack files were copied into the web root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo cp&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; ~/CodeTrack/&lt;span class="k"&gt;*&lt;/span&gt; /var/www/html/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Permissions and ownership were then applied:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; www-data:www-data /var/www/html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo chmod&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; 755 /var/www/html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the deployment, I validated the Nginx configuration again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nginx &lt;span class="nt"&gt;-t&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then restarted Nginx:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, I confirmed that the expected files existed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; /var/www/html/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The directory contained:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;index.html
style.css
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, the CodeTrack files had been placed in the location Nginx was serving.&lt;/p&gt;




&lt;h1&gt;
  
  
  Part 11: Verifying the Deployment with curl
&lt;/h1&gt;

&lt;p&gt;A deployment should not be considered complete simply because the files were copied successfully.&lt;/p&gt;

&lt;p&gt;The application needs to be tested.&lt;/p&gt;

&lt;p&gt;I used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-I&lt;/span&gt; http://localhost
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The expected response was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP/1.1 200 OK
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This confirmed that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The request reached the server.&lt;/li&gt;
&lt;li&gt;Nginx was running.&lt;/li&gt;
&lt;li&gt;Nginx was able to serve the deployed content.&lt;/li&gt;
&lt;li&gt;The server returned a successful HTTP response.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is a simple but valuable deployment check.&lt;/p&gt;

&lt;p&gt;It verifies the server from inside the machine before relying only on a browser.&lt;/p&gt;

&lt;p&gt;The final step was opening the EC2 public IP in a browser and confirming that the CodeTrack application was accessible externally.&lt;/p&gt;




&lt;h1&gt;
  
  
  Understanding the Complete Deployment Flow
&lt;/h1&gt;

&lt;p&gt;The complete workflow looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌──────────────────────────┐
│ Local CodeTrack Project  │
└────────────┬─────────────┘
             │
             ▼
       git status
             │
             ▼
       git add
             │
             ▼
       git commit
             │
             ▼
      Controlled Changes
             │
             ▼
        Second Commit
             │
             ▼
          SCP Transfer
             │
             ▼
┌──────────────────────────┐
│      AWS EC2 Server      │
│         Ubuntu           │
└────────────┬─────────────┘
             │
             ▼
          Nginx
             │
             ▼
     /var/www/html
             │
             ▼
       CodeTrack Website
             │
             ▼
        HTTP 200 OK
             │
             ▼
        Browser Access
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This workflow demonstrated how multiple tools work together:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Git&lt;/td&gt;
&lt;td&gt;Track changes and maintain project history&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Git Commit&lt;/td&gt;
&lt;td&gt;Save meaningful project snapshots&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SSH&lt;/td&gt;
&lt;td&gt;Securely connect to the EC2 server&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SCP&lt;/td&gt;
&lt;td&gt;Transfer files securely&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AWS EC2&lt;/td&gt;
&lt;td&gt;Provide the virtual server&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ubuntu&lt;/td&gt;
&lt;td&gt;Operating system for the server&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nginx&lt;/td&gt;
&lt;td&gt;Serve the website&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;curl&lt;/td&gt;
&lt;td&gt;Test HTTP responses&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Browser&lt;/td&gt;
&lt;td&gt;Verify the application externally&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h1&gt;
  
  
  What I Learned
&lt;/h1&gt;

&lt;p&gt;This project helped me understand several important concepts more practically.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Git is more than just saving code
&lt;/h2&gt;

&lt;p&gt;Git provides a history of how a project changes over time.&lt;/p&gt;

&lt;p&gt;Creating separate commits for separate changes makes it easier to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Review changes&lt;/li&gt;
&lt;li&gt;Understand project history&lt;/li&gt;
&lt;li&gt;Troubleshoot issues&lt;/li&gt;
&lt;li&gt;Roll back changes&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  2. Staging provides control
&lt;/h2&gt;

&lt;p&gt;The staging area acts as a checkpoint between the working directory and the commit.&lt;/p&gt;

&lt;p&gt;The workflow is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Make Changes
     ↓
Review Changes
     ↓
Stage Selected Files
     ↓
Commit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This prevents unrelated changes from accidentally being included in the same commit.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Deployment requires verification
&lt;/h2&gt;

&lt;p&gt;Copying files to a server is not enough.&lt;/p&gt;

&lt;p&gt;A proper deployment should verify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The service is running&lt;/li&gt;
&lt;li&gt;The configuration is valid&lt;/li&gt;
&lt;li&gt;The files exist in the correct location&lt;/li&gt;
&lt;li&gt;The server returns the expected HTTP response&lt;/li&gt;
&lt;li&gt;The application is accessible from the browser&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  4. Nginx is the bridge between the server and the browser
&lt;/h2&gt;

&lt;p&gt;The browser does not directly open the &lt;code&gt;index.html&lt;/code&gt; file from my local computer.&lt;/p&gt;

&lt;p&gt;Instead, the request follows a path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser
   ↓
EC2 Public IP
   ↓
Nginx
   ↓
/var/www/html/index.html
   ↓
HTTP Response
   ↓
Browser
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Understanding this request flow makes troubleshooting much easier.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Manual deployment helps explain CI/CD
&lt;/h2&gt;

&lt;p&gt;Although this was a manual deployment, the underlying process is similar to what automated pipelines do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Code Change
    ↓
Version Control
    ↓
Build
    ↓
Transfer
    ↓
Deploy
    ↓
Restart/Reload
    ↓
Verify
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A CI/CD pipeline automates many of these steps, but understanding the manual process first makes the automation easier to understand.&lt;/p&gt;




&lt;h1&gt;
  
  
  Final Thoughts
&lt;/h1&gt;

&lt;p&gt;This CodeTrack project was more than simply putting a static website online.&lt;/p&gt;

&lt;p&gt;It helped me connect several foundational DevOps concepts into one practical workflow.&lt;/p&gt;

&lt;p&gt;I started with a local project, tracked it with Git, created meaningful commits, transferred the files securely to AWS, deployed them to an Ubuntu server, configured the web server, and verified the result using HTTP testing.&lt;/p&gt;

&lt;p&gt;The biggest lesson for me was the importance of verification.&lt;/p&gt;

&lt;p&gt;A deployment is not complete simply because a command ran successfully.&lt;/p&gt;

&lt;p&gt;You need evidence.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Nginx is active
        +
Configuration is valid
        +
Files exist in the web root
        +
curl returns HTTP 200
        +
The browser loads the application
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Together, these checks provide much stronger confidence that the deployment is actually working.&lt;/p&gt;

&lt;p&gt;This project is another step in my journey toward becoming a stronger Software Engineer with deeper skills in &lt;strong&gt;Linux, AWS, Git, Nginx, Cloud Engineering, and DevOps&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Acknowledgements
&lt;/h2&gt;

&lt;p&gt;You can follow &lt;a href="https://dmi.pravinmishra.com/s/horlartundhey.html" rel="noopener noreferrer"&gt;my graded progress&lt;/a&gt; throughout the cohort.&lt;/p&gt;

&lt;p&gt;This project is part of the &lt;strong&gt;DevOps Micro Internship with Agentic AI Cohort 3&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Technologies Used
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Git&lt;/li&gt;
&lt;li&gt;AWS EC2&lt;/li&gt;
&lt;li&gt;Ubuntu&lt;/li&gt;
&lt;li&gt;Nginx&lt;/li&gt;
&lt;li&gt;SSH&lt;/li&gt;
&lt;li&gt;SCP&lt;/li&gt;
&lt;li&gt;HTML&lt;/li&gt;
&lt;li&gt;CSS&lt;/li&gt;
&lt;li&gt;curl&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  DevOps #Linux #Git #AWS #EC2 #Nginx #CloudEngineering #SoftwareEngineering #Deployment #LearningInPublic
&lt;/h1&gt;

</description>
      <category>devops</category>
      <category>github</category>
      <category>ec2</category>
    </item>
    <item>
      <title>Week 3 at DMI — When Linux Stopped Being Theory</title>
      <dc:creator>horlartundhey</dc:creator>
      <pubDate>Mon, 20 Jul 2026 16:04:19 +0000</pubDate>
      <link>https://dev.to/horlartundhey/week-3-at-dmi-when-linux-stopped-being-theory-1cl5</link>
      <guid>https://dev.to/horlartundhey/week-3-at-dmi-when-linux-stopped-being-theory-1cl5</guid>
      <description>&lt;p&gt;Week 3 of DevOps Micro Internship was the week Linux stopped being something I read about and became something I broke, diagnosed, and fixed on a real Ubuntu VM.&lt;/p&gt;

&lt;p&gt;It started with spinning up an AWS Free Tier account and provisioning the EC2 instance I'd be operating on for the rest of the week. From there, I deployed two real applications through Nginx: a React app built with Node.js and npm, and a personal portfolio site, EpicReads. Both drills covered the same core loop — install the runtime, clone the code, build it, move the build into Nginx's web root, and configure the server block to serve it correctly. Small details mattered more than I expected: file permissions, the right root path, and a working reverse-proxy config were the difference between a blank page and a live site.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ficpu6gc0vi2z0vodc831.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ficpu6gc0vi2z0vodc831.png" alt="Linux" width="635" height="249"&gt;&lt;/a&gt;&lt;br&gt;
The most valuable part of the week was the &lt;strong&gt;Production Maintenance Drill&lt;/strong&gt;, where I deliberately broke things in order to learn how to recover them. In one simulation, two semicolons went missing from my Nginx config. &lt;code&gt;nginx -t&lt;/code&gt; caught the syntax error before I restarted anything, I restored the missing semicolons, re-validated, and restarted the service — confirming recovery with an external &lt;code&gt;curl -I&lt;/code&gt; check for a clean &lt;code&gt;200 OK&lt;/code&gt;. In a second simulation, the entire web root was emptied out, and the app started returning &lt;code&gt;500&lt;/code&gt;s even though Nginx itself stayed healthy. Because I'd backed up the deployment beforehand, recovery was just restoring the backup and re-verifying — a small preview of why atomic, versioned deployments matter in real production systems.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbq8tyiojc5l4g4949uvs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbq8tyiojc5l4g4949uvs.png" alt="Bash Scripting" width="800" height="542"&gt;&lt;/a&gt;&lt;br&gt;
Alongside that, I worked through Bash scripting fundamentals — variables, arrays, loops, conditionals, and functions — building up to a small automation script. That groundwork paid off immediately in the week's capstone: building an &lt;strong&gt;AI-assisted Linux health-check skill&lt;/strong&gt; with Claude Code. I wrote a triage script, wired it into a Claude Code skill, then simulated a real incident (nginx stopped, port 80 down, HTTP checks failing). The skill correctly gathered evidence and diagnosed the cause from the logs — but it was restricted to read-only analysis by design. Per the project's safety rules, only I, the human, could approve and run the actual fix (&lt;code&gt;sudo systemctl start nginx&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;That distinction — AI that investigates and recommends, but doesn't execute — is the same "guardrails, not autopilot" lesson from Week 2, and it's the one I keep relearning: the tooling should make me faster at making decisions, not replace the decision.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;P.S. This post is a part of DevOps Micro Internship with Agentic AI Cohort-3 by &lt;a href="https://www.linkedin.com/in/pravin-mishra-aws-trainer/" rel="noopener noreferrer"&gt;Pravin Mishra&lt;/a&gt;. You can start your DevOps journey by joining the DMI waiting list: &lt;a href="https://forms.gle/3hvrWJBDzsDeJoPs6" rel="noopener noreferrer"&gt;https://forms.gle/3hvrWJBDzsDeJoPs6&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  DMIByPravinMishra
&lt;/h1&gt;

&lt;p&gt;You can follow &lt;a href="https://dmi.pravinmishra.com/s/horlartundhey.html" rel="noopener noreferrer"&gt;my graded progress&lt;/a&gt; throughout the cohort.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Week 0 at DMI: Relearning the Internet From First Principles</title>
      <dc:creator>horlartundhey</dc:creator>
      <pubDate>Mon, 20 Jul 2026 15:59:53 +0000</pubDate>
      <link>https://dev.to/horlartundhey/week-0-at-dmi-relearning-the-internet-from-first-principles-588n</link>
      <guid>https://dev.to/horlartundhey/week-0-at-dmi-relearning-the-internet-from-first-principles-588n</guid>
      <description>&lt;p&gt;Before I touched a single cloud console, DevOps Micro Internship Cohort 3 sent me back to the internet's plumbing — and it turned out I needed the refresher more than I expected.&lt;/p&gt;

&lt;p&gt;Task one asked me to use ChatGPT as a learning assistant, prompting it to explain "What is a protocol in networking?" with a real-life example. The answer stuck with me in the simplest possible way: a protocol is just a set of rules devices agree on before they talk, the same way two people agree on a shared language before a conversation makes sense. That framing carried me through everything else that week.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftsphwunhduvcttjwhc9p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftsphwunhduvcttjwhc9p.png" alt="Task one using ChatGPT as a learning assistant" width="799" height="440"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The core exercise put me in the shoes of a friend launching an online bookstore called &lt;strong&gt;EpicReads&lt;/strong&gt;, hosted on a server in Finland, and asked me to explain to him how someone on the other side of the world would actually reach his site. Walking through packet switching, IP addressing, TCP/IP, and HTTP/HTTPS forced me to stop treating "the internet" as a black box. A request doesn't travel as one block of data — it's broken into packets, routed independently, addressed to a specific IP, and reassembled at the other end, all wrapped in TCP/IP's reliability guarantees and delivered over HTTP or HTTPS.&lt;/p&gt;

&lt;p&gt;From there, I mapped out two-tier and three-tier application architecture (frontend/database vs. frontend/backend/database), which made it obvious why EpicReads would eventually need a backend layer once it outgrew a simple storefront. Then came DNS: understanding that &lt;code&gt;epicreads.com&lt;/code&gt; is really just a friendly alias for &lt;code&gt;52.172.142.222&lt;/code&gt;, resolved through an &lt;strong&gt;A record&lt;/strong&gt;, was the moment the whole stack finally clicked together — domain name, DNS, IP, and server all pointing at the same thing from different angles.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd0oc3o2ietm2k3x35qp2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd0oc3o2ietm2k3x35qp2.png" alt="mapped out two-tier and three-tier application architecture" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The hardest part wasn't any single concept — it was holding TCP/IP, DNS, and architecture in my head at the same time as one connected flow instead of four separate facts. That's the habit I'm carrying into the rest of the internship: build the mental model first, then layer in the tools.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;P.S. This post is a part of DevOps Micro Internship with Agentic AI Cohort-3 by &lt;a href="https://www.linkedin.com/in/pravin-mishra-aws-trainer/" rel="noopener noreferrer"&gt;Pravin Mishra&lt;/a&gt;. You can start your DevOps journey by joining the DMI waiting list: &lt;a href="https://forms.gle/3hvrWJBDzsDeJoPs6" rel="noopener noreferrer"&gt;https://forms.gle/3hvrWJBDzsDeJoPs6&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  DMIByPravinMishra
&lt;/h1&gt;

&lt;p&gt;You can follow &lt;a href="https://dmi.pravinmishra.com/s/horlartundhey.html" rel="noopener noreferrer"&gt;my graded progress&lt;/a&gt; throughout the cohort.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Week 2 of my reflections from the DevOps Micro Internship: Guardrails, Not Autopilot</title>
      <dc:creator>horlartundhey</dc:creator>
      <pubDate>Fri, 10 Jul 2026 13:42:20 +0000</pubDate>
      <link>https://dev.to/horlartundhey/week-2-of-my-reflections-from-the-devops-micro-internship-guardrails-not-autopilot-1nje</link>
      <guid>https://dev.to/horlartundhey/week-2-of-my-reflections-from-the-devops-micro-internship-guardrails-not-autopilot-1nje</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd0z4n2m3te06nhlzfr4v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd0z4n2m3te06nhlzfr4v.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Learning to trust an AI coding agent with real infrastructure, one permission at a time&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Going into Week 2 of the DMI DevOps Micro Internship, I assumed I already understood what an AI coding assistant does: you type a prompt, it writes some code, you review it. What I actually learned this week is that Claude Code isn't a text generator you talk to; it's an &lt;strong&gt;agent&lt;/strong&gt; operating inside a system of guardrails, and understanding those guardrails turned out to be the real curriculum.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Biggest technical insight I got this week
&lt;/h2&gt;

&lt;p&gt;The concept that reframed everything for me was the &lt;strong&gt;agentic loop&lt;/strong&gt;: read context, plan, call a tool, observe the result, decide the next step, repeat until done. That loop is powerful, but it's only &lt;em&gt;safe&lt;/em&gt; because of what wraps around it.&lt;/p&gt;

&lt;p&gt;In the project I worked on, &lt;code&gt;CLAUDE.md&lt;/code&gt; tells the agent upfront that the site is pure HTML/CSS with no JavaScript and no build step, so it doesn't waste a turn inventing a bundler or npm scripts that were never there. Layered on top of that are &lt;strong&gt;skills&lt;/strong&gt;: reusable slash commands like &lt;code&gt;/scaffold-terraform&lt;/code&gt; and &lt;code&gt;/tf-plan&lt;/code&gt; that turn a long, easy-to-forget prompt into a single, repeatable action, each with its own declared &lt;code&gt;allowed-tools&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That's where tool restrictions stopped being an abstract "best practice" and became something I could point to. The &lt;code&gt;tf-plan&lt;/code&gt; skill is scoped to &lt;code&gt;Bash, Read, Grep&lt;/code&gt;, deliberately &lt;strong&gt;no &lt;code&gt;Write&lt;/code&gt;&lt;/strong&gt;, because a plan should only &lt;em&gt;observe&lt;/em&gt; infrastructure, never change it. &lt;code&gt;settings.json&lt;/code&gt; reinforces the same philosophy at the project level: it explicitly allows read-only Terraform and AWS calls (&lt;code&gt;terraform plan&lt;/code&gt;, &lt;code&gt;aws s3 ls&lt;/code&gt;, &lt;code&gt;aws sts get-caller-identity&lt;/code&gt;) and explicitly denies destructive ones (&lt;code&gt;rm -rf *&lt;/code&gt;, &lt;code&gt;aws iam *&lt;/code&gt;). Permissions, I realized, aren't bureaucracy bolted onto AI, they're the actual mechanism that makes it safe to hand an agent something as consequential as real cloud infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Biggest insight I got about myself this week
&lt;/h2&gt;

&lt;p&gt;I learn better while doing a video guide that helps people, which surprisingly makes the learning and doing more interesting.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. My biggest weakness or loop I noticed
&lt;/h2&gt;

&lt;p&gt;The loop I kept catching myself in was trusting documentation over the actual state of the repo. This project's own &lt;code&gt;CLAUDE.md&lt;/code&gt; includes a candid note that earlier commits referenced &lt;code&gt;.claude/skills/&lt;/code&gt;, a GitHub Actions workflow, and a &lt;code&gt;terraform/&lt;/code&gt; directory, but those were later deleted from the working tree, meaning the "documented" pipeline was aspirational, not real. More than once I found myself reasoning about a file as if it existed because a description said it should, instead of running &lt;code&gt;git status&lt;/code&gt; or listing the directory first. It's a small habit gap, but in DevOps, where "what's documented" and "what's actually deployed" can quietly diverge, it's exactly the kind of gap that causes real incidents.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. One system I will implement this week
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Before running any skill or command that touches infrastructure, like &lt;code&gt;/scaffold-terraform&lt;/code&gt; or &lt;code&gt;/tf-plan&lt;/code&gt;, I will first run &lt;code&gt;git status&lt;/code&gt; and check the relevant directory to confirm what actually exists on disk, rather than trusting &lt;code&gt;CLAUDE.md&lt;/code&gt; or a skill description alone. I'll do this at the start of &lt;em&gt;every&lt;/em&gt; task, not just once per session, so I stop building on assumptions about files that were deleted, renamed, or never committed in the first place.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  5. What I learned about Agentic AI and DevOps
&lt;/h2&gt;

&lt;p&gt;Before this internship, "AI" meant a chat window that answered questions. This week reframed it as an &lt;strong&gt;operator with guardrails&lt;/strong&gt;. An agentic AI can follow a structured workflow end-to-end, scaffold Terraform, run a plan, hand the output to a &lt;code&gt;security-auditor&lt;/code&gt; or &lt;code&gt;cost-optimizer&lt;/code&gt; subagent, and only then wait for a human to approve &lt;code&gt;tf-apply&lt;/code&gt;, instead of producing a single disconnected answer.&lt;/p&gt;

&lt;p&gt;What stood out most is that the safety doesn't come from the model being well-behaved; it comes from &lt;em&gt;design&lt;/em&gt;. &lt;code&gt;tf-plan&lt;/code&gt; physically cannot write files. Destructive commands are denied at the settings level, not just discouraged in a prompt. Hooks fire before and after every shell command to check and log what's happening, whether the model "intended" to be careful or not. Reusable skills cut the repetitive typing, which is nice, but the real lesson is that DevOps engineers now have to design two things at once: the automation, and the control layer that keeps it honest. Human review before &lt;code&gt;apply&lt;/code&gt; isn't a leftover step from the pre-AI era. It's still the last line of defense.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. My Week 2 highlight
&lt;/h2&gt;

&lt;p&gt;Highlight of this week will be understanding and actually creating &lt;code&gt;skills&lt;/code&gt; and &lt;code&gt;sub-agents&lt;/code&gt;, using &lt;code&gt;claude.md&lt;/code&gt; as a guide to make sure my agent has the correct context to work with. &lt;/p&gt;

&lt;p&gt;I had a bit of an issue with Terraform, but this made me understand the flow better. &lt;/p&gt;

&lt;p&gt;*You can view my graded progress for the DevOps Micro Internship &lt;a href="https://dmi.pravinmishra.com/s/horlartundhey.html" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This is Week 2 of my reflections from the DevOps Micro Internship. If you're on a similar path, I'd love to hear what clicked for you this week too.*&lt;/p&gt;

</description>
      <category>devops</category>
      <category>claude</category>
    </item>
    <item>
      <title>Introduction to the Document Object Model(DOM): The Foundation of Frontend Development</title>
      <dc:creator>horlartundhey</dc:creator>
      <pubDate>Thu, 12 Jun 2025 13:32:11 +0000</pubDate>
      <link>https://dev.to/horlartundhey/introduction-to-the-document-object-modeldom-the-foundation-of-frontend-development-3fim</link>
      <guid>https://dev.to/horlartundhey/introduction-to-the-document-object-modeldom-the-foundation-of-frontend-development-3fim</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fds2ns7asvgke8dnjw83g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fds2ns7asvgke8dnjw83g.png" alt="Image description" width="800" height="1200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What's this article about?
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Introduction&lt;/li&gt;
&lt;li&gt;What is the DOM?&lt;/li&gt;
&lt;li&gt;Why is the DOM Important in Frontend Development?&lt;/li&gt;
&lt;li&gt;Understanding the DOM Tree Structure&lt;/li&gt;
&lt;li&gt;Interacting with the DOM using JavaScript&lt;/li&gt;
&lt;li&gt;Events and the DOM&lt;/li&gt;
&lt;li&gt;Common Mistakes Beginners Make with the DOM&lt;/li&gt;
&lt;li&gt;Tools and Browser DevTools for DOM Exploration&lt;/li&gt;
&lt;li&gt;Best Practices for DOM Manipulation&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  1. Introduction
&lt;/h2&gt;

&lt;p&gt;When you open a website in your browser, you see buttons, text, images, and various interactive elements. But have you ever wondered how your browser understands and organizes all these parts of a webpage? The answer lies in something called the Document Object Model (DOM).&lt;/p&gt;

&lt;p&gt;The DOM serves as the foundation of frontend development. It’s a programming interface that allows developers to structure, access, and modify the content and appearance of a webpage dynamically. Without the DOM, modern interactive websites as we know them would not exist.&lt;/p&gt;

&lt;p&gt;For beginners starting in frontend development, understanding the DOM is one of the most crucial first steps. Once you grasp how the DOM works, you’ll be able to:&lt;/p&gt;

&lt;p&gt;Dynamically update content on a webpage.&lt;/p&gt;

&lt;p&gt;Handle user interactions like clicks, form submissions, and keyboard events.&lt;/p&gt;

&lt;p&gt;Build more interactive and responsive user experiences.&lt;/p&gt;

&lt;p&gt;In this article, we’ll break down the DOM in a beginner-friendly way, explore its structure, show you how to interact with it using JavaScript, and highlight best practices along the way.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. What is the DOM?
&lt;/h2&gt;

&lt;p&gt;The Document Object Model (DOM) is a structured representation of an HTML or XML document. Think of it as a live map of your webpage that your browser creates when it loads a page.&lt;/p&gt;

&lt;p&gt;When you write HTML code like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;My First Webpage&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Hello, World!&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;This is my first webpage.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your browser takes this code and builds a tree-like structure that represents every element, attribute, and piece of text. This structure is the DOM.&lt;/p&gt;

&lt;p&gt;At its core, the DOM:&lt;/p&gt;

&lt;p&gt;Represents the document as a tree of objects.&lt;/p&gt;

&lt;p&gt;Allows programming languages (like JavaScript) to interact with and manipulate the document’s content, structure, and style.&lt;/p&gt;

&lt;p&gt;Updates automatically when changes are made, allowing for dynamic content.&lt;/p&gt;

&lt;p&gt;In simple terms: HTML is the source, the DOM is the live version your browser uses.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Characteristics of the DOM:
&lt;/h3&gt;

&lt;p&gt;Object-Oriented: Everything in the DOM is represented as an object (elements, attributes, text).&lt;/p&gt;

&lt;p&gt;Hierarchical: Elements are nested inside one another, forming a tree.&lt;/p&gt;

&lt;p&gt;Dynamic: The DOM can be changed in real-time using JavaScript.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Why is the DOM Important in Frontend Development?
&lt;/h2&gt;

&lt;p&gt;The DOM is at the heart of almost everything you do as a frontend developer. It bridges the gap between your code and what the user sees and interacts with on the screen. Let’s explore why it's so important:&lt;/p&gt;

&lt;h3&gt;
  
  
  3.1. Dynamic User Interfaces
&lt;/h3&gt;

&lt;p&gt;Without the DOM, webpages would be static, once loaded, they would never change. But thanks to the DOM, developers can:&lt;/p&gt;

&lt;p&gt;Add or remove elements.&lt;/p&gt;

&lt;p&gt;Update content without reloading the page.&lt;/p&gt;

&lt;p&gt;Show or hide parts of the page based on user actions.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;greeting&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;innerText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Welcome back!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above code changes the text content of an element dynamically.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.2. User Interaction Handling
&lt;/h3&gt;

&lt;p&gt;When users interact with a webpage (clicking buttons, submitting forms, hovering over elements), the DOM allows developers to capture these events and respond accordingly.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Show a message when a button is clicked.&lt;/li&gt;
&lt;li&gt;Validate a form before submission.&lt;/li&gt;
&lt;li&gt;Animate elements on user actions.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3.3. Powering Modern Web Frameworks
&lt;/h3&gt;

&lt;p&gt;Modern frontend frameworks like React, Angular, and Vue work heavily with the DOM (or even virtual DOMs) to create smooth, efficient updates to the user interface.&lt;/p&gt;

&lt;p&gt;Understanding the DOM is essential even if you plan to work with these frameworks, because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;They build upon DOM principles.&lt;/li&gt;
&lt;li&gt;You’ll often need to work directly with the DOM for custom behaviors.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3.4. Debugging and Browser DevTools
&lt;/h3&gt;

&lt;p&gt;When debugging your frontend code, most of your work involves inspecting and manipulating the DOM via browser developer tools. A solid understanding of the DOM helps you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Diagnose layout problems.&lt;/li&gt;
&lt;li&gt;Understand how CSS applies to elements.&lt;/li&gt;
&lt;li&gt;Verify event bindings.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. Understanding the DOM Tree Structure
&lt;/h2&gt;

&lt;p&gt;The DOM is often visualized as a tree, where every part of your HTML document becomes a node in the tree. Understanding this tree structure makes it easier to navigate, select, and manipulate elements using JavaScript.&lt;/p&gt;

&lt;p&gt;Let’s break it down:&lt;/p&gt;

&lt;h3&gt;
  
  
  4.1. The Tree Analogy
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Root Node: The very top of the tree is the document object. Everything starts here.&lt;/li&gt;
&lt;li&gt;Element Nodes: Each HTML tag (like , , &lt;h1&gt;, &lt;/h1&gt;
&lt;p&gt;) becomes an element node.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;Text Nodes: The actual text inside elements becomes text nodes.&lt;/li&gt;
&lt;li&gt;Attribute Nodes: Attributes (like class, id, and src) are nodes attached to their respective elements.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4.2. A Simple Example
&lt;/h3&gt;

&lt;p&gt;Consider this HTML code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;title&amp;gt;DOM Example&amp;lt;/title&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;h1&amp;gt;Hello World&amp;lt;/h1&amp;gt;
    &amp;lt;p&amp;gt;This is a paragraph.&amp;lt;/p&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;

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

&lt;/div&gt;



&lt;p&gt;The DOM tree would look something like this:&lt;br&gt;
Document&lt;br&gt;
└── html&lt;br&gt;
    ├── head&lt;br&gt;
    │   └── title&lt;br&gt;
    │       └── "DOM Example"&lt;br&gt;
    └── body&lt;br&gt;
        ├── h1&lt;br&gt;
        │   └── "Hello World"&lt;br&gt;
        └── p&lt;br&gt;
            └── "This is a paragraph."&lt;/p&gt;
&lt;h3&gt;
  
  
  4.3. Why is the Tree Important?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You can move up and down the tree using JavaScript.&lt;/li&gt;
&lt;li&gt;You can select parent, child, or sibling elements.&lt;/li&gt;
&lt;li&gt;You can add or remove branches (nodes) dynamically.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  4.4. The Parent-Child Relationship
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; is a child of &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; are children of &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The text "Hello World" is a child of &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  4.5. Visualizing the DOM
&lt;/h3&gt;

&lt;p&gt;Visualizing the DOM tree is extremely helpful for beginners because it helps you understand how elements are connected.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdhmhl9c8a97lb0lmhusr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdhmhl9c8a97lb0lmhusr.png" alt="Image description" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  5. Interacting with the DOM using JavaScript
&lt;/h2&gt;

&lt;p&gt;Now that you understand what the DOM is and how it’s structured, let’s dive into how you can interact with it using JavaScript. This is where the DOM truly becomes powerful, allowing you to make your webpage dynamic and interactive.&lt;/p&gt;
&lt;h3&gt;
  
  
  5.1 Accessing Elements
&lt;/h3&gt;

&lt;p&gt;To manipulate an element, you first need to select it. JavaScript provides several ways to do this:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;getElementById()&lt;/code&gt;&lt;br&gt;
Selects an element by its ID.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;greeting&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Hello&lt;/span&gt;&lt;span class="o"&gt;!&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/p&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;greeting&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;greeting&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;greeting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerText&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Outputs: Hello!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;getElementsByClassName()&lt;/code&gt;&lt;br&gt;
Selects elements by class name (returns a collection).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;message&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;First&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/p&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;message&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Second&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/p&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementsByClassName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;message&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;innerText&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Outputs: First message&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;querySelector()&lt;/code&gt;&lt;br&gt;
Selects the first element that matches a CSS selector.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;heading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;h1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;querySelectorAll()&lt;/code&gt;&lt;br&gt;
Selects all elements that match a CSS selector.&lt;br&gt;
&lt;code&gt;let paragraphs = document.querySelectorAll("p");&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  5.2 Modifying Elements
&lt;/h3&gt;

&lt;p&gt;Once you’ve selected an element, you can modify its content, style, and attributes.&lt;/p&gt;

&lt;p&gt;Change Text Content&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;greeting&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;innerText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Welcome!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Change HTML Content&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;greeting&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;strong&amp;gt;Welcome!&amp;lt;/strong&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Change Style&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;greeting&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;blue&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5.3 Adding and Removing Elements
&lt;/h3&gt;

&lt;p&gt;You can also create, append, or remove elements dynamically.&lt;/p&gt;

&lt;p&gt;Create and Append&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;newParagraph&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;p&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;newParagraph&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;This is a new paragraph.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newParagraph&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remove Element&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;greeting&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  6. Events and the DOM
&lt;/h2&gt;

&lt;p&gt;While manipulating the DOM allows you to change the webpage structure and content, events are what make your webpage interactive. An event occurs when the user or the browser does something, like clicking a button, moving the mouse, submitting a form, or pressing a key.&lt;/p&gt;

&lt;p&gt;The DOM allows you to listen for these events and execute JavaScript code in response.&lt;/p&gt;

&lt;h3&gt;
  
  
  6.1 Common DOM Events
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Event&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;click&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;When an element is clicked&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mouseover&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;When the mouse hovers over an element&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mouseout&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;When the mouse leaves an element&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;keydown&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;When a keyboard key is pressed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;submit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;When a form is submitted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;load&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;When the page has finished loading&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  6.2 Adding Event Listeners
&lt;/h3&gt;

&lt;p&gt;The most common way to handle events is by using the addEventListener() method.&lt;/p&gt;

&lt;p&gt;Example: Button Click&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"myButton"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Click Me!&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"result"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;myButton&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;result&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Button was clicked!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the user clicks the button, the text inside the &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; tag updates dynamically.&lt;/p&gt;

&lt;h2&gt;
  
  
  6.3 Why Use Event Listeners?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Separation of Concerns: Keeps HTML and JavaScript code separate.&lt;/li&gt;
&lt;li&gt;Multiple Handlers: You can attach multiple listeners to the same element.&lt;/li&gt;
&lt;li&gt;Flexibility: Allows better control over complex user interactions.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  6.4 Other Example: Form Submission
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"myForm"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"nameInput"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Submit&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"displayName"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;myForm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;display&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;displayName&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;submit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Prevents page reload&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;nameInput&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;display&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`Hello, &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;!`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpwgm5yv1hwogsrsxmhod.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpwgm5yv1hwogsrsxmhod.png" alt="Image description" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  7.Common Mistakes Beginners Make with the DOM
&lt;/h2&gt;

&lt;p&gt;As you start working with the DOM, it’s natural to run into a few pitfalls. Let’s go over some of the most common mistakes and how to avoid them.&lt;/p&gt;

&lt;h3&gt;
  
  
  7.1 Trying to Access DOM Elements Before the Page Loads
&lt;/h3&gt;

&lt;p&gt;Problem:&lt;br&gt;
If you try to select or modify DOM elements before they exist in the browser, your code may fail.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;heading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;heading&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;heading&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;If&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="nx"&gt;runs&lt;/span&gt; &lt;span class="nx"&gt;before&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;DOM&lt;/span&gt; &lt;span class="nx"&gt;has&lt;/span&gt; &lt;span class="nx"&gt;fully&lt;/span&gt; &lt;span class="nx"&gt;loaded&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;heading&lt;/span&gt; &lt;span class="nx"&gt;will&lt;/span&gt; &lt;span class="nx"&gt;be&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;causing&lt;/span&gt; &lt;span class="nx"&gt;an&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Solution:&lt;br&gt;
Use the DOMContentLoaded event to ensure your code runs after the DOM is ready.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DOMContentLoaded&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;heading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;heading&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;heading&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  7.2 Overusing innerHTML
&lt;/h3&gt;

&lt;p&gt;While innerHTML is powerful, it’s easy to misuse it and accidentally introduce bugs or security vulnerabilities like Cross-Site Scripting (XSS).&lt;/p&gt;

&lt;p&gt;Better practice: Use textContent or DOM methods to modify elements.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Safe Text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  7.3 Forgetting to Remove Event Listeners
&lt;/h3&gt;

&lt;p&gt;When dynamically adding and removing elements, forgetting to remove event listeners can cause memory leaks and unexpected behavior.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;myFunction&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  7.4 Mixing Inline JavaScript with DOM Manipulation
&lt;/h3&gt;

&lt;p&gt;While it’s possible to use inline onclick attributes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;onclick=&lt;/span&gt;&lt;span class="s"&gt;"alert('Hello!')"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Click Me&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Best practice:&lt;br&gt;
Separate your JavaScript code from HTML by using addEventListener(). This makes your code more maintainable and scalable.&lt;/p&gt;
&lt;h3&gt;
  
  
  7.5 Not Using Efficient Selectors
&lt;/h3&gt;

&lt;p&gt;Using getElementById() is faster and more specific.&lt;/p&gt;

&lt;p&gt;Avoid using overly broad selectors that may select multiple unintended elements.&lt;/p&gt;
&lt;h2&gt;
  
  
  8. Tools and Browser DevTools for DOM Exploration
&lt;/h2&gt;

&lt;p&gt;One of the best ways to learn and work with the DOM is by using the powerful tools built into modern web browsers. These tools allow you to inspect, edit, and experiment with the DOM in real-time.&lt;/p&gt;
&lt;h3&gt;
  
  
  8.1 The Browser Developer Tools (DevTools)
&lt;/h3&gt;

&lt;p&gt;Almost every modern browser (Chrome, Firefox, Edge, Safari) includes DevTools. You can usually open them by:&lt;/p&gt;

&lt;p&gt;Right-click on any element on the page and select Inspect.&lt;/p&gt;

&lt;p&gt;Or press F12 or Ctrl+Shift+I (Windows/Linux) or Cmd+Option+I (Mac).&lt;/p&gt;
&lt;h3&gt;
  
  
  8.1.1 Elements Panel
&lt;/h3&gt;

&lt;p&gt;Displays the live DOM tree of the current webpage.&lt;/p&gt;

&lt;p&gt;You can expand and collapse elements to see the structure.&lt;/p&gt;

&lt;p&gt;You can edit HTML directly to see instant changes.&lt;/p&gt;
&lt;h3&gt;
  
  
  8.1.2 Console Panel
&lt;/h3&gt;

&lt;p&gt;Allows you to run JavaScript directly in the context of the webpage.&lt;/p&gt;

&lt;p&gt;Great for testing DOM manipulations quickly.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;h1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;innerText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Changed via Console!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  8.1.3 Other Useful Panels
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Network: See how resources load.&lt;/li&gt;
&lt;li&gt;Sources: Debug JavaScript.&lt;/li&gt;
&lt;li&gt;Performance: Analyze page speed.&lt;/li&gt;
&lt;li&gt;Accessibility: Review accessibility issues.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  8.2 Online Playground Tools
&lt;/h3&gt;

&lt;p&gt;Many online tools allow you to experiment with the DOM:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CodePen (&lt;a href="https://codepen.io" rel="noopener noreferrer"&gt;https://codepen.io&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;JSFiddle (&lt;a href="https://jsfiddle.net" rel="noopener noreferrer"&gt;https://jsfiddle.net&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;JSBin (&lt;a href="https://jsbin.com" rel="noopener noreferrer"&gt;https://jsbin.com&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are great for practicing DOM manipulation without having to set up files on your computer.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Best Practices for DOM Manipulation
&lt;/h2&gt;

&lt;p&gt;As you grow more comfortable working with the DOM, following best practices will help you write more efficient, maintainable, and secure code. Let’s cover some important tips:&lt;/p&gt;

&lt;h3&gt;
  
  
  9.1 Access Elements Efficiently
&lt;/h3&gt;

&lt;p&gt;Use &lt;code&gt;getElementById()&lt;/code&gt; when possible — it’s the fastest.&lt;/p&gt;

&lt;p&gt;Minimize repeated DOM queries — store elements in variables if used multiple times.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;submitBtn&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// reuse `button` instead of querying again&lt;/span&gt;
&lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handleSubmit&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  9.2 Avoid Unnecessary Reflows and Repaints
&lt;/h3&gt;

&lt;p&gt;Manipulating the DOM excessively or inefficiently can slow down your page. If you're making multiple changes, try:&lt;/p&gt;

&lt;p&gt;Modifying elements outside of the visible document (using DocumentFragment).&lt;/p&gt;

&lt;p&gt;Minimizing layout calculations in loops.&lt;/p&gt;

&lt;h3&gt;
  
  
  9.3 Sanitize User Input
&lt;/h3&gt;

&lt;p&gt;Never directly insert user input into the DOM using innerHTML. This can lead to security issues like Cross-Site Scripting (XSS).&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;userInput&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  9.4 Use Event Delegation
&lt;/h3&gt;

&lt;p&gt;Instead of attaching multiple event listeners to many child elements, attach one listener to a parent element.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;list&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tagName&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;LI&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerText&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach is more efficient, especially for dynamic content.&lt;/p&gt;

&lt;h3&gt;
  
  
  9.5 Separate Concerns
&lt;/h3&gt;

&lt;p&gt;Keep your HTML, CSS, and JavaScript separate.&lt;/p&gt;

&lt;p&gt;Avoid mixing inline JavaScript (onclick="...") with your HTML.&lt;/p&gt;

&lt;p&gt;This leads to cleaner, more maintainable code.&lt;/p&gt;

&lt;h3&gt;
  
  
  9.6 Use DOMContentLoaded
&lt;/h3&gt;

&lt;p&gt;Make sure the DOM is fully loaded before manipulating it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DOMContentLoaded&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Safe DOM manipulation here&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  9.7 Clean Up Event Listeners
&lt;/h3&gt;

&lt;p&gt;When removing elements dynamically, ensure you also remove any event listeners to prevent memory leaks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;myFunction&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  10. Conclusion
&lt;/h2&gt;

&lt;p&gt;The Document Object Model (DOM) is truly the foundation of frontend development. Every time you build a webpage, you're essentially creating a DOM structure that your browser understands and renders.&lt;/p&gt;

&lt;p&gt;For beginners, mastering the DOM unlocks the ability to:&lt;/p&gt;

&lt;p&gt;✅ Create dynamic, interactive webpages&lt;br&gt;
✅ Respond to user actions with JavaScript&lt;br&gt;
✅ Modify page content on the fly&lt;br&gt;
✅ Build confidence for learning modern frontend frameworks like React, Vue, or Angular&lt;/p&gt;

&lt;p&gt;Key Takeaways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The DOM represents your HTML as a live tree structure.&lt;/li&gt;
&lt;li&gt;JavaScript allows you to interact with and manipulate the DOM.&lt;/li&gt;
&lt;li&gt;Events are key to making webpages interactive.&lt;/li&gt;
&lt;li&gt;Browser DevTools are your best friend for exploring and debugging the DOM.&lt;/li&gt;
&lt;li&gt;Always follow best practices for efficient, safe, and maintainable code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 Next Steps for You as a Beginner:&lt;/p&gt;

&lt;p&gt;Practice manipulating the DOM using simple projects (to-do list, calculator, form validation).&lt;/p&gt;

&lt;p&gt;Experiment using DevTools on existing websites.&lt;/p&gt;

&lt;p&gt;Gradually move on to understanding Virtual DOM (used by modern frameworks).&lt;/p&gt;

&lt;p&gt;💡 Always remember:&lt;/p&gt;

&lt;p&gt;If you master the DOM, you're already halfway to becoming a great frontend developer!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>frontend</category>
      <category>programming</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
