<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="/feed.xml" rel="self" type="application/atom+xml" /><link href="/" rel="alternate" type="text/html" /><updated>2026-03-14T19:07:47+00:00</updated><id>/feed.xml</id><title type="html">Collection of Coding Learnings</title><subtitle></subtitle><entry><title type="html">Moving from Nextjs to Qwik</title><link href="/posts/nextjs-vs-qwik" rel="alternate" type="text/html" title="Moving from Nextjs to Qwik" /><published>2026-01-12T15:00:00+00:00</published><updated>2026-01-12T15:00:00+00:00</updated><id>/posts/nextjs-vs-qwik</id><content type="html" xml:base="/posts/nextjs-vs-qwik"><![CDATA[<p><img src="/assets/2026-01-12-nextjs-vs-qwik/banner.png" alt="Moving from Nextjs to Qwik" /></p>

<p>Whenever I’m working on a web application, Next.js is my go-to framework. I like it and I’m used to it, but I’ve been hearing a lot about Qwik and decided to give it a shot.</p>

<p>In my previous post, <a href="https://www.garciadiazjaime.com/posts/artic-gemini-quiz">Exploring the Art Institute API and Gemini</a>, I built the app using Next.js. Since it’s a new project, I thought it would be a good exercise to redo it using Qwik instead.</p>

<p>You can still access the previous version, and to make it a bit more interesting, I’m using Lighthouse to compare the two versions:</p>

<ul>
  <li><a href="https://artic.mintitmedia.com/">With Qwik</a></li>
  <li><a href="https://artic-old.mintitmedia.com/">With Next.js</a></li>
</ul>

<blockquote>
  <p>Both codebases render a static site, which is generated daily using a cron job and a deploy hook.</p>
</blockquote>

<h2 id="network">Network</h2>

<p>Let’s start by looking at the Network tab:</p>

<blockquote>
  <p>Left is Next.js and right is Qwik</p>
</blockquote>

<p><img src="/assets/2026-01-12-nextjs-vs-qwik/network-tab.png" alt="Network tab" /></p>

<p>This is very interesting because, in summary, Qwik sends fewer bytes to the browser, and fewer bytes translate into faster rendering as well, even though both apps show basically the same UI.</p>

<table>
  <thead>
    <tr>
      <th> </th>
      <th>Next.js</th>
      <th>Qwik</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Transferred</td>
      <td>285 kB</td>
      <td>118 kB</td>
    </tr>
    <tr>
      <td>Resources</td>
      <td>649 kB</td>
      <td>172 kB</td>
    </tr>
    <tr>
      <td>DomContentLoaded</td>
      <td>92 ms</td>
      <td>96 ms</td>
    </tr>
    <tr>
      <td>Load</td>
      <td>368 ms</td>
      <td>124 ms</td>
    </tr>
  </tbody>
</table>

<ul>
  <li><strong>kB transferred</strong> is the data sent from the server (CDN) to the browser, which is zipped.</li>
  <li><strong>kB resources</strong> is the total size once unzipped.</li>
</ul>

<p>As we can see, the Qwik project sends less data. <strong>This is a big win for Qwik.</strong></p>

<ul>
  <li><strong>DomContentLoaded</strong> is the event fired when the HTML is parsed and the DOM is built. At this point, JavaScript is executed and images and stylesheets start loading.</li>
  <li><strong>Load</strong> is the event fired when everything is ready. The browser doesn’t need to download anything else (images, styles, etc.), and the page is fully rendered.</li>
</ul>

<p>Notice that while <strong>DomContentLoaded</strong> happens at almost the same time, <strong>Load</strong> happens much faster for Qwik. This is just how the framework works. In the case of Next.js, since the app is larger, it takes a bit longer to completely render the page.</p>

<blockquote>
  <p>Note: I checked the “Disable cache” checkbox to always download files from the origin (CDN).</p>
</blockquote>

<p>Also keep in mind that these values change on every load. Things like browser cache, internet connectivity, and server response times are different every time. That’s why, when testing these values, a load test is a better approach. Loading the page N times gives you a better average. A single run can be influenced by chance. Still, these numbers are a great benchmark for any website.</p>

<h2 id="lighthouse">Lighthouse</h2>

<h3 id="performance">Performance</h3>

<p><img src="/assets/2026-01-12-nextjs-vs-qwik/lighthouse-performance-results.png" alt="Lighthouse performance results" /></p>

<p>Right from the start, Qwik gets a perfect score of 100 across the board, while Next.js gets a slightly lower score. This is interesting because it’s not about the application code, but about the framework itself.</p>

<p>Sure, a Next.js application can also reach 100, but that usually requires extra work, which isn’t necessary when using Qwik.</p>

<h3 id="metrics">Metrics</h3>

<p><img src="/assets/2026-01-12-nextjs-vs-qwik/lighthouse-metrics-results.png" alt="Lighthouse metrics results" /></p>

<p>Based on the results from the <strong>Network tab</strong>, it makes sense that these values are lower for Qwik. In simple terms: same UI, fewer bytes, faster rendering.</p>

<h3 id="diagnostics">Diagnostics</h3>

<p><img src="/assets/2026-01-12-nextjs-vs-qwik/lighthouse-diagnostics-results.png" alt="Lighthouse diagnostics results" /></p>

<p>Here we can see Lighthouse pointing out that Next.js has some unused JavaScript. This doesn’t happen with Qwik, confirming that Qwik generates a leaner build.</p>

<h2 id="coding">Coding</h2>

<p>This is my first time using Qwik, and I didn’t want to use AI agents to convert the code. I spent some time reading the <a href="https://qwik.dev/docs/">Qwik documentation</a>, and that was enough to build this simple app. Here are some findings:</p>

<h3 id="components">Components</h3>

<p>In Next.js, a function is a component, with no extra syntax needed:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nx">Home</span><span class="p">()</span> <span class="p">{</span>
  <span class="k">return</span> <span class="o">&lt;</span><span class="nx">div</span><span class="o">&gt;</span><span class="nx">Home</span> <span class="nx">Page</span> <span class="nx">Component</span><span class="o">&lt;</span><span class="sr">/div&gt;</span><span class="err">;
</span><span class="p">}</span>
</code></pre></div></div>

<p>With Qwik, you need to use the <code class="language-plaintext highlighter-rouge">component$</code> function:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="k">default</span> <span class="nx">component$</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="k">return</span> <span class="o">&lt;</span><span class="nx">div</span><span class="o">&gt;</span><span class="nx">Home</span> <span class="nx">Page</span> <span class="nx">Component</span><span class="o">&lt;</span><span class="sr">/div&gt;</span><span class="err">;
</span><span class="p">});</span>
</code></pre></div></div>

<p>Not a big deal. The syntax changes a bit, but it’s easy enough to follow.</p>

<h3 id="state-variables-and-clicks">State variables and clicks</h3>

<p>In Next.js, we use <code class="language-plaintext highlighter-rouge">useState</code> along with <code class="language-plaintext highlighter-rouge">"use client";</code> because state lives on the client. Similarly, <code class="language-plaintext highlighter-rouge">onClick</code> handlers only run on the client.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="dl">"</span><span class="s2">use client</span><span class="dl">"</span><span class="p">;</span>

<span class="k">import</span> <span class="p">{</span> <span class="nx">useState</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">react</span><span class="dl">"</span><span class="p">;</span>

<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nx">Home</span><span class="p">()</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="p">[</span><span class="nx">greeting</span><span class="p">,</span> <span class="nx">setGreeting</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="p">(</span><span class="dl">"</span><span class="s2">hello world</span><span class="dl">"</span><span class="p">);</span>

  <span class="k">return</span> <span class="o">&lt;</span><span class="nx">div</span> <span class="nx">onClick</span><span class="o">=</span><span class="p">{()</span> <span class="o">=&gt;</span> <span class="nx">setGreeting</span><span class="p">(</span><span class="dl">"</span><span class="s2">hello from click</span><span class="dl">"</span><span class="p">)}</span><span class="o">&gt;</span><span class="p">{</span><span class="nx">greeting</span><span class="p">}</span><span class="o">&lt;</span><span class="sr">/div&gt;</span><span class="err">;
</span><span class="p">}</span>
</code></pre></div></div>

<p>Qwik uses <code class="language-plaintext highlighter-rouge">useSignal</code> for “signal variables” and <code class="language-plaintext highlighter-rouge">onClick$</code> for click handlers:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">component$</span><span class="p">,</span> <span class="nx">useSignal</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@builder.io/qwik</span><span class="dl">"</span><span class="p">;</span>

<span class="k">export</span> <span class="k">default</span> <span class="nx">component$</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">greetingSignal</span> <span class="o">=</span> <span class="nx">useSignal</span><span class="p">(</span><span class="dl">"</span><span class="s2">hello world</span><span class="dl">"</span><span class="p">);</span>

  <span class="k">return</span> <span class="p">(</span>
    <span class="o">&lt;</span><span class="nx">div</span> <span class="nx">onClick$</span><span class="o">=</span><span class="p">{()</span> <span class="o">=&gt;</span> <span class="p">(</span><span class="nx">greetingSignal</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">hello from click</span><span class="dl">"</span><span class="p">)}</span><span class="o">&gt;</span>
      <span class="p">{</span><span class="nx">greetingSignal</span><span class="p">}</span>
    <span class="o">&lt;</span><span class="sr">/div</span><span class="err">&gt;
</span>  <span class="p">);</span>
<span class="p">});</span>
</code></pre></div></div>

<p>Assigning a new value to <code class="language-plaintext highlighter-rouge">greetingSignal.value = ...</code> is equivalent to calling <code class="language-plaintext highlighter-rouge">setGreeting(...)</code>, and it triggers a re-render.</p>

<h3 id="fetching-data">Fetching data</h3>

<p>In both cases, data is fetched on the server. Remember, the page is generated daily, meaning the server generates the HTML, which is then cached in the CDN and served to users. Once per day, the server fetches a JSON file.</p>

<p><strong>Next.js:</strong></p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="k">default</span> <span class="k">async</span> <span class="kd">function</span> <span class="nx">Home</span><span class="p">()</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">quiz</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">fetch</span><span class="p">(</span><span class="s2">`https://domain.com/name.json`</span><span class="p">).</span><span class="nx">then</span><span class="p">((</span><span class="nx">res</span><span class="p">)</span> <span class="o">=&gt;</span>
    <span class="nx">res</span><span class="p">.</span><span class="nx">json</span><span class="p">()</span>
  <span class="p">);</span>

  <span class="k">return</span> <span class="o">&lt;</span><span class="nx">Quiz</span> <span class="nx">quiz</span><span class="o">=</span><span class="p">{</span><span class="nx">quiz</span><span class="p">}</span> <span class="sr">/&gt;</span><span class="err">;
</span><span class="p">}</span>
</code></pre></div></div>

<p>Since there’s no <code class="language-plaintext highlighter-rouge">"use client";</code> directive, this code runs only on the server.</p>

<p><strong>Qwik:</strong></p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">routeLoader$</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@builder.io/qwik-city</span><span class="dl">"</span><span class="p">;</span>

<span class="k">export</span> <span class="kd">const</span> <span class="nx">useQuiz</span> <span class="o">=</span> <span class="nx">routeLoader$</span><span class="p">(</span><span class="k">async</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">response</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">fetch</span><span class="p">(</span><span class="s2">`https://domain.com/name.json`</span><span class="p">).</span><span class="nx">then</span><span class="p">((</span><span class="nx">res</span><span class="p">)</span> <span class="o">=&gt;</span>
    <span class="nx">res</span><span class="p">.</span><span class="nx">json</span><span class="p">()</span>
  <span class="p">);</span>
  <span class="k">return</span> <span class="nx">response</span><span class="p">;</span>
<span class="p">});</span>

<span class="k">export</span> <span class="k">default</span> <span class="nx">component$</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">quiz</span> <span class="o">=</span> <span class="nx">useQuiz</span><span class="p">();</span>

  <span class="k">return</span> <span class="o">&lt;</span><span class="nx">Quiz</span> <span class="nx">quiz</span><span class="o">=</span><span class="p">{</span><span class="nx">quiz</span><span class="p">}</span> <span class="sr">/&gt;</span><span class="err">;
</span><span class="p">});</span>
</code></pre></div></div>

<p>With Qwik, <code class="language-plaintext highlighter-rouge">routeLoader$</code> defines server-side logic, and the component consumes it as a hook.</p>

<h2 id="summary">Summary</h2>

<p><strong>Qwik is clearly a leaner framework than Next.js</strong>, and the syntax differences are minimal. Both frameworks share the same core concepts: components, state/signals, data fetching, and more.</p>

<p>One big advantage of Next.js is the size of its community.</p>

<p>At the time of writing:</p>

<table>
  <thead>
    <tr>
      <th> </th>
      <th>Next.js</th>
      <th>Qwik</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>GitHub stars</td>
      <td>137k</td>
      <td>21.9k</td>
    </tr>
    <tr>
      <td>GitHub watchers</td>
      <td>1.5k</td>
      <td>135</td>
    </tr>
    <tr>
      <td>GitHub pull requests</td>
      <td>1.2k</td>
      <td>43</td>
    </tr>
    <tr>
      <td>NPM weekly downloads</td>
      <td>15.5 million</td>
      <td>16.3 thousand</td>
    </tr>
  </tbody>
</table>

<ul>
  <li><a href="https://github.com/vercel/next.js">Next.js GitHub repository</a></li>
  <li><a href="https://www.npmjs.com/package/next">Next.js NPM page</a></li>
  <li><a href="https://github.com/QwikDev/qwik">Qwik GitHub repository</a></li>
  <li><a href="https://www.npmjs.com/package/@builder.io/qwik">Qwik NPM page</a></li>
</ul>

<p>It’s easy to see that the Next.js community is much larger, which means better ecosystem support, more documentation, and more tooling.</p>

<p>For my personal projects, I’ll definitely keep using Qwik to get more familiar with it. In a professional environment, I’ll stick with Next.js. Just because a framework does something better doesn’t justify the cost of refactoring existing code. Even with AI agents helping, refactoring still involves testing, monitoring, and maintenance. Shaving off a fraction of a second usually isn’t worth the investment—unless you’re in the business of speed, like e-commerce, streaming, or other high-traffic services. Even then, there are often alternatives besides switching frameworks.</p>

<p>Finally, now that a lot of code is written by AI agents, I don’t think the framework choice matters. What really matters is keeping things like reusability, scalability, performance, and design patterns in mind.</p>

<p>It’s not about the framework, it’s about the solution.</p>

<p>That’s it—happy coding.</p>]]></content><author><name></name></author><category term="nextjs" /><category term="qwik" /><category term="javascript-frameworks" /><category term="lighthouse" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">One Artwork, Seven Questions: Exploring the Art Institute API and Gemini</title><link href="/posts/artic-gemini-quiz" rel="alternate" type="text/html" title="One Artwork, Seven Questions: Exploring the Art Institute API and Gemini" /><published>2026-01-01T15:00:00+00:00</published><updated>2026-01-01T15:00:00+00:00</updated><id>/posts/artic-gemini-quiz</id><content type="html" xml:base="/posts/artic-gemini-quiz"><![CDATA[<p><img src="/assets/2026-01-01-artic-gemini-quiz/banner.png" alt="Exploring the Art Institute API and Gemini" /></p>

<p>New year, new project. As a fan of the <a href="https://www.artic.edu/">Art Institute of Chicago</a>, I just realized they have a public <a href="https://api.artic.edu/docs/">RESTful API</a> and decided to build a simple web app: one piece of art a day and seven questions to learn more about the artwork.</p>

<p>This is how I built it:</p>

<h2 id="1-artist-list">1. Artist list</h2>

<p>First, I asked GPT to provide a list of famous artists found at the Art Institute of Chicago. I’ll keep the list private so as not to spoil the artists you’ll find in the app.</p>

<h2 id="2-artwork">2. Artwork</h2>

<p>Second, I hit the <a href="https://api.artic.edu/docs/#get-artworks-search">/search</a> endpoint to request one artwork from <em>artist X</em>. The endpoint is part of an Elasticsearch service, meaning it offers good tooling around filters. Here’s my query:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">query</span> <span class="o">=</span> <span class="p">{</span>
  <span class="na">query</span><span class="p">:</span> <span class="p">{</span>
    <span class="na">bool</span><span class="p">:</span> <span class="p">{</span>
      <span class="na">must</span><span class="p">:</span> <span class="p">[</span>
        <span class="p">{</span> <span class="na">term</span><span class="p">:</span> <span class="p">{</span> <span class="na">is_public_domain</span><span class="p">:</span> <span class="kc">true</span> <span class="p">}</span> <span class="p">},</span>
        <span class="p">{</span> <span class="na">term</span><span class="p">:</span> <span class="p">{</span> <span class="dl">"</span><span class="s2">artist_title.keyword</span><span class="dl">"</span><span class="p">:</span> <span class="nx">artist</span> <span class="p">}</span> <span class="p">},</span>
        <span class="p">{</span>
          <span class="na">match</span><span class="p">:</span> <span class="p">{</span>
            <span class="na">medium_display</span><span class="p">:</span> <span class="p">{</span> <span class="na">query</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Oil on canvas</span><span class="dl">"</span><span class="p">,</span> <span class="na">fuzziness</span><span class="p">:</span> <span class="dl">"</span><span class="s2">AUTO</span><span class="dl">"</span> <span class="p">},</span>
          <span class="p">},</span>
        <span class="p">},</span>
      <span class="p">],</span>
    <span class="p">},</span>
  <span class="p">},</span>
<span class="p">};</span>
</code></pre></div></div>

<p>There are three filters:</p>

<p>a) <strong>Public domain</strong>. This is very important to avoid copyright issues.</p>

<p>b) The artist’s name needs to match. There are a lot of artists and a lot of great works, but for now I’m interested in specific names.</p>

<p>c) I like <code class="language-plaintext highlighter-rouge">oil on canvas</code>, so I’m filtering by it, although this is more of a personal choice.</p>

<h2 id="3-quiz">3. Quiz</h2>

<p>Now that I have an artist’s name and one of their artworks (which could actually be found at the museum), the next step is to use AI to generate the questions. Here’s the prompt I’m using:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  Generate 7 quiz questions about the following artwork: <span class="k">${</span><span class="nv">artist</span><span class="k">}</span> - <span class="k">${</span><span class="nv">artwork</span><span class="k">}</span><span class="p">;</span>
  use the following example questions as a guide: <span class="k">${</span><span class="nv">JSON</span><span class="p">.stringify(exampleQuestions)</span><span class="k">}</span><span class="p">;</span>
  the questions should be a mix of easy, intermediate, hard, and expert difficulty levels<span class="p">;</span>
  each question should have 3 options labeled A, B, and C<span class="p">;</span>
  provide the correct answer <span class="k">for </span>each question<span class="p">;</span>
  <span class="k">return </span>the result as a JSON array of question objects with the following structure: <span class="o">{</span> difficulty: string, question_number: number, question_text: string, options: <span class="o">{</span> A: string, B: string, C: string <span class="o">}</span>, correct_answer: string <span class="o">(</span>A, B, or C<span class="o">)</span> <span class="o">}</span><span class="p">;</span>
  only <span class="k">return </span>the JSON array without any additional text<span class="p">;</span>
  the questions should only be about the artwork
</code></pre></div></div>

<p>Then I pass the prompt to library <code class="language-plaintext highlighter-rouge">@google/genai</code></p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">GoogleGenAI</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@google/genai</span><span class="dl">"</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">ai</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">GoogleGenAI</span><span class="p">({});</span>
<span class="kd">const</span> <span class="nx">response</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">ai</span><span class="p">.</span><span class="nx">models</span><span class="p">.</span><span class="nx">generateContent</span><span class="p">({</span>
  <span class="na">model</span><span class="p">:</span> <span class="dl">"</span><span class="s2">gemini-2.5-flash</span><span class="dl">"</span><span class="p">,</span>
  <span class="na">contents</span><span class="p">:</span> <span class="nx">prompt</span><span class="p">,</span>
<span class="p">});</span>
<span class="kd">const</span> <span class="nx">cleanJson</span> <span class="o">=</span> <span class="nx">response</span><span class="p">.</span><span class="nx">text</span><span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="sr">/^```json|```$/g</span><span class="p">,</span> <span class="dl">""</span><span class="p">).</span><span class="nx">trim</span><span class="p">();</span>
<span class="kd">const</span> <span class="nx">questions</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">parse</span><span class="p">(</span><span class="nx">cleanJson</span><span class="p">);</span>
</code></pre></div></div>

<p>A couple of things to mention here:</p>

<p>a) You need a <code class="language-plaintext highlighter-rouge">GEMINI_API_KEY</code>; get one from <a href="https://aistudio.google.com/">Google</a>.</p>

<p>b) I’m using the model <code class="language-plaintext highlighter-rouge">gemini-2.5-flash</code>, but it has some rate limitations. Not bad considering it’s free, but if you need to make more requests, you can try another model like <code class="language-plaintext highlighter-rouge">gemma-3-12b-it</code>, which allows more requests. Each model is trained differently, and at least for this simple case, I didn’t notice any significant difference.</p>

<p><img src="/assets/2026-01-01-artic-gemini-quiz/models-limitations.png" alt="Model Rate Limitations" /></p>

<p>c) One last thing: the agent usually returns something like this:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>```json [
  {
    "image": "...",
    "quiz_title": "...",
    "questions": []
  }
}
```
</code></pre></div></div>

<p>So I had to remove ```json` and then parse the rest of the string, which was the array of questions I was expecting.</p>

<h2 id="4-web-app">4. Web app</h2>

<p>Finally, I’m going with <strong>Next.js</strong> plus <strong>Copilot</strong> to build the UI. I wrote a bunch of prompts, but the idea was to:</p>

<ul>
  <li>Display the image found in the JSON</li>
  <li>Show one question at a time</li>
  <li>Keep score of correct answers</li>
  <li>Once the quiz is finished, show a short summary</li>
</ul>

<p>And that’s it. Please visit the <a href="https://artic.mintitmedia.com/">quiz app</a> and check out the <a href="https://github.com/garciadiazjaime/website-artic/tree/main">codebase</a>.</p>

<p><img src="/assets/2026-01-01-artic-gemini-quiz/website.png" alt="One Artwork, Seven Questions" /></p>

<p>Happy 2026 coding!</p>]]></content><author><name></name></author><category term="nextjs" /><category term="art" /><category term="gemini" /><category term="copilot" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Another ETL: Night Lift Tickets</title><link href="/posts/etl-night-lift-tickets" rel="alternate" type="text/html" title="Another ETL: Night Lift Tickets" /><published>2025-12-22T15:00:00+00:00</published><updated>2025-12-22T15:00:00+00:00</updated><id>/posts/etl-night-lift-tickets</id><content type="html" xml:base="/posts/etl-night-lift-tickets"><![CDATA[<p><img src="/assets/2025-12-22-etl-night-lift-tickets/banner.png" alt="Another ETL: Night Lift Tickets" /></p>

<p>I live in Chicago, and one thing I like about the winter is having the chance to go snowboarding. I’m not very good at it, but I enjoy it. Usually, I would open multiple websites until I find a place to go. So every time I find myself doing a repetitive manual task—in this case, <strong>opening multiple websites with similar content for the same purpose</strong>—my brain goes directly to ETL.</p>

<p>And yes, there are already some catalog websites that show different hills around, but not the way I want. In my case, I’m interested in <strong>night lift tickets</strong>, because they have the best price, and I want to <strong>see the places on a map</strong> to find out how far they are from my place.</p>

<p>I wrote a couple of ETLs; they all have the same structure: <strong>Extract, Transform, and Load</strong>. In this case, <strong>Extract</strong> and <strong>Load</strong> are basically the same for all ETLs, which is why I put those functions in a <code class="language-plaintext highlighter-rouge">common.js</code> file, while the only custom part lives in <strong>Transform</strong>, which helps extract the price from the HTML using a CSS selector.</p>

<p>For this part, the HTML is passed to <code class="language-plaintext highlighter-rouge">cheerio</code>, which is a nice package that enables something similar to <code class="language-plaintext highlighter-rouge">jQuery</code>, making it easier to find elements in the HTML. Take a look at the following line:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">price</span> <span class="o">=</span> <span class="nx">$</span><span class="p">(</span><span class="nx">$</span><span class="p">(</span><span class="dl">"</span><span class="s2">.seasoncontainer.liftcontainer.clsDaynight.clsNight li</span><span class="dl">"</span><span class="p">)[</span><span class="mi">2</span><span class="p">]);</span>
</code></pre></div></div>

<p>Of course, if the CSS classes change on the site, then the scraper won’t work. This is one of the challenges of using this approach in ETLs. In production, you would have an alert to help you know whenever the price can’t be extracted. Or now, you could use AI—maybe <a href="https://www.garciadiazjaime.com/posts/gemini-api-free-tier">Gemini</a>—and try to extract the price using a model.</p>

<p>I went with a CSS selector because these sites rarely change their code.</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">cheerio</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">cheerio</span><span class="dl">"</span><span class="p">);</span>

<span class="kd">const</span> <span class="p">{</span> <span class="nx">extract</span><span class="p">,</span> <span class="nx">load</span><span class="p">,</span> <span class="nx">loggerInfo</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">./common</span><span class="dl">"</span><span class="p">);</span>

<span class="k">async</span> <span class="kd">function</span> <span class="nx">transform</span><span class="p">(</span><span class="nx">html</span><span class="p">)</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">$</span> <span class="o">=</span> <span class="nx">cheerio</span><span class="p">.</span><span class="nx">load</span><span class="p">(</span><span class="nx">html</span><span class="p">);</span>

  <span class="kd">const</span> <span class="nx">price</span> <span class="o">=</span> <span class="nx">$</span><span class="p">(</span>
    <span class="nx">$</span><span class="p">(</span><span class="dl">"</span><span class="s2">.seasoncontainer.liftcontainer.clsDaynight.clsNight li</span><span class="dl">"</span><span class="p">)[</span><span class="mi">2</span><span class="p">]</span>
  <span class="p">)</span>
    <span class="p">.</span><span class="nx">find</span><span class="p">(</span><span class="dl">"</span><span class="s2">p</span><span class="dl">"</span><span class="p">)</span>
    <span class="p">.</span><span class="nx">text</span><span class="p">()</span>
    <span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="dl">"</span><span class="s2">$</span><span class="dl">"</span><span class="p">,</span> <span class="dl">""</span><span class="p">);</span>

  <span class="k">return</span> <span class="p">{</span>
    <span class="na">price</span><span class="p">:</span> <span class="nb">parseInt</span><span class="p">(</span><span class="nx">price</span><span class="p">),</span>
  <span class="p">};</span>
<span class="p">}</span>

<span class="k">async</span> <span class="kd">function</span> <span class="nx">main</span><span class="p">()</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">place</span> <span class="o">=</span> <span class="p">{</span>
    <span class="na">id</span><span class="p">:</span> <span class="dl">"</span><span class="s2">cascademountain</span><span class="dl">"</span><span class="p">,</span>
    <span class="na">website</span><span class="p">:</span> <span class="dl">"</span><span class="s2">https://www.cascademountain.com</span><span class="dl">"</span><span class="p">,</span>
    <span class="na">name</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Cascade Mountain</span><span class="dl">"</span><span class="p">,</span>
    <span class="na">lat</span><span class="p">:</span> <span class="mf">43.502728</span><span class="p">,</span>
    <span class="na">lng</span><span class="p">:</span> <span class="o">-</span><span class="mf">89.515996</span><span class="p">,</span>
    <span class="na">gmaps</span><span class="p">:</span> <span class="dl">"</span><span class="s2">https://maps.app.goo.gl/YWdnQvZiJZwPhj79A</span><span class="dl">"</span><span class="p">,</span>
    <span class="na">url</span><span class="p">:</span> <span class="s2">`https://www.cascademountain.com/lift-tickets/`</span><span class="p">,</span>
  <span class="p">};</span>
  <span class="nx">loggerInfo</span><span class="p">(</span><span class="dl">"</span><span class="s2">etl start</span><span class="dl">"</span><span class="p">,</span> <span class="p">{</span> <span class="na">id</span><span class="p">:</span> <span class="nx">place</span><span class="p">.</span><span class="nx">id</span> <span class="p">});</span>

  <span class="kd">const</span> <span class="nx">html</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">extract</span><span class="p">(</span><span class="nx">place</span><span class="p">.</span><span class="nx">url</span><span class="p">);</span>
  <span class="kd">const</span> <span class="nx">data</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">transform</span><span class="p">(</span><span class="nx">html</span><span class="p">);</span>
  <span class="k">await</span> <span class="nx">load</span><span class="p">(</span><span class="nx">place</span><span class="p">,</span> <span class="nx">data</span><span class="p">);</span>

  <span class="nx">loggerInfo</span><span class="p">(</span><span class="dl">"</span><span class="s2">etl done</span><span class="dl">"</span><span class="p">,</span> <span class="p">{</span> <span class="na">id</span><span class="p">:</span> <span class="nx">place</span><span class="p">.</span><span class="nx">id</span> <span class="p">});</span>
<span class="p">}</span>

<span class="nx">main</span><span class="p">().</span><span class="nx">then</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{});</span>
</code></pre></div></div>

<p>The only dynamic property is <code class="language-plaintext highlighter-rouge">price</code>; the others are hardcoded. The thought is that prices change over time, but a business is either running or closed. The hardcoded values could be extracted using, maybe, Google Maps APIs, but considering that the number of places is limited, hardcoding them for now is fine. If my site gets real traffic, then I would consider writing a script to use Google Maps APIs and programmatically get a more comprehensive list of ski places.</p>

<p>The <code class="language-plaintext highlighter-rouge">extract</code> function simply makes a <code class="language-plaintext highlighter-rouge">fetch</code> request to the URL passed. There’s a variation in the codebase that uses <a href="https://github.com/garciadiazjaime/website-ski/blob/main/etl/common.js#L16">puppeteer</a>, because some sites don’t like scrapers.</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">async</span> <span class="kd">function</span> <span class="nx">extract</span><span class="p">(</span><span class="nx">url</span><span class="p">)</span> <span class="p">{</span>
  <span class="nx">loggerInfo</span><span class="p">(</span><span class="dl">"</span><span class="s2">extracting</span><span class="dl">"</span><span class="p">,</span> <span class="p">{</span> <span class="nx">url</span> <span class="p">});</span>

  <span class="kd">const</span> <span class="nx">response</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">fetch</span><span class="p">(</span><span class="nx">url</span><span class="p">);</span>
  <span class="kd">const</span> <span class="nx">html</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">response</span><span class="p">.</span><span class="nx">text</span><span class="p">();</span>
  <span class="k">return</span> <span class="nx">html</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">load</code> function only saves the data in a <code class="language-plaintext highlighter-rouge">json</code> file. For now, a file is enough. At some point, this should be a database; I would go with <strong>DynamoDB</strong> because it’s cheap.</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">async</span> <span class="kd">function</span> <span class="nx">load</span><span class="p">(</span><span class="nx">place</span><span class="p">,</span> <span class="nx">extraData</span><span class="p">)</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">data</span> <span class="o">=</span> <span class="p">{</span> <span class="p">...</span><span class="nx">place</span><span class="p">,</span> <span class="p">...</span><span class="nx">extraData</span> <span class="p">};</span>
  <span class="nx">loggerInfo</span><span class="p">(</span><span class="dl">"</span><span class="s2">load</span><span class="dl">"</span><span class="p">,</span> <span class="nx">data</span><span class="p">);</span>

  <span class="kd">const</span> <span class="nx">filename</span> <span class="o">=</span> <span class="s2">`public/sites/</span><span class="p">${</span><span class="nx">data</span><span class="p">.</span><span class="nx">id</span><span class="p">}</span><span class="s2">.json`</span><span class="p">;</span>
  <span class="k">await</span> <span class="nx">fs</span><span class="p">.</span><span class="nx">writeFile</span><span class="p">(</span><span class="nx">filename</span><span class="p">,</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">data</span><span class="p">,</span> <span class="kc">null</span><span class="p">,</span> <span class="mi">2</span><span class="p">));</span>

  <span class="nx">loggerInfo</span><span class="p">(</span><span class="dl">"</span><span class="s2">saved</span><span class="dl">"</span><span class="p">,</span> <span class="p">{</span> <span class="nx">filename</span> <span class="p">});</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">loggerInfo</code> is nothing more than a wrapper around <code class="language-plaintext highlighter-rouge">console.log</code>, which for now is enough. But in a production-like application, you could plug in something like <strong>New Relic</strong> or any other logging service.</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">loggerInfo</span> <span class="o">=</span> <span class="p">(...</span><span class="nx">args</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(...</span><span class="nx">args</span><span class="p">);</span>
<span class="p">};</span>
</code></pre></div></div>

<p>Alright, so that’s the ETL. Now that I have the information, the rest is to build a website to show the places. I usually go with <strong>Next.js</strong> and then use <strong>Copilot</strong> to build it, so the prompt looks like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>On this Next.js project, build a page with the following requirements:
- Only one page.
- The page should show a map using Google Maps.
- The map should show a marker for each place found in `all-places.json`, displaying the price.
- The page should have Google Analytics.
- The page should let the user click a marker and open a small card with the place information: name, link to Google Maps, and link to the `url` found in the JSON.
- The UI should be simple and use inline styles.
</code></pre></div></div>

<p>Something like that is a good starter. Of course, after that there would be some adjustments—some will be taken care of by it, and some by them.</p>

<p>And that’s it. Please visit the site and let me know what you think:</p>

<ul>
  <li><a href="https://goofy.mintitmedia.com/">goofy</a></li>
</ul>

<p><img src="/assets/2025-12-22-etl-night-lift-tickets/website.png" alt="Another ETL: Night Lift Tickets" /></p>

<ul>
  <li><a href="https://github.com/garciadiazjaime/website-ski/tree/main">codebase</a></li>
</ul>

<p>Happy coding.</p>

<p>P.S. Yes, I’m goofy.</p>]]></content><author><name></name></author><category term="nextjs" /><category term="react" /><category term="etl" /><category term="copilot" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Shai Hulud Scanner</title><link href="/posts/shai-hulud-scanner" rel="alternate" type="text/html" title="Shai Hulud Scanner" /><published>2025-11-27T15:00:00+00:00</published><updated>2025-11-27T15:00:00+00:00</updated><id>/posts/shai-hulud-scanner</id><content type="html" xml:base="/posts/shai-hulud-scanner"><![CDATA[<p><img src="/assets/2025-11-27-shai-hulud-scanner/banner.png" alt="Shai Hulud Scanner" /></p>

<p>This week I spent some time looking for infected npm packages. Initially, the warning was to not install any package or run any AI agent, so I went ahead and created this Node.js script.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">scanForShaiHulud</span> <span class="o">=</span> <span class="p">(</span><span class="nx">content</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">content</span><span class="p">)</span> <span class="k">return</span><span class="p">;</span>

  <span class="kd">const</span> <span class="nx">report</span> <span class="o">=</span> <span class="p">{</span>
    <span class="na">total</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
    <span class="na">warning</span><span class="p">:</span> <span class="p">[],</span>
    <span class="na">infected</span><span class="p">:</span> <span class="p">[],</span>
  <span class="p">};</span>

  <span class="nb">Object</span><span class="p">.</span><span class="nx">keys</span><span class="p">(</span><span class="nx">content</span><span class="p">.</span><span class="nx">packages</span><span class="p">).</span><span class="nx">forEach</span><span class="p">((</span><span class="nx">packageName</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">packageName</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">return</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="kd">let</span> <span class="nx">cleanedName</span> <span class="o">=</span> <span class="nx">packageName</span><span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="dl">"</span><span class="s2">node_modules/</span><span class="dl">"</span><span class="p">,</span> <span class="dl">""</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="nx">cleanedName</span><span class="p">.</span><span class="nx">includes</span><span class="p">(</span><span class="dl">"</span><span class="s2">/node_modules/</span><span class="dl">"</span><span class="p">))</span> <span class="p">{</span>
      <span class="nx">cleanedName</span> <span class="o">=</span> <span class="nx">cleanedName</span><span class="p">.</span><span class="nx">split</span><span class="p">(</span><span class="dl">"</span><span class="s2">/node_modules/</span><span class="dl">"</span><span class="p">)[</span><span class="mi">1</span><span class="p">];</span>
    <span class="p">}</span>

    <span class="nx">report</span><span class="p">.</span><span class="nx">total</span> <span class="o">+=</span> <span class="mi">1</span><span class="p">;</span>

    <span class="k">if</span> <span class="p">(</span><span class="nx">infectedPackages</span><span class="p">[</span><span class="nx">cleanedName</span><span class="p">])</span> <span class="p">{</span>
      <span class="k">if</span> <span class="p">(</span>
        <span class="nx">infectedPackages</span><span class="p">[</span><span class="nx">cleanedName</span><span class="p">].</span><span class="nx">includes</span><span class="p">(</span>
          <span class="nx">content</span><span class="p">.</span><span class="nx">packages</span><span class="p">[</span><span class="nx">packageName</span><span class="p">].</span><span class="nx">version</span>
        <span class="p">)</span>
      <span class="p">)</span> <span class="p">{</span>
        <span class="nx">report</span><span class="p">.</span><span class="nx">infected</span><span class="p">.</span><span class="nx">push</span><span class="p">({</span>
          <span class="na">name</span><span class="p">:</span> <span class="nx">cleanedName</span><span class="p">,</span>
          <span class="na">version</span><span class="p">:</span> <span class="nx">content</span><span class="p">.</span><span class="nx">packages</span><span class="p">[</span><span class="nx">packageName</span><span class="p">].</span><span class="nx">version</span><span class="p">,</span>
          <span class="na">affectedVersions</span><span class="p">:</span> <span class="nx">infectedPackages</span><span class="p">[</span><span class="nx">cleanedName</span><span class="p">],</span>
        <span class="p">});</span>
      <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="nx">report</span><span class="p">.</span><span class="nx">warning</span><span class="p">.</span><span class="nx">push</span><span class="p">({</span>
          <span class="na">name</span><span class="p">:</span> <span class="nx">cleanedName</span><span class="p">,</span>
          <span class="na">version</span><span class="p">:</span> <span class="nx">content</span><span class="p">.</span><span class="nx">packages</span><span class="p">[</span><span class="nx">packageName</span><span class="p">].</span><span class="nx">version</span><span class="p">,</span>
          <span class="na">affectedVersions</span><span class="p">:</span> <span class="nx">infectedPackages</span><span class="p">[</span><span class="nx">cleanedName</span><span class="p">],</span>
        <span class="p">});</span>
      <span class="p">}</span>
    <span class="p">}</span>
  <span class="p">});</span>

  <span class="k">return</span> <span class="nx">report</span><span class="p">;</span>
<span class="p">};</span>

<span class="kd">const</span> <span class="nx">fileContent</span> <span class="o">=</span> <span class="nx">fs</span><span class="p">.</span><span class="nx">readFileSync</span><span class="p">(</span><span class="dl">"</span><span class="s2">./package-lock.json</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">utf-8</span><span class="dl">"</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">parsedContent</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">parse</span><span class="p">(</span><span class="nx">fileContent</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">report</span> <span class="o">=</span> <span class="nx">scanForShaiHulud</span><span class="p">(</span><span class="nx">parsedContent</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">report</span><span class="p">);</span>
</code></pre></div></div>

<p>Before this week, I knew that <code class="language-plaintext highlighter-rouge">package-lock.json</code> declared all the specific versions installed, but I hadn’t really spent time understanding the structure. Now I know that <code class="language-plaintext highlighter-rouge">package-lock.json</code> stores, in the key <code class="language-plaintext highlighter-rouge">.packages</code>, a map of all the packages installed in the repository—either as dependencies of the project or dependencies of dependencies. The point is that <code class="language-plaintext highlighter-rouge">.packages</code> holds a map with all the packages installed, so it can be used to check if any of the infected packages reported by <a href="https://github.com/wiz-sec-public/wiz-research-iocs/blob/main/reports/shai-hulud-2-packages.csv">wiz-sec-public</a> matches the version.</p>

<p>The helper is pretty simple: it iterates over every single key found in <code class="language-plaintext highlighter-rouge">.packages</code> and checks it against the list passed.</p>

<p>At the end, the report shows if there’s any:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">warning</code>: package reported by Wiz as infected, but not the same version</li>
  <li><code class="language-plaintext highlighter-rouge">infected</code>: package reported by Wiz as infected, with a matching version</li>
</ul>

<p>In the case of <code class="language-plaintext highlighter-rouge">infected</code>, you must remove that package; otherwise, whatever secrets are stored in your project locally could be exposed.</p>

<p>In the case of <code class="language-plaintext highlighter-rouge">warning</code>, I would do the same—remove the package—just to be safe. But if the version is lower than the one reported, in theory you shouldn’t be in trouble.</p>

<p>Feel free to download the <a href="https://github.com/garciadiazjaime/demo-reactjs/blob/main/app/shai-hulud-scanner/shai-hulud-scanner.js">shai-hulud-scanner</a> to your Node.js project. Once you run it, you should see a report like this:</p>

<p><img src="/assets/2025-11-27-shai-hulud-scanner/terminal-shai-hulud-scanner-report.png" alt="Terminal Shai Hulud Scanner Report" /></p>

<p>Hopefully everything is clear; otherwise take immediate action.</p>

<p>Additionally, you can visit this <a href="https://demo.garciadiazjaime.com/shai-hulud-scanner">Shai-Hulud Scanner</a> and upload a <code class="language-plaintext highlighter-rouge">package-lock.json</code> file. The same scanner function will run, and you can see the results in the browser.</p>

<p><img src="/assets/2025-11-27-shai-hulud-scanner/online-shai-hulud-scanner-report.png" alt="Online Shai Hulud Scanner Report" /></p>

<p><strong>Note</strong>: This post is meant for package-lock.json. If you are using <code class="language-plaintext highlighter-rouge">yarn</code> or <code class="language-plaintext highlighter-rouge">pnpm</code>, this won’t work because those package managers use different lock files. In any case, you should be able to adjust the logic a bit and make it work.</p>

<p>Luckily, we didn’t find any threats, proving that the Wiz list is comprehensive. I’m a big fan of open source—I believe in the power of community—but as with everything, there are risks too. Someone will always try to break the rules, which in some way pushes us to be more aware and spend more time on security. After this week, I’ve seen numerous packages for detecting this kind of threat, and that’s one of the perks of open source: code written by the people, for the people.</p>

<p>Happy coding.</p>]]></content><author><name></name></author><category term="nodejs" /><category term="npm" /><category term="cybersecurity" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Setting a Reverse Proxy with Nginx</title><link href="/posts/nginx-reverse-proxy" rel="alternate" type="text/html" title="Setting a Reverse Proxy with Nginx" /><published>2025-10-27T15:00:00+00:00</published><updated>2025-10-27T15:00:00+00:00</updated><id>/posts/nginx-reverse-proxy</id><content type="html" xml:base="/posts/nginx-reverse-proxy"><![CDATA[<p><img src="/assets/nginx-reverse-proxy/banner.png" alt="Setting a Reverse Proxy with Nginx" /></p>

<p>Last week, I needed to run a POC and wanted to use a reverse proxy so that the URLs looked clean during the demo. The demo was running locally, and I didn’t really need the reverse proxy — but since it was simple to set up, why not? So I’m leaving here the steps I followed.</p>

<h3 id="1-install-nginx">1. Install <code class="language-plaintext highlighter-rouge">nginx</code></h3>

<p>I already had <code class="language-plaintext highlighter-rouge">nginx</code> installed, and I’m on macOS. If you don’t have it installed, you can use brew.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>brew <span class="nb">install </span>nginx
</code></pre></div></div>

<h3 id="2-democonf">2. <code class="language-plaintext highlighter-rouge">demo.conf</code></h3>

<p>I created a <code class="language-plaintext highlighter-rouge">demo.conf</code> file in this directory:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/opt/homebrew/etc/nginx/servers/demo.conf
</code></pre></div></div>

<p>That’s usually where Homebrew installs packages, and any file named <code class="language-plaintext highlighter-rouge">*.conf</code> will be read automatically by Nginx.</p>

<p>Nginx allows you to define a reverse proxy, which is a way to configure a server — in my case, <code class="language-plaintext highlighter-rouge">demo.local</code> — and then, based on the path, redirect the request to one location or another. For example, when I open <code class="language-plaintext highlighter-rouge">http://demo.local</code>, the request is served from <code class="language-plaintext highlighter-rouge">http://127.0.0.1:3000/</code>, which is a simple Next.js application. And whenever I open <code class="language-plaintext highlighter-rouge">http://demo.local/coupons</code>, the request is served from <code class="language-plaintext highlighter-rouge">https://coupons.garitacenter.com/</code>.</p>

<p>Notice the transparency: without knowing that there’s a reverse proxy, it would be hard to guess that we’re interacting with two different applications.</p>

<p>Here’s the Nginx configuration to route the requests:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># /opt/homebrew/etc/nginx/servers/demo.conf</span>
server <span class="o">{</span>
    listen 80<span class="p">;</span>
    server_name demo.local<span class="p">;</span>

    location /coupons <span class="o">{</span>
        proxy_pass https://coupons.garitacenter.com/<span class="p">;</span>
        proxy_set_header Host coupons.garitacenter.com<span class="p">;</span>
        proxy_set_header X-Real-IP <span class="nv">$remote_addr</span><span class="p">;</span>
        proxy_set_header X-Forwarded-For <span class="nv">$proxy_add_x_forwarded_for</span><span class="p">;</span>
        proxy_set_header X-Forwarded-Proto <span class="nv">$scheme</span><span class="p">;</span>
        proxy_ssl_server_name on<span class="p">;</span>
    <span class="o">}</span>

    location / <span class="o">{</span>
        proxy_pass http://127.0.0.1:3000/<span class="p">;</span>
        proxy_set_header Host <span class="nv">$host</span><span class="p">;</span>
        proxy_set_header X-Real-IP <span class="nv">$remote_addr</span><span class="p">;</span>
        proxy_set_header X-Forwarded-For <span class="nv">$proxy_add_x_forwarded_for</span><span class="p">;</span>
        proxy_set_header X-Forwarded-Proto <span class="nv">$scheme</span><span class="p">;</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p>As you can see, it listens on port <code class="language-plaintext highlighter-rouge">80</code> and assigns the <code class="language-plaintext highlighter-rouge">server_name</code> to <code class="language-plaintext highlighter-rouge">demo.local</code>. Then, based on the path (<code class="language-plaintext highlighter-rouge">/coupons</code> vs <code class="language-plaintext highlighter-rouge">/</code>), it redirects the request to one place or another.</p>

<p>Any change made to this file requires Nginx to be restarted.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>brew services restart nginx
</code></pre></div></div>

<h3 id="3-update-hosts">3. Update <code class="language-plaintext highlighter-rouge">hosts</code></h3>

<p>Additionally, I needed to update my <code class="language-plaintext highlighter-rouge">hosts</code> configuration file so that <code class="language-plaintext highlighter-rouge">demo.local</code> points to my <code class="language-plaintext highlighter-rouge">localhost</code>.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># /etc/hosts</span>
127.0.0.1       demo.local
</code></pre></div></div>

<p>Changes here are picked up automatically by the machine, so as soon as the file is modified, you can start using <code class="language-plaintext highlighter-rouge">demo.local</code>.</p>

<p>In the following image, notice how <code class="language-plaintext highlighter-rouge">http://demo.local/nginx-reverse-proxy</code> and <code class="language-plaintext highlighter-rouge">http://localhost:3000/nginx-reverse-proxy</code> render the same content.</p>

<p><img src="/assets/nginx-reverse-proxy/localhost_and_custom_url.png" alt="Nginx reverse proxy" /></p>

<p>Now let’s take a look at the other location:</p>

<p><img src="/assets/nginx-reverse-proxy/other_location.png" alt="Other location" /></p>

<hr />

<p>Browsers nowadays, by default, try to use <code class="language-plaintext highlighter-rouge">https</code>, which means the above instructions might work in incognito mode, but in a regular session you might see a <strong>“Your connection is not private”</strong> message.</p>

<p><img src="/assets/nginx-reverse-proxy/browser-error.png" alt="Your connection is not private Browser Error" /></p>

<h2 id="certificates">Certificates</h2>

<h3 id="4-install-mkcert">4. Install <code class="language-plaintext highlighter-rouge">mkcert</code></h3>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>brew <span class="nb">install </span>mkcert nss
</code></pre></div></div>

<h3 id="5-create-certificates">5. Create certificates</h3>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mkcert <span class="nt">-install</span>

mkcert <span class="nt">-cert-file</span> /opt/homebrew/etc/nginx/certs/demo.local.pem <span class="nt">-key-file</span> /opt/homebrew/etc/nginx/certs/demo.local-key.pem demo.local localhost 127.0.0.1
</code></pre></div></div>

<p>Note: If you need to use <code class="language-plaintext highlighter-rouge">sudo</code> to create the certificates, make sure to change the owner of the files to your regular user afterwards.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">ls</span> <span class="nt">-la</span> /opt/homebrew/etc/nginx/certs/

<span class="nt">-rw-------</span>@  1 myuser  admin  1708 Oct 19 14:26 demo.local-key.pem
<span class="nt">-rw-r--r--</span>@  1 myuser  admin  1562 Oct 19 14:26 demo.local.pem
</code></pre></div></div>

<h3 id="6-update-democonf">6. Update <code class="language-plaintext highlighter-rouge">demo.conf</code></h3>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>server <span class="o">{</span>
    listen 80<span class="p">;</span>
    server_name local.media.patientpoint.io<span class="p">;</span>

    <span class="c"># Redirect HTTP to HTTPS</span>
    <span class="k">return </span>301 https://<span class="nv">$server_name$request_uri</span><span class="p">;</span>
<span class="o">}</span>

server <span class="o">{</span>
    listen 443 ssl<span class="p">;</span>
    server_name demo.local<span class="p">;</span>

    ssl_certificate /opt/homebrew/etc/nginx/certs/demo.local.pem<span class="p">;</span>
    ssl_certificate_key /opt/homebrew/etc/nginx/certs/demo.local-key.pem<span class="p">;</span>

    location /coupons <span class="o">{</span>
        proxy_pass https://coupons.garitacenter.com/<span class="p">;</span>
        proxy_set_header Host coupons.garitacenter.com<span class="p">;</span>
        proxy_set_header X-Real-IP <span class="nv">$remote_addr</span><span class="p">;</span>
        proxy_set_header X-Forwarded-For <span class="nv">$proxy_add_x_forwarded_for</span><span class="p">;</span>
        proxy_set_header X-Forwarded-Proto <span class="nv">$scheme</span><span class="p">;</span>
        proxy_ssl_server_name on<span class="p">;</span>
    <span class="o">}</span>

    location / <span class="o">{</span>
        proxy_pass http://127.0.0.1:3000/<span class="p">;</span>
        proxy_set_header Host <span class="nv">$host</span><span class="p">;</span>
        proxy_set_header X-Real-IP <span class="nv">$remote_addr</span><span class="p">;</span>
        proxy_set_header X-Forwarded-For <span class="nv">$proxy_add_x_forwarded_for</span><span class="p">;</span>
        proxy_set_header X-Forwarded-Proto <span class="nv">$scheme</span><span class="p">;</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h3 id="7-restart-nginx">7. Restart Nginx</h3>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>brew services restart nginx
</code></pre></div></div>

<p>You should see output like this:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>brew services restart nginx

Stopping <span class="sb">`</span>nginx<span class="sb">`</span>... <span class="o">(</span>might take a <span class="k">while</span><span class="o">)</span>
<span class="o">==&gt;</span> Successfully stopped <span class="sb">`</span>nginx<span class="sb">`</span> <span class="o">(</span>label: homebrew.mxcl.nginx<span class="o">)</span>
<span class="o">==&gt;</span> Successfully started <span class="sb">`</span>nginx<span class="sb">`</span> <span class="o">(</span>label: homebrew.mxcl.nginx<span class="o">)</span>
</code></pre></div></div>

<p>If everything went well, you can now try the URL again, and the browser shouldn’t complain about security:</p>

<p><img src="/assets/nginx-reverse-proxy/nginx-https.png" alt="HTTPS Enable For Localhost" /></p>

<h3 id="how-to-debug">How to debug</h3>

<p>If you face any issues, you can check the Nginx logs:</p>

<ul>
  <li>Error logs:</li>
</ul>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">tail</span> <span class="nt">-f</span> /opt/homebrew/var/log/nginx/error.log
</code></pre></div></div>

<ul>
  <li>Access logs:</li>
</ul>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">tail</span> <span class="nt">-f</span> /opt/homebrew/var/log/nginx/access.log
</code></pre></div></div>

<p>These two files should give a good indication of what’s going on. Sometimes it’s a permission issue (e.g., the certificates are owned by <code class="language-plaintext highlighter-rouge">admin</code>), or they might not be found because of a wrong path, or the request might be missing some headers. In any case, when facing an error, these two logs are where you’ll find the root cause.</p>

<h2 id="summary">Summary</h2>

<p>This is a simple example of how to use <code class="language-plaintext highlighter-rouge">nginx</code> as a <code class="language-plaintext highlighter-rouge">reverse proxy</code> on a <code class="language-plaintext highlighter-rouge">local machine</code>. Now, two different web applications can be accessed through a custom URL, and based on the path — either root <code class="language-plaintext highlighter-rouge">/</code> or <code class="language-plaintext highlighter-rouge">/coupons</code> — the responses come from different servers.</p>

<p>Although this setup is running locally, the concept applies in production. In many cases, a proxy will sit in front of our services. Of course, in production you would put more effort into security, caching, and possibly work with a vendor like AWS, but the concept remains the same.</p>]]></content><author><name></name></author><category term="nginx" /><category term="reverse-proxy" /><category term="software-architecture" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Nextjs Todo Application Using Mysql and Terraform</title><link href="/posts/react-todo-aws-mysql" rel="alternate" type="text/html" title="Nextjs Todo Application Using Mysql and Terraform" /><published>2025-09-29T15:00:00+00:00</published><updated>2025-09-29T15:00:00+00:00</updated><id>/posts/react-todo-aws-mysql</id><content type="html" xml:base="/posts/react-todo-aws-mysql"><![CDATA[<p><img src="/assets/react-todo-aws-mysql/banner.png" alt="Nextjs Todo Application Using Mysql and Terraform" /></p>

<p>Hi, here’s another Todo application in React. Nothing exceptional compared to other examples, but I’ll focus more on hosting the database in AWS and using Terraform for deployment.</p>

<p>Take a look at the <a href="https://demo.garciadiazjaime.com/react-todo-aws-mysql">Demo</a> and <a href="https://github.com/garciadiazjaime/demo-reactjs/blob/main/app/react-todo-aws-mysql/page.tsx">Codebase</a>.</p>

<h2 id="front-end-architecture">Front-End Architecture</h2>

<p><img src="/assets/react-todo-aws-mysql/architecture.jpeg" alt="Todo List Architecture" /></p>

<p>The application can perform six main functions:</p>

<ul>
  <li>Add Todos</li>
  <li>Edit a Single Todo</li>
  <li>Delete a Single Todo</li>
  <li>Get all Todos</li>
  <li>Delete all Todos</li>
</ul>

<p>As a good practice for client applications, all user interactions are first stored in the browser — in this case, in a React state variable — and then synced with the database in the background.</p>

<p>The downside of this approach is the risk of a race condition. For example, if the user closes the browser before the todo is saved in the database, the information will be lost. The upside is that the user doesn’t need to wait for backend confirmation, which makes the app feel more responsive. And since users typically don’t close the app immediately, this trade-off works fine for most cases.</p>

<p>For this demo, the goal is not to cover 100% of scenarios but to show the overall approach.</p>

<h2 id="snippets">Snippets</h2>

<p>In the codebase you will find things like this:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">newTodos</span> <span class="o">=</span> <span class="nx">textareaValue</span>
  <span class="p">.</span><span class="nx">split</span><span class="p">(</span><span class="dl">"</span><span class="se">\n</span><span class="dl">"</span><span class="p">)</span>
  <span class="p">.</span><span class="nx">filter</span><span class="p">((</span><span class="nx">line</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">line</span><span class="p">.</span><span class="nx">trim</span><span class="p">()</span> <span class="o">!==</span> <span class="dl">""</span><span class="p">)</span>
  <span class="p">.</span><span class="nx">map</span><span class="p">((</span><span class="nx">line</span><span class="p">,</span> <span class="nx">index</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">({</span>
    <span class="na">id</span><span class="p">:</span> <span class="nb">Date</span><span class="p">.</span><span class="nx">now</span><span class="p">()</span> <span class="o">+</span> <span class="nx">index</span><span class="p">,</span>
    <span class="na">text</span><span class="p">:</span> <span class="nx">line</span><span class="p">.</span><span class="nx">trim</span><span class="p">().</span><span class="nx">toLocaleLowerCase</span><span class="p">(),</span>
    <span class="na">done</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
  <span class="p">}));</span>

<span class="nx">setTodos</span><span class="p">(</span><span class="nx">newTodos</span><span class="p">);</span>

<span class="k">await</span> <span class="nx">addTodosToDB</span><span class="p">(</span><span class="nx">newTodos</span><span class="p">);</span>
</code></pre></div></div>

<p>Notice how the React state variable <code class="language-plaintext highlighter-rouge">todos</code> is updated. This triggers a change in the UI, allowing the user to see the new todos in the list. Meanwhile, in the background, <code class="language-plaintext highlighter-rouge">await addTodosToDB(newTodos)</code> is executed to save the <code class="language-plaintext highlighter-rouge">todos</code> in the database.</p>

<p>Let’s take a look at the function <a href="https://github.com/garciadiazjaime/demo-reactjs/blob/main/app/react-todo-aws-mysql/todoActions.ts#L31">addTodosToDB</a>:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="dl">"</span><span class="s2">use server</span><span class="dl">"</span><span class="p">;</span>

<span class="p">...</span>

<span class="k">export</span> <span class="k">async</span> <span class="kd">function</span> <span class="nx">addTodosToDB</span><span class="p">(</span>
  <span class="nx">todos</span><span class="p">:</span> <span class="p">{</span> <span class="nl">id</span><span class="p">:</span> <span class="nx">number</span><span class="p">,</span> <span class="nx">text</span><span class="p">:</span> <span class="nx">string</span><span class="p">,</span> <span class="nx">done</span><span class="p">:</span> <span class="nx">boolean</span> <span class="p">}[]</span>
<span class="p">)</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">values</span> <span class="o">=</span> <span class="nx">todos</span><span class="p">.</span><span class="nx">map</span><span class="p">((</span><span class="nx">todo</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">[</span><span class="nx">todo</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span> <span class="nx">todo</span><span class="p">.</span><span class="nx">text</span><span class="p">,</span> <span class="nx">todo</span><span class="p">.</span><span class="nx">done</span><span class="p">,</span> <span class="nx">LIST</span><span class="p">]);</span>
  <span class="k">await</span> <span class="nx">db</span><span class="p">.</span><span class="nx">query</span><span class="p">(</span><span class="dl">"</span><span class="s2">INSERT INTO todos (id, text, done, list) VALUES ?</span><span class="dl">"</span><span class="p">,</span> <span class="p">[</span><span class="nx">values</span><span class="p">]);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Here, you’ll notice <code class="language-plaintext highlighter-rouge">"use server";</code> at the top of the file. This tells Next.js that the file should be executed on the server as a <a href="https://www.garciadiazjaime.com/posts/react-19-server-function">server action</a>.</p>

<p>Since the code runs on the server, it’s fine to interact directly with the database—for example, with a raw MySQL insert query.</p>

<blockquote>
  <p>In a production application, you’ll usually want to use an ORM when working with a database. ORM packages typically provide features such as migrations, validation, schemas, and more.</p>
</blockquote>

<p>I won’t go into the rest of the code since it’s fairly self-explanatory. Also, most of it was generated with the help of an AI agent. Here are some of the prompts I used:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Create a react component for a todo list application that allows users to add, edit, delete, and mark todos as done. The todos should be fetched from a database, and changes should be synced back to the database. Include a textarea for bulk adding todos and a reset button to clear the list.
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Add functionality to a todo list component where pressing 'enter' adds a new todo, 'shift + enter' allows multiline input, and 'escape' clears the textarea. Include buttons for toggling the 'done' state, editing todos inline, and deleting todos. Ensure the UI updates dynamically and is synced with a backend database. Style the component with inline styles for a clean and modern look.
</code></pre></div></div>

<h2 id="terraform-database">Terraform (Database)</h2>

<p>This is the part I’m most interested in. As a web developer, it’s pretty easy to build a web application—especially now with AI agents—but deploying it is not always as straightforward. There are some cool <a href="https://www.garciadiazjaime.com/posts/web-application-free-hosting">services that let you host applications for free</a>, and I definitely recommend them. However, in most work environments you’ll run into AWS, especially for databases.</p>

<blockquote>
  <p>The following steps will create a MySQL database in AWS and add a network so the database can be accessed from the application.</p>
</blockquote>

<p>Here’s what you’ll need:</p>

<ol>
  <li>An AWS account (credit card required)</li>
  <li>Access Keys, which consist of a Key ID and a Secret Access Key</li>
</ol>

<p><img src="/assets/react-todo-aws-mysql/aws-key-access.png" alt="AWS Key ID and Secret Access Key" /></p>

<ol>
  <li><a href="https://developer.hashicorp.com/terraform/tutorials/aws-get-started/install-cli">Terraform</a> installed</li>
</ol>

<p>These first three steps might take a bit of time, but once you have them, the next step is:</p>

<ol>
  <li><strong>AWS Profile</strong></li>
</ol>

<p>Not required, but it’s a good way to manage your credentials in your local environment.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>aws configure <span class="nt">--profile</span> todo-profile

AWS Access Key ID <span class="o">[</span><span class="k">****************</span><span class="o">]</span>: <span class="k">****************</span>
AWS Secret Access Key <span class="o">[</span><span class="k">****************</span><span class="o">]</span>: <span class="k">****************</span>
Default region name <span class="o">[</span>None]:
Default output format <span class="o">[</span>None]:
</code></pre></div></div>

<ol>
  <li>Environment Variables</li>
</ol>

<p>You’ll need to <code class="language-plaintext highlighter-rouge">export</code> the following variables in your terminal:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">export </span><span class="nv">TF_VAR_mysql_database</span><span class="o">=</span><span class="s2">"*****"</span>
<span class="nb">export </span><span class="nv">TF_VAR_mysql_user</span><span class="o">=</span><span class="k">*****</span>
<span class="nb">export </span><span class="nv">TF_VAR_mysql_pwd</span><span class="o">=</span><span class="s2">"*****"</span>
</code></pre></div></div>

<p><strong>Note: The values are whatever string you want.</strong></p>

<p>In the Terraform <code class="language-plaintext highlighter-rouge">rds</code> file, these variables are used like this:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>db_name                 <span class="o">=</span> var.mysql_database  <span class="c"># Pull from environment variable</span>
username                <span class="o">=</span> var.mysql_user
password                <span class="o">=</span> var.mysql_pwd
</code></pre></div></div>

<p>Terraform automatically picks up any environment variable that’s prefixed with <code class="language-plaintext highlighter-rouge">TF_VAR_</code> and will map these to the corresponding variables defined in your <code class="language-plaintext highlighter-rouge">variables.tf</code> file.</p>

<ol>
  <li>Init</li>
</ol>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>terraform init

Initializing the backend...
Initializing provider plugins...
- Reusing previous version of hashicorp/aws from the dependency lock file
- Reusing previous version of hashicorp/null from the dependency lock file
- Using previously-installed hashicorp/aws v6.13.0
- Using previously-installed hashicorp/null v3.2.4

Terraform has been successfully initialized!

...
</code></pre></div></div>

<ol>
  <li>Plan</li>
</ol>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>terraform plan

data.aws_availability_zones.available: Reading...
data.aws_availability_zones.available: Read <span class="nb">complete </span>after 0s <span class="o">[</span><span class="nb">id</span><span class="o">=</span>us-east-1]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  <span class="c"># aws_db_instance.todo_db will be created</span>
  + resource <span class="s2">"aws_db_instance"</span> <span class="s2">"todo_db"</span> <span class="o">{</span>
      + address                               <span class="o">=</span> <span class="o">(</span>known after apply<span class="o">)</span>
      + allocated_storage                     <span class="o">=</span> 20
...

Plan: 12 to add, 0 to change, 0 to destroy.
</code></pre></div></div>

<ol>
  <li>Apply</li>
</ol>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>terraform apply
data.aws_availability_zones.available: Reading...

...

Plan: 12 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only <span class="s1">'yes'</span> will be accepted to approve.

  Enter a value: <span class="nb">yes

</span>aws_vpc.todo_vpc: Creating...
aws_vpc.todo_vpc: Still creating... <span class="o">[</span>10s elapsed]
aws_vpc.todo_vpc: Creation <span class="nb">complete </span>after 11s <span class="o">[</span><span class="nb">id</span><span class="o">=</span>vpc-095daafd54946d1c0]

...

null_resource.init_db <span class="o">(</span>local-exec<span class="o">)</span>: host: <span class="k">*****</span>

...

Apply <span class="nb">complete</span><span class="o">!</span> Resources: 12 added, 0 changed, 0 destroyed.
</code></pre></div></div>

<hr />

<p>Notice the line above that shows the <code class="language-plaintext highlighter-rouge">host</code> URL:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>null_resource.init_db <span class="o">(</span>local-exec<span class="o">)</span>: host: <span class="k">*****</span>
</code></pre></div></div>

<p>This is important because the application will need it. You can also use it to connect directly to your database.</p>

<p>Just type your password when prompted, and you should be inside AWS.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mysql <span class="nt">-h</span> <span class="o">[</span>host] <span class="nt">-P</span> 3306 <span class="nt">-u</span> <span class="o">[</span>user] <span class="nt">-p</span>
Enter password:
Welcome to the MySQL monitor.

mysql&gt; show databases<span class="p">;</span>
+--------------------+
| Database           |
+--------------------+
| todo_database      |
+--------------------+

mysql&gt; use todo_database<span class="p">;</span>
Database changed

mysql&gt; show tables<span class="p">;</span>
+-------------------------+
| Tables_in_todo_database |
+-------------------------+
| todos                   |
+-------------------------+
</code></pre></div></div>

<p>Perfect! You can also view your MySQL database in the AWS UI.</p>

<p><img src="/assets/react-todo-aws-mysql/aws-mysql-database.png" alt="AWS RDS Database MySQL" /></p>

<h2 id="webapp-database-credentials">Webapp Database Credentials</h2>

<p>This is how the application receives its credentials:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">db</span> <span class="o">=</span> <span class="nx">mysql</span><span class="p">.</span><span class="nx">createPool</span><span class="p">({</span>
  <span class="na">host</span><span class="p">:</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">TF_VAR_mysql_host</span><span class="p">,</span>
  <span class="na">user</span><span class="p">:</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">TF_VAR_mysql_user</span><span class="p">,</span>
  <span class="na">password</span><span class="p">:</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">TF_VAR_mysql_pwd</span><span class="p">,</span>
  <span class="na">database</span><span class="p">:</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">TF_VAR_mysql_database</span><span class="p">,</span>
<span class="p">});</span>
</code></pre></div></div>

<p>I added one extra variable, <code class="language-plaintext highlighter-rouge">TF_VAR_mysql_host</code>, whose value you get after applying the Terraform scripts.</p>

<h2 id="costs">Costs</h2>

<p>Using AWS is usually not free. Here’s the cost of running this small database for almost a month:</p>

<p><img src="/assets/react-todo-aws-mysql/aws-mysql-costs.png" alt="AWS Relational Database Service and Virtual Private Cloud Cost" /></p>

<p>The Virtual Private Cloud (VPC) provides access via HTTP to the database, which is necessary for connecting the web application to it.</p>

<p>One nice thing about using <code class="language-plaintext highlighter-rouge">Terraform</code> is that you can remove everything from AWS just by running:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>terraform destroy

data.aws_availability_zones.available: Reading...
...

Plan: 0 to add, 0 to change, 12 to destroy.

Do you really want to destroy all resources?
  Terraform will destroy all your managed infrastructure, as shown above.
  There is no undo. Only <span class="s1">'yes'</span> will be accepted to confirm.

  Enter a value: <span class="nb">yes

</span>null_resource.init_db: Destroying... <span class="o">[</span><span class="nb">id</span><span class="o">=</span>5959882105264964999]
...

Destroy <span class="nb">complete</span><span class="o">!</span> Resources: 12 destroyed.
</code></pre></div></div>

<p>By destroying everything, you stop AWS from charging you—especially if it’s just for testing. When working for a company, it’s also a good practice to remove unused resources to reduce costs.</p>

<h2 id="final-thoughts">Final Thoughts</h2>

<p>As a web developer, at some point you’ll likely need a database. In a work environment, this usually means setting one up in AWS. Feel free to experiment with the demo: the Next.js application has both a client side and a server side. The server side (server actions) is integrated directly with the MySQL database hosted in AWS.</p>]]></content><author><name></name></author><category term="nextjs" /><category term="aws" /><category term="mysql" /><category term="terraform" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">NextAuth.js: An easy way to add authentication to your Next.js project</title><link href="/posts/nextjs-auth-session-login" rel="alternate" type="text/html" title="NextAuth.js: An easy way to add authentication to your Next.js project" /><published>2025-06-09T15:00:00+00:00</published><updated>2025-06-09T15:00:00+00:00</updated><id>/posts/nextjs-auth-session-login</id><content type="html" xml:base="/posts/nextjs-auth-session-login"><![CDATA[<p><img src="/assets/nextjs-auth-session-login/banner.png" alt="NextAuth.js: An easy way to add authentication to your Next.js project" /></p>

<p>Recently, I needed to add session management to a Next.js project and came across <a href="https://next-auth.js.org/">NextAuth.js</a>. It’s a pretty solid library that simplifies login, logout, and session handling.</p>

<p>Here are the steps I followed:</p>

<h2 id="1-api-auth-route-handler">1. API Auth Route Handler</h2>

<p>First, create the following file:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>api/auth/[...nextauth]/route.ts
</code></pre></div></div>

<p>And add this content: (you can customize it based on your provider and config needs)</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="nx">NextAuth</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">next-auth</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">CredentialsProvider</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">next-auth/providers/credentials</span><span class="dl">"</span><span class="p">;</span>

<span class="nx">type</span> <span class="nx">UserToken</span> <span class="o">=</span> <span class="p">{</span> <span class="na">id</span><span class="p">:</span> <span class="nx">string</span><span class="p">;</span> <span class="nl">name</span><span class="p">:</span> <span class="nx">string</span><span class="p">;</span> <span class="nl">email</span><span class="p">:</span> <span class="nx">string</span> <span class="p">};</span>

<span class="kd">const</span> <span class="nx">handler</span> <span class="o">=</span> <span class="nx">NextAuth</span><span class="p">({</span>
  <span class="na">providers</span><span class="p">:</span> <span class="p">[</span>
    <span class="nx">CredentialsProvider</span><span class="p">({</span>
      <span class="na">name</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Credentials</span><span class="dl">"</span><span class="p">,</span>
      <span class="na">credentials</span><span class="p">:</span> <span class="p">{</span>
        <span class="na">username</span><span class="p">:</span> <span class="p">{</span> <span class="na">label</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Username</span><span class="dl">"</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">text</span><span class="dl">"</span><span class="p">,</span> <span class="na">placeholder</span><span class="p">:</span> <span class="dl">"</span><span class="s2">admin</span><span class="dl">"</span> <span class="p">},</span>
        <span class="na">password</span><span class="p">:</span> <span class="p">{</span>
          <span class="na">label</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Password</span><span class="dl">"</span><span class="p">,</span>
          <span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">password</span><span class="dl">"</span><span class="p">,</span>
          <span class="na">placeholder</span><span class="p">:</span> <span class="dl">"</span><span class="s2">1234</span><span class="dl">"</span><span class="p">,</span>
        <span class="p">},</span>
      <span class="p">},</span>
      <span class="k">async</span> <span class="nx">authorize</span><span class="p">(</span><span class="nx">credentials</span><span class="p">,</span> <span class="nx">req</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">return</span> <span class="p">{</span> <span class="na">id</span><span class="p">:</span> <span class="dl">"</span><span class="s2">1</span><span class="dl">"</span><span class="p">,</span> <span class="na">name</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Admin</span><span class="dl">"</span><span class="p">,</span> <span class="na">email</span><span class="p">:</span> <span class="dl">"</span><span class="s2">admin@example.com</span><span class="dl">"</span> <span class="p">};</span>
      <span class="p">},</span>
    <span class="p">}),</span>
  <span class="p">],</span>
  <span class="na">secret</span><span class="p">:</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">NEXTAUTH_SECRET</span><span class="p">,</span>
  <span class="na">callbacks</span><span class="p">:</span> <span class="p">{</span>
    <span class="k">async</span> <span class="nx">jwt</span><span class="p">({</span> <span class="nx">token</span><span class="p">,</span> <span class="nx">user</span> <span class="p">})</span> <span class="p">{</span>
      <span class="k">if</span> <span class="p">(</span><span class="nx">user</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">token</span><span class="p">.</span><span class="nx">user</span> <span class="o">=</span> <span class="nx">user</span><span class="p">;</span>
      <span class="p">}</span>
      <span class="k">return</span> <span class="nx">token</span><span class="p">;</span>
    <span class="p">},</span>
    <span class="k">async</span> <span class="nx">session</span><span class="p">({</span> <span class="nx">session</span><span class="p">,</span> <span class="nx">token</span> <span class="p">})</span> <span class="p">{</span>
      <span class="nx">session</span><span class="p">.</span><span class="nx">user</span> <span class="o">=</span> <span class="nx">token</span><span class="p">.</span><span class="nx">user</span> <span class="k">as</span> <span class="nx">UserToken</span><span class="p">;</span>
      <span class="k">return</span> <span class="nx">session</span><span class="p">;</span>
    <span class="p">},</span>
  <span class="p">},</span>
<span class="p">});</span>

<span class="k">export</span> <span class="p">{</span> <span class="nx">handler</span> <span class="k">as</span> <span class="nx">GET</span><span class="p">,</span> <span class="nx">handler</span> <span class="k">as</span> <span class="nx">POST</span> <span class="p">};</span>
</code></pre></div></div>

<p>This is the main configuration file, and it defines a few key things:</p>

<ul>
  <li>
    <p>Provider</p>

    <p><code class="language-plaintext highlighter-rouge">NextAuth.js</code> supports multiple providers. For this simple demo, I’m using Credentials, which is super handy when you already have a login system in place. It basically lets you plug directly into your existing auth service.</p>
  </li>
  <li>
    <p>authorize</p>

    <p>This function is where you hook into your internal login service. If it returns an object, <code class="language-plaintext highlighter-rouge">NextAuth.js</code> treats it as a successful login and creates a session. If you return null, it means login failed.</p>
  </li>
</ul>

<blockquote>
  <p>Note: providers is an array—so you can support multiple login methods, like <code class="language-plaintext highlighter-rouge">Credentials</code> (custom login), <code class="language-plaintext highlighter-rouge">Google</code>, <code class="language-plaintext highlighter-rouge">GitHub</code>, etc.</p>
</blockquote>

<ul>
  <li>
    <p>JWT</p>

    <p>This callback attaches the user data returned by <code class="language-plaintext highlighter-rouge">authorize</code> to the token. You can include whatever you want inside the token here. That token will later be used to build the session.</p>
  </li>
  <li>
    <p>session</p>

    <p>This is where the session is created using the token. You’ll see how the user info gets added to the session object. This is what your app can access using the <code class="language-plaintext highlighter-rouge">useSession()</code> hook on the client side.</p>
  </li>
</ul>

<p>For this demo, I’m not calling any real auth service. Just passing a user and password is enough to simulate a session. But in a real-world scenario, you’d call your actual login API inside <code class="language-plaintext highlighter-rouge">authorize</code>, and then return the user object (or <code class="language-plaintext highlighter-rouge">null</code> on failure).</p>

<h2 id="2-ui-updates">2. UI Updates</h2>

<p>Now that the provider setup is done, the next step is to implement the UI components. In this case, I only have two components:</p>

<ul>
  <li>
    <p>Page</p>

    <p>A very simple page that uses <code class="language-plaintext highlighter-rouge">'use client'</code> (required for client-side hooks like <code class="language-plaintext highlighter-rouge">useSession</code>) and wraps everything in <code class="language-plaintext highlighter-rouge">SessionProvider</code>:</p>
  </li>
</ul>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="dl">"</span><span class="s2">use client</span><span class="dl">"</span><span class="p">;</span>

<span class="k">import</span> <span class="p">{</span> <span class="nx">SessionProvider</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">next-auth/react</span><span class="dl">"</span><span class="p">;</span>

<span class="k">import</span> <span class="nx">LoginButton</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./login-btn</span><span class="dl">"</span><span class="p">;</span>

<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nx">Page</span><span class="p">()</span> <span class="p">{</span>
  <span class="k">return</span> <span class="p">(</span>
    <span class="o">&lt;</span><span class="nx">SessionProvider</span><span class="o">&gt;</span>
      <span class="o">&lt;</span><span class="nx">LoginButton</span> <span class="o">/&gt;</span>
    <span class="o">&lt;</span><span class="sr">/SessionProvider</span><span class="err">&gt;
</span>  <span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<blockquote>
  <p>Note: The <code class="language-plaintext highlighter-rouge">LoginButton</code> component uses <code class="language-plaintext highlighter-rouge">useSession</code>, so it needs to be inside a <code class="language-plaintext highlighter-rouge">SessionProvider</code>.</p>
</blockquote>

<ul>
  <li>
    <p>LoginButton</p>

    <p>A very simple LoginButton component that uses <code class="language-plaintext highlighter-rouge">useSession</code> to either show a <code class="language-plaintext highlighter-rouge">Sign In</code> or <code class="language-plaintext highlighter-rouge">Sign Out</code> button depending on the session state:</p>
  </li>
</ul>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">useSession</span><span class="p">,</span> <span class="nx">signIn</span><span class="p">,</span> <span class="nx">signOut</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">next-auth/react</span><span class="dl">"</span><span class="p">;</span>

<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nx">Component</span><span class="p">()</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="p">{</span> <span class="na">data</span><span class="p">:</span> <span class="nx">session</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">useSession</span><span class="p">();</span>

  <span class="k">if</span> <span class="p">(</span><span class="nx">session</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="p">(</span>
      <span class="o">&lt;</span><span class="nx">div</span><span class="o">&gt;</span>
        <span class="o">&lt;</span><span class="nx">p</span><span class="o">&gt;</span>
          <span class="nx">Signed</span> <span class="k">in</span> <span class="k">as</span> <span class="o">&lt;</span><span class="nx">strong</span><span class="o">&gt;</span><span class="p">{</span><span class="nx">session</span><span class="p">.</span><span class="nx">user</span><span class="p">.</span><span class="nx">name</span><span class="p">}</span><span class="o">&lt;</span><span class="sr">/strong</span><span class="err">&gt;
</span>        <span class="o">&lt;</span><span class="sr">/p</span><span class="err">&gt;
</span>        <span class="o">&lt;</span><span class="nx">button</span> <span class="nx">onClick</span><span class="o">=</span><span class="p">{()</span> <span class="o">=&gt;</span> <span class="nx">signOut</span><span class="p">()}</span><span class="o">&gt;</span><span class="nx">Sign</span> <span class="nx">out</span><span class="o">&lt;</span><span class="sr">/button</span><span class="err">&gt;
</span>      <span class="o">&lt;</span><span class="sr">/div</span><span class="err">&gt;
</span>    <span class="p">);</span>
  <span class="p">}</span>

  <span class="k">return</span> <span class="p">(</span>
    <span class="o">&lt;</span><span class="nx">div</span><span class="o">&gt;</span>
      <span class="o">&lt;</span><span class="nx">p</span><span class="o">&gt;</span><span class="nx">Not</span> <span class="nx">signed</span> <span class="k">in</span><span class="o">&lt;</span><span class="sr">/p</span><span class="err">&gt;
</span>      <span class="o">&lt;</span><span class="nx">button</span> <span class="nx">onClick</span><span class="o">=</span><span class="p">{()</span> <span class="o">=&gt;</span> <span class="nx">signIn</span><span class="p">()}</span><span class="o">&gt;</span><span class="nx">Sign</span> <span class="k">in</span><span class="o">&lt;</span><span class="sr">/button</span><span class="err">&gt;
</span>    <span class="o">&lt;</span><span class="sr">/div</span><span class="err">&gt;
</span>  <span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>And that’s it—you’ve got a simple login system in place using NextAuth.js</p>

<p><img src="/assets/nextjs-auth-session-login/sign_in.png" alt="Sign In Using NextAuth.js" /></p>

<p><img src="/assets/nextjs-auth-session-login/login_form.png" alt="Default Login Form" /></p>

<p><img src="/assets/nextjs-auth-session-login/sign_out.png" alt="Sign Out Using NextAuth.js" /></p>

<h2 id="demo">Demo</h2>

<p>Make sure to check out the <a href="https://demo.garciadiazjaime.com/nextjs-next-auth-login">Website</a> and take a look at the <a href="https://github.com/garciadiazjaime/demo-reactjs/blob/main/app/api/auth/%5B...nextauth%5D/route.ts">Codebase</a>.</p>

<p>Thanks for reading!</p>]]></content><author><name></name></author><category term="nextjs" /><category term="react" /><category term="nextAuth.js" /><category term="authentication" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">How to Connect Your Next.js React Application to Redis</title><link href="/posts/react-server-action-redis" rel="alternate" type="text/html" title="How to Connect Your Next.js React Application to Redis" /><published>2025-04-14T15:00:00+00:00</published><updated>2025-04-14T15:00:00+00:00</updated><id>/posts/react-server-action-redis</id><content type="html" xml:base="/posts/react-server-action-redis"><![CDATA[<p><img src="/assets/react-server-action-redis/banner.png" alt="How to Connect Your Next.js React Application to Redis" /></p>

<p>Recently, I had to add Redis to a React application running on Next.js using server actions. Here are the important pieces.</p>

<ol>
  <li>Select an npm package</li>
</ol>

<p>I went with <a href="https://www.npmjs.com/package/ioredis">ioredis</a>. At the time of writing, it had 7 million downloads, and after opening their repo, the last commit was from two days ago. This means the project is active.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm i ioredis
</code></pre></div></div>

<ol>
  <li>Initialize a redis client</li>
</ol>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">redis</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Redis</span><span class="p">(</span><span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">REDIS_CONNECTION_STRING</span><span class="p">);</span>
</code></pre></div></div>

<p>If another team takes care of the infrastructure they will provide a “connection string”, that looks something like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rediss://[user]:[password]@[host]:[port]
</code></pre></div></div>

<p>For this demo I’m using <a href="https://aiven.io/">Aiven</a>, they offer a 1GB free account, which is more than enough for a POC or prototype.</p>

<ol>
  <li>Read from Redis</li>
</ol>

<p>In the previous step, if all went well: valid credentials, Redis server running, correct configurations; a Redis client instance should have been created. This means reading is as easy as running the following lines:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="k">async</span> <span class="kd">function</span> <span class="nx">readFromRedis</span><span class="p">()</span> <span class="p">{</span>
  <span class="k">try</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">value</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">redis</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">"</span><span class="s2">name</span><span class="dl">"</span><span class="p">);</span>
    <span class="k">return</span> <span class="nx">value</span><span class="p">;</span>
  <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="dl">"</span><span class="s2">Error reading from Redis:</span><span class="dl">"</span><span class="p">,</span> <span class="nx">error</span><span class="p">);</span>
    <span class="k">return</span> <span class="dl">""</span><span class="p">;</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Note: In my case, <code class="language-plaintext highlighter-rouge">name</code> is the key I’ll use to save data into Redis.</p>

<ol>
  <li>Write to Redis</li>
</ol>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="k">async</span> <span class="kd">function</span> <span class="nx">writeToRedis</span><span class="p">(</span><span class="nx">name</span><span class="p">:</span> <span class="nx">string</span><span class="p">):</span> <span class="nb">Promise</span><span class="o">&lt;</span><span class="k">void</span><span class="o">&gt;</span> <span class="p">{</span>
  <span class="k">try</span> <span class="p">{</span>
    <span class="k">await</span> <span class="nx">redis</span><span class="p">.</span><span class="kd">set</span><span class="p">(</span><span class="dl">"</span><span class="s2">name</span><span class="dl">"</span><span class="p">,</span> <span class="nx">name</span><span class="p">);</span>
  <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="dl">"</span><span class="s2">Error writing to Redis:</span><span class="dl">"</span><span class="p">,</span> <span class="nx">error</span><span class="p">);</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Notice how the key is <code class="language-plaintext highlighter-rouge">name</code></p>

<ol>
  <li>Ensure helpers are run on the server</li>
</ol>

<p>Is important to run these helpers in the server so the credentials are not exposed in the browser. Make sure to use <code class="language-plaintext highlighter-rouge">"use server";</code>. Take a look at the <a href="https://github.com/garciadiazjaime/demo-reactjs/blob/main/app/react-server-action-redis/actions.ts">example</a>.</p>

<p>Notice how I’m using an environment variable to store the Redis connection string: <code class="language-plaintext highlighter-rouge">process.env.REDIS_CONNECTION_STRING</code>. This information is sensitive and shouldn’t be exposed in the browser.</p>

<p>This adds an extra jump from the browser to your server to the Redis server, but it is a secure way. There’s an alternative to securely connect from the browser, but I would probably still go with a server function.</p>

<ol>
  <li>Finally, plug the methods into your React application.</li>
</ol>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="p">[</span><span class="nx">inputValue</span><span class="p">,</span> <span class="nx">setInputValue</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="p">(</span><span class="dl">""</span><span class="p">);</span>
<span class="kd">const</span> <span class="p">[</span><span class="nx">name</span><span class="p">,</span> <span class="nx">setName</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="p">(</span><span class="dl">""</span><span class="p">);</span>

<span class="kd">const</span> <span class="nx">handleButtonClick</span> <span class="o">=</span> <span class="k">async</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="k">await</span> <span class="nx">writeToRedis</span><span class="p">(</span><span class="nx">inputValue</span><span class="p">);</span>
  <span class="nx">setInputValue</span><span class="p">(</span><span class="dl">""</span><span class="p">);</span>
  <span class="k">await</span> <span class="nx">fetchName</span><span class="p">();</span>
<span class="p">};</span>

<span class="kd">const</span> <span class="nx">fetchName</span> <span class="o">=</span> <span class="k">async</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">nameFromRedis</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">readFromRedis</span><span class="p">();</span>
  <span class="nx">setName</span><span class="p">(</span><span class="nx">nameFromRedis</span><span class="p">);</span>
<span class="p">};</span>

<span class="nx">useEffect</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="nx">fetchName</span><span class="p">();</span>
<span class="p">},</span> <span class="p">[]);</span>
</code></pre></div></div>

<p>Take a look at the full <a href="https://github.com/garciadiazjaime/demo-reactjs/blob/main/app/react-server-action-redis/page.tsx">component</a>.</p>

<h2 id="demo">Demo</h2>

<p>Make sure to take a look at the <a href="https://demo.garciadiazjaime.com/react-server-action-redis">demo page</a></p>

<h2 id="conclusion">Conclusion</h2>

<p>Adding Redis is pretty straightforward, especially if you have a team that takes care of the infrastructure. If not, you might need to spend a bit of time with AWS or any other provider, making sure you set up the Redis server properly and follow secure guidelines.</p>

<p>Once the Redis server is set up, plugging it into your React application is just about selecting an npm package that works for you. These packages do the heavy lifting and provide helpers to write and read, which is what you will need most of the time.</p>

<p>Thanks for reading.</p>]]></content><author><name></name></author><category term="nextjs" /><category term="react" /><category term="redis" /><category term="javascript" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">How JavaScript helped me find a Car</title><link href="/posts/nextjs-javascript-etl-jetta-chicago" rel="alternate" type="text/html" title="How JavaScript helped me find a Car" /><published>2025-04-14T15:00:00+00:00</published><updated>2025-04-14T15:00:00+00:00</updated><id>/posts/nextjs-javascript-etl-jetta-chicago</id><content type="html" xml:base="/posts/nextjs-javascript-etl-jetta-chicago"><![CDATA[<p><img src="/assets/nextjs-javascript-etl-jetta-chicago/banner.png" alt="How JavaScript helped me find a Car" /></p>

<p>I’m looking for a Jetta, and of course, I want to make sure I get the best option. Instead of manually checking car dealers’ websites, I’m writing ETLs to collect car data, save it as JSON, and use a Next.js app to display the results.</p>

<h2 id="architecture">Architecture</h2>

<p><img src="/assets/nextjs-javascript-etl-jetta-chicago/architecture.png" alt="Architecture" /></p>

<p>I’m going to write one ETL (Extract, Transform, Load) per website (source).</p>

<h3 id="extract">Extract</h3>

<p>This step involves opening the website and retrieving the data. Sometimes it’s in HTML, sometimes in JSON.</p>

<h3 id="transform">Transform</h3>

<p>I’m interested in the following properties:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>title
price
year
link
vin
mileage
</code></pre></div></div>

<p>Each website may have a different structure, but the goal is to extract these values.</p>

<h3 id="load">Load</h3>

<p>For this demo, I’m keeping it simple by saving a JSON file per site. In a real-world setup, the data would be stored in a database.</p>

<h3 id="aggregator">Aggregator</h3>

<p>This script will combine all the JSON files into a single one, simulating what a database would do in a real example.</p>

<h3 id="nextjs">Next.js</h3>

<p>A basic web app to display the data in a table-like format with simple filters.</p>

<h2 id="example">Example</h2>

<p>Let’s take a look at the following dealer’s website:</p>

<p><img src="/assets/nextjs-javascript-etl-jetta-chicago/jetta-car-dealer-website.png" alt="Jetta Dealership Website" /></p>

<p>Notice how the website displays the properties I’m interested in. Fortunately, at the time of writing, this site returns a JSON response, which makes it easy to traverse and extract car data. The ETL looks like this:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">fs</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">fs</span><span class="dl">"</span><span class="p">);</span>

<span class="kd">const</span> <span class="nx">extract</span> <span class="o">=</span> <span class="k">async</span> <span class="p">(</span><span class="nx">url</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">response</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">fetch</span><span class="p">(</span><span class="nx">url</span><span class="p">);</span>

  <span class="k">return</span> <span class="k">await</span> <span class="nx">response</span><span class="p">.</span><span class="nx">json</span><span class="p">();</span>
<span class="p">};</span>

<span class="kd">const</span> <span class="nx">transform</span> <span class="o">=</span> <span class="p">(</span><span class="nx">data</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="k">return</span> <span class="nx">data</span><span class="p">.</span><span class="nx">DisplayCards</span><span class="p">.</span><span class="nx">map</span><span class="p">((</span><span class="nx">VehicleCard</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="k">return</span> <span class="p">{</span>
      <span class="na">title</span><span class="p">:</span> <span class="nx">VehicleCard</span><span class="p">.</span><span class="nx">VehicleName</span><span class="p">,</span>
      <span class="na">price</span><span class="p">:</span> <span class="nx">VehicleCard</span><span class="p">.</span><span class="nx">VehicleInternetPrice</span><span class="p">,</span>
      <span class="na">year</span><span class="p">:</span> <span class="nx">VehicleCard</span><span class="p">.</span><span class="nx">VehicleYear</span><span class="p">,</span>
      <span class="na">link</span><span class="p">:</span> <span class="nx">VehicleCard</span><span class="p">.</span><span class="nx">VehicleDetailUrl</span><span class="p">,</span>
      <span class="na">vin</span><span class="p">:</span> <span class="nx">VehicleCard</span><span class="p">.</span><span class="nx">VehicleVin</span><span class="p">,</span>
      <span class="na">mileage</span><span class="p">:</span> <span class="nx">VehicleCard</span><span class="p">.</span><span class="nx">Mileage</span><span class="p">,</span>
    <span class="p">};</span>
  <span class="p">});</span>
<span class="p">};</span>

<span class="kd">const</span> <span class="nx">load</span> <span class="o">=</span> <span class="p">(</span><span class="nx">cars</span><span class="p">,</span> <span class="nx">name</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="nx">fs</span><span class="p">.</span><span class="nx">writeFileSync</span><span class="p">(</span><span class="s2">`./_sites/</span><span class="p">${</span><span class="nx">name</span><span class="p">}</span><span class="s2">.json`</span><span class="p">,</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">cars</span><span class="p">));</span>
<span class="p">};</span>

<span class="kd">const</span> <span class="nx">main</span> <span class="o">=</span> <span class="k">async</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">site</span> <span class="o">=</span> <span class="p">{</span>
    <span class="na">url</span><span class="p">:</span> <span class="dl">"</span><span class="s2">https://www.vwoaklawn.com/api/vhcliaa/vehicle-pages/cosmos/srp/vehicles/25795/2631261?st=Price+asc&amp;Make=Volkswagen&amp;mileagerange=0-50000&amp;host=www.vwoaklawn.com&amp;baseFilter=dHlwZT0ndSc=&amp;displayCardsShown=NaN</span><span class="dl">"</span><span class="p">,</span>
  <span class="p">};</span>

  <span class="kd">const</span> <span class="nx">data</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">extract</span><span class="p">(</span><span class="nx">site</span><span class="p">.</span><span class="nx">url</span><span class="p">);</span>

  <span class="kd">const</span> <span class="nx">cars</span> <span class="o">=</span> <span class="nx">transform</span><span class="p">(</span><span class="nx">data</span><span class="p">);</span>

  <span class="nx">load</span><span class="p">(</span><span class="nx">cars</span><span class="p">,</span> <span class="dl">"</span><span class="s2">vwoaklawn</span><span class="dl">"</span><span class="p">);</span>
<span class="p">};</span>
</code></pre></div></div>

<h2 id="site_ajson">Site_A.json</h2>

<p>After running the ETL, I’ll get a JSON file like this:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="w">
    </span><span class="p">{</span><span class="w">
        </span><span class="nl">"title"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Volkswagen Jetta 1.4T SE"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"price"</span><span class="p">:</span><span class="w"> </span><span class="mi">19640</span><span class="p">,</span><span class="w">
        </span><span class="nl">"year"</span><span class="p">:</span><span class="w"> </span><span class="mi">2021</span><span class="p">,</span><span class="w">
        </span><span class="nl">"link"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://www.cityvwofchicago.com/inventory/certified-used-2021-volkswagen-jetta-1-4t-se-fwd-4d-sedan-3vwc57bu4mm003393/"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"vin"</span><span class="p">:</span><span class="w"> </span><span class="s2">"3VWC57BU4MM003393"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"mileage"</span><span class="p">:</span><span class="w"> </span><span class="mi">27203</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="err">...</span><span class="w">
</span><span class="p">]</span><span class="w">
</span></code></pre></div></div>

<h2 id="aggregator-1">Aggregator</h2>

<p>This <a href="https://github.com/garciadiazjaime/website-cars/blob/main/support/aggregator.js">helper</a> combines all the <code class="language-plaintext highlighter-rouge">Site_N.json</code> files into a single <code class="language-plaintext highlighter-rouge">cars.json</code> file, which will be used by the web application.</p>

<h2 id="nextjs-1">Next.js</h2>

<p>A simple web app that displays <code class="language-plaintext highlighter-rouge">cars.json</code>. The page looks like this:</p>

<p><img src="/assets/nextjs-javascript-etl-jetta-chicago/jetta-chicago-ui.png" alt="Jetta Nextjs Web app" /></p>

<h2 id="demo">Demo</h2>

<p>Make sure to check out the <a href="https://volkswagen-chicago.mintitmedia.com/">Website</a> and take a look at the <a href="https://github.com/garciadiazjaime/website-cars">Codebase</a>.</p>

<p>So now, I know which Jetta to get 🤓</p>

<p>Thanks for reading!</p>

<p><strong>Disclaimer: I’m not sponsored in any way—just genuinely trying to find the best 🚗.</strong></p>]]></content><author><name></name></author><category term="nextjs" /><category term="react" /><category term="javascript" /><category term="etl" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">How to Set Up a Node.js Web Server on Raspberry Pi</title><link href="/posts/raspberrypi-nodejs-web-server" rel="alternate" type="text/html" title="How to Set Up a Node.js Web Server on Raspberry Pi" /><published>2025-01-20T15:00:00+00:00</published><updated>2025-01-20T15:00:00+00:00</updated><id>/posts/raspberrypi-nodejs-web-server</id><content type="html" xml:base="/posts/raspberrypi-nodejs-web-server"><![CDATA[<p><img src="/assets/three-lessons-learned-2024/banner.png" alt="How to Set Up a Node.js Web Server on Raspberry Pi" /></p>

<p>A couple of years ago, I bought a Raspberry Pi Model B+, and I finally decided to set up a web server on it.</p>

<p><img src="/assets/raspberrypi-nodejs-web-server/raspberrypi-model-bp.png" alt="Raspberry Pi Model B+" /></p>

<p>This might sound obvious, but I now understand that a Raspberry Pi is essentially a mini PC, which means it requires an operating system (OS) to function. This is different from other boards, like Arduino, where you can just run your program without needing to install an OS.</p>

<p>Here are the steps to set up a Node.js web server on a Raspberry Pi from scratch:</p>

<h2 id="1-install-raspberry-pi-imager">1. Install <code class="language-plaintext highlighter-rouge">Raspberry Pi Imager</code></h2>

<p><a href="https://www.raspberrypi.com/software/">Imager</a> will help you install Raspberry Pi OS onto a microSD card.</p>

<blockquote>
  <p>While there are other OS options like Debian, Ubuntu, etc., Raspberry Pi recommends using the official Raspberry Pi OS for the best experience with their boards.</p>
</blockquote>

<h2 id="2-install-raspberry-pi-os">2. Install <code class="language-plaintext highlighter-rouge">Raspberry Pi OS</code></h2>

<p>Open <code class="language-plaintext highlighter-rouge">Raspberry Pi Imager</code> and select:</p>

<ul>
  <li><strong>Raspberry Pi Device</strong>: Select your model.</li>
  <li><strong>Operating System</strong>: Choose the recommended option.</li>
  <li><strong>Storage</strong>: Select your microSD card.</li>
</ul>

<p><img src="/assets/raspberrypi-nodejs-web-server/os-setup.png" alt="Raspberry Pi OS Setup with Raspberry Pi Imager" /></p>

<p>I went with the default settings, and once it finished, it showed this message:</p>

<p><img src="/assets/raspberrypi-nodejs-web-server/os-setup-success.png" alt="Raspberry Pi Imager Setup Success Message" /></p>

<h2 id="3-insert-the-microsd-card-place-it-into-your-raspberry-pi-board">3. Insert the microSD card: Place it into your Raspberry Pi board.</h2>

<p>Along with the microSD card, connect any other peripherals, such as:</p>

<ul>
  <li>Mouse</li>
  <li>Keyboard</li>
  <li>Monitor</li>
  <li>Ethernet cable</li>
  <li>Power cable (it’s recommended to plug this in last).</li>
</ul>

<p><img src="/assets/raspberrypi-nodejs-web-server/raspberrypi-peripherals.png" alt="Raspberry Pi Peripherals" /></p>

<p>It’s recommended to plug in the power cable last. Notice that the microSD card is inserted on the opposite side of the board.</p>

<p><img src="/assets/raspberrypi-nodejs-web-server/raspberrypi-micro-sd-card.png" alt="Raspberry Pi Board MicroSD Card" /></p>

<p>Once Raspberry Pi OS boots up, you’ll see the desktop welcome message, which should look something like this:</p>

<p><img src="/assets/raspberrypi-nodejs-web-server/raspberrypi-desktop-welcome.png" alt="Raspberry Pi OS Desktop Environment" /></p>

<p>Finally, you’ll see the desktop environment, which looks like this:</p>

<p><img src="/assets/raspberrypi-nodejs-web-server/raspberrypi-desktop.png" alt="Raspberry Pi OS Desktop Environment" /></p>

<p>This means your Raspberry Pi OS is ready to use.</p>

<h2 id="install-updates">Install Updates</h2>

<p>Once your board is turned on, it will take some time (in my case, about 2 minutes) to boot up and display the desktop UI. Once it’s ready, open the terminal and run the following commands:</p>

<ol>
  <li>Update your system packages</li>
</ol>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt-get update <span class="nt">-y</span>
</code></pre></div></div>

<ol>
  <li>Update installed packages</li>
</ol>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt-get dist-upgrade <span class="nt">-y</span>
</code></pre></div></div>

<h2 id="install-nodejs">Install <code class="language-plaintext highlighter-rouge">Nodejs</code></h2>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt-get <span class="nb">install </span>nodejs <span class="nt">-y</span>
</code></pre></div></div>

<p>Let’s install <code class="language-plaintext highlighter-rouge">npm</code> as well</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt-get <span class="nb">install </span>npm <span class="nt">-y</span>
</code></pre></div></div>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>node <span class="nt">-v</span>
v18.19.0

<span class="nv">$ </span>npm <span class="nt">-v</span>
9.2.0
</code></pre></div></div>

<h2 id="install-express">Install <code class="language-plaintext highlighter-rouge">Express</code></h2>

<p>Express is an npm package that lets you easily run a web server. I used their generator and stuck with the default options.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npx express-generator
</code></pre></div></div>

<ul>
  <li>Say yes to the default settings</li>
</ul>

<h2 id="install-npm-packages">Install npm packages</h2>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm <span class="nb">install</span>
</code></pre></div></div>

<h2 id="run-the-server">Run the Server</h2>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm start
</code></pre></div></div>

<p>By default, the web server runs on PORT 3000. To access it from another machine, you just need the Raspberry Pi’s IP address. For instance, my Raspberry Pi’s IP is <code class="language-plaintext highlighter-rouge">192.168.100.239</code>, but yours will likely be different.</p>

<h2 id="open-the-web-server-on-another-machine">Open the Web Server on Another Machine</h2>

<p>Go to your regular machine, open the browser, and paste the IP and PORT (e.g., <code class="language-plaintext highlighter-rouge">http://192.168.1.239:3000</code>) in the address bar. You should see something like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>http://192.168.100.239:3000/
</code></pre></div></div>

<p><img src="/assets/raspberrypi-nodejs-web-server/raspberrypi-web-server-ip-port.png" alt="Raspberry Pi Web Server Accessed From Another Machine" /></p>

<p>If everything is set up correctly, your Express app will now be accessible from any device on the same network.</p>

<blockquote>
  <p>In my case, since I’m using the Raspberry Pi B+ board, the RAM and CPU aren’t as powerful, so it took a bit of time to run each command. It’s normal for lower-spec devices like this to take longer for tasks like installing dependencies or starting the server.</p>
</blockquote>

<h2 id="conclusion">Conclusion</h2>

<p>The Raspberry Pi is essentially a mini PC, offering seamless integration with IoT devices. You can connect sensors or just about any electronic gadget to the board.</p>

<p>While the Model B+ isn’t the most powerful option, there are more advanced boards available now, and it’s safe to assume the Raspberry Pi team will keep enhancing their devices in the future.</p>

<h2 id="extra">Extra</h2>

<p>Initially, I wanted to run Next.js on the Raspberry Pi, but I encountered the following error:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;--- JS stacktrace ---&gt;

FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory
Aborted
</code></pre></div></div>

<p>So, I ultimately decided to switch to Express. Remember, this is an older board with limited resources. Modern boards should have more power. The cool part is that, since it runs a Linux OS, you can install pretty much any package.</p>]]></content><author><name></name></author><category term="javascript" /><category term="nodejs" /><category term="iot" /><category term="raspberrypi" /><summary type="html"><![CDATA[]]></summary></entry></feed>