<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Matteo</title>
    <description>The latest articles on DEV Community by Matteo (@matfire).</description>
    <link>https://dev.to/matfire</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F39802%2F6b79d91e-812b-48ec-9abc-6176d66eaa25.png</url>
      <title>DEV Community: Matteo</title>
      <link>https://dev.to/matfire</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/matfire"/>
    <language>en</language>
    <item>
      <title>Making an RSS Feed for a Nuxt Website</title>
      <dc:creator>Matteo</dc:creator>
      <pubDate>Mon, 22 Sep 2025 15:11:18 +0000</pubDate>
      <link>https://dev.to/matfire/making-an-rss-feed-for-a-nuxt-website-3jab</link>
      <guid>https://dev.to/matfire/making-an-rss-feed-for-a-nuxt-website-3jab</guid>
      <description>&lt;p&gt;I recently remade my website (I know, I know) and I got a surprise when getting to reimplement an rss feed because, while &lt;a href="https://astro.build" rel="noopener noreferrer"&gt;Astro&lt;/a&gt; has a module that helps with generating an rss feed, Nuxt doesn't - at least not for V3 and consequently V4. But worry not, for making one is easy enough !&lt;/p&gt;

&lt;h2&gt;
  
  
  What is an RSS Feed ?
&lt;/h2&gt;

&lt;p&gt;An RSS feed is an xml document that allows users to subscribe to updates on a website. It is an xml document that can be easily parsed to retrieve the latest published article, the website's author and more info (wikipedia entry &lt;a href="https://en.wikipedia.org/wiki/RSS" rel="noopener noreferrer"&gt;here&lt;/a&gt; if you want to learn more).&lt;/p&gt;

&lt;p&gt;There are a lot of applications that will allow you to subscribe to an rss feed and even get notified when one is updated.&lt;/p&gt;

&lt;p&gt;Although RSS in its xml format is the most well-known way to consume web feeds, there are other formats that are also widely supported:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;json feeds&lt;/li&gt;
&lt;li&gt;atom feeds&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this article I'll only be talking about RSS - and briefly mentioning atom - but all the things I'm talking about apply to all of them.&lt;/p&gt;

&lt;h3&gt;
  
  
  The structure of an RSS Feed
&lt;/h3&gt;

&lt;p&gt;Let's take a look at what an rss feed looks like (you can find mine &lt;a href="https://www.matteogassend.com/rss.xml" rel="noopener noreferrer"&gt;here&lt;/a&gt;) - specifically the one for my website&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;rss&lt;/span&gt; &lt;span class="na"&gt;version=&lt;/span&gt;&lt;span class="s"&gt;"2.0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;channel&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Matteo's Log&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;link&amp;gt;&lt;/span&gt;https://www.matteogassend.com&lt;span class="nt"&gt;&amp;lt;/link&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;generator&amp;gt;&lt;/span&gt;Nuxt &lt;span class="err"&gt;&amp;amp;&lt;/span&gt; Feed&lt;span class="nt"&gt;&amp;lt;/generator&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;language&amp;gt;&lt;/span&gt;en&lt;span class="nt"&gt;&amp;lt;/language&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;atom:link&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://www.matteogassend.com/rss.xml"&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"self"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"application/rss+xml"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;item&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;T3Chat Cloneathon Postmortem&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;link&amp;gt;&lt;/span&gt;
      https://www.matteogassend.com/articles/t3-cloneathon-postmortem
      &lt;span class="nt"&gt;&amp;lt;/link&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;guid&lt;/span&gt; &lt;span class="na"&gt;isPermaLink=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;t3-cloneathon-postmortem&lt;span class="nt"&gt;&amp;lt;/guid&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;pubDate&amp;gt;&lt;/span&gt;Mon, 14 Jul 2025 00:00:00 GMT&lt;span class="nt"&gt;&amp;lt;/pubDate&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;description&amp;gt;&lt;/span&gt;
      I participated in the T3Chat cloneathon; here's how it went
      &lt;span class="nt"&gt;&amp;lt;/description&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;content:encoded&amp;gt;&lt;/span&gt;
        ...
      &lt;span class="nt"&gt;&amp;lt;/content:encoded&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/item&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/channel&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/rss&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First we have the main tag specifying that this xml document is an rss feed and its implementation version (in this case, version 2.0).&lt;br&gt;
We then get to the &lt;strong&gt;channel&lt;/strong&gt; definition; this is the actual feed that will be consumed and inside of it we can find info such as its name, description, language and more. &lt;br&gt;
Then, for each element we want to add to our &lt;strong&gt;channel&lt;/strong&gt;, we have a &lt;strong&gt;item&lt;/strong&gt; tag. Each item contains a name, description, publication date, unique id and optionally the raw content of the item (in this case, a blog article).&lt;/p&gt;
&lt;h2&gt;
  
  
  How to build one in Nuxt
&lt;/h2&gt;

&lt;p&gt;Luckily, Nuxt routes can be either Vue components or server endpoints - you could also use tsx, but we'll skip that for now. Server - or API - endpoints are handled by Nitro and use the classic browser Request &amp;amp; Response, meaning we can simply return an xml document with the correct headers and we're good to go! Let's see how to do it in detail&lt;/p&gt;
&lt;h3&gt;
  
  
  Declaring the API route
&lt;/h3&gt;

&lt;p&gt;Let's start by declaring the Nitro route; in this instance I'll have it match the &lt;code&gt;/rss.xml&lt;/code&gt; path, meaning I will create a file here: &lt;code&gt;/server/routes/rss.xml.ts&lt;/code&gt; and fill it with the basic Nitro handlers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineEventHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;//generate the feed&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="c1"&gt;//return the actual feed data here&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The RSS Feed enter the game
&lt;/h3&gt;

&lt;p&gt;We &lt;strong&gt;could&lt;/strong&gt; write the xml document by hand, but why bother? There exist the perfect package for this: &lt;a href="https://github.com/jpmonette/feed" rel="noopener noreferrer"&gt;feed&lt;/a&gt;. It allows use to programmatically build a feed and return it in different formats. Let's see how we can integrate it with Nitro.&lt;/p&gt;

&lt;p&gt;First things first, let's install the dependency:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;And let's start creating the feed itself; we'll begin by setting all the basic info needed for the feed and leave the items for later:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;feed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Feed&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Matteo's Log&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;All the articles from matteogassend.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;unique&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;link&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;your&lt;/span&gt; &lt;span class="nx"&gt;website&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;language&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;copyright&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`All rights reserved &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;getFullYear&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;, Matteo Gassend`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;updated&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;generator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Nuxt &amp;amp; Feed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;feedLinks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;rss&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;url of your website&amp;gt;/rss.xml`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;author&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With just this, we could generate an xml document and return it with the endpoint we made above. Let's do just that&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineEventHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="c1"&gt;// the feed is here&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;feed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rss2&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We only need two other things before we have a fully functional rss feed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;informing the browser of the type of data we're sending&lt;/li&gt;
&lt;li&gt;inserting the actual items in the feed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first step is simple enough, so let's get it out of the way; before returning the generated feed, let's set the &lt;code&gt;content-type&lt;/code&gt; header. This header tells the browser how to understand the content we'll send it back; you can see it used mostly when sending back json data - when making an api, for example. Here's how we can do it in Nitro:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;setResponseHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;content-type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text/xml&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells the browser we're sending back an xml document.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding Items to the feed
&lt;/h3&gt;

&lt;p&gt;Now that we have our basis covered, let's get to adding actual items to the feed. In my usecase, I needed to fetch items from a Nuxt Content collection and add it to the feed. So let's do just that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;articles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;queryCollection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;articles&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;publishDate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DESC&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you don't have a collection, just pretend this is an array of objects.&lt;/p&gt;

&lt;p&gt;After that, it's as simple as looping over your data array and calling the &lt;code&gt;addItem&lt;/code&gt; method on your feed instance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="nx"&gt;articles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;feed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addItem&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;publishDate&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;link&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;BASE_URL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/articles/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rawbody&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that's all you need to do to generate an RSS feed in your Nuxt app! I also added an atom feed with the exact same logic - I just had to replace &lt;code&gt;rss2&lt;/code&gt; call with &lt;code&gt;atom1&lt;/code&gt; and you're done!&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding the Feed to the Website
&lt;/h2&gt;

&lt;p&gt;Once you have your server route you just need to add it to your pages and most rss readers should be able to pick it up. Let's say our rss feed lives at &lt;code&gt;/rss.xml&lt;/code&gt;: if so, we'll need to add the following &lt;code&gt;link&lt;/code&gt; tag to our page's &lt;code&gt;head&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"alternate"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"application/rss+xml"&lt;/span&gt; &lt;span class="na"&gt;title=&lt;/span&gt;&lt;span class="s"&gt;"Your feed's title"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;`${BASE_URL}/rss.xml`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're using &lt;code&gt;unhead&lt;/code&gt;, then this can also be done in a useHead composable hook using something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;useHead&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;link&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;alternate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/rss+xml&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Matteo's Log&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/rss.xml&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;alternate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/atom+xml&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Matteo's Log&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/atom&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And you're officially done!&lt;/p&gt;

</description>
      <category>nuxt</category>
      <category>javascript</category>
      <category>rss</category>
    </item>
    <item>
      <title>Developing and Compiling Webapps with Vite and Go</title>
      <dc:creator>Matteo</dc:creator>
      <pubDate>Sat, 10 May 2025 00:00:00 +0000</pubDate>
      <link>https://dev.to/matfire/developing-and-compiling-webapps-with-vite-and-go-32mp</link>
      <guid>https://dev.to/matfire/developing-and-compiling-webapps-with-vite-and-go-32mp</guid>
      <description>&lt;p&gt;&lt;a href="https://www.matteogassend.com/articles/go-webapp-vite" class="ltag_cta ltag_cta--branded" rel="noopener noreferrer"&gt;this article uses custom markdown components. Expect some of them to break in places other than my blog&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;I've recently been playing around with Go (and RPC) and have stumbled upon the &lt;code&gt;embed&lt;/code&gt; package, which allows us to specify files and directory to be &lt;strong&gt;embedded&lt;/strong&gt; (hence the name) into the final binary. You would usually use this to embed something like the db schema / migrations, but there's also a world where this can be used for a vite bundle so that we can embed a SPA (single page app) into the final executable; neat right ?&lt;/p&gt;

&lt;h2&gt;
  
  
  Bundling the assets
&lt;/h2&gt;

&lt;p&gt;Let's consider that we already have a Vite application and that we have already run an &lt;code&gt;npm run build&lt;/code&gt; to generate its bundle and so we have a &lt;code&gt;dist&lt;/code&gt; folder where all our front assets are.&lt;/p&gt;

&lt;p&gt;If we have a standard http mux, we could simply add a &lt;code&gt;Handle&lt;/code&gt; function. Let's see what that would look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// this file could be called front_prod.go&lt;/span&gt;

&lt;span class="c"&gt;//go:embed dist/*&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;embedFS&lt;/span&gt; &lt;span class="n"&gt;embed&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FS&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Front&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mux&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServeMux&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;staticFiles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;fs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;embedFS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"dist"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;fileServer&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FileServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;staticFiles&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;mux&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fileServer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Serving static files from embedded filesystem"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's break this down: most of the magic happens on these lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;//go:embed dist/*&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;embedFS&lt;/span&gt; &lt;span class="n"&gt;embed&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FS&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These lines tell go to get the &lt;code&gt;dist&lt;/code&gt; folder and all its subfolders (and files) into a structure that matches the standard library's &lt;code&gt;FS&lt;/code&gt; interface and stores it in a variable called &lt;code&gt;embedFS&lt;/code&gt;. Inside the &lt;code&gt;Front&lt;/code&gt;  function we simply target the &lt;code&gt;dist&lt;/code&gt; folder (with a bit of error handling ) and we add the &lt;code&gt;FileServer&lt;/code&gt; as a route to our mux.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Caveats
&lt;/h3&gt;

&lt;p&gt;This is a simple and pretty "dumb" implementation of the "production assets"; if using something like React, you might need to write some logic to redirect the requests to the &lt;code&gt;index.html&lt;/code&gt; file if no matching asset is found.&lt;/p&gt;

&lt;h2&gt;
  
  
  What about during development ?
&lt;/h2&gt;

&lt;p&gt;Well, I think having to build a Vite app every time you make a change during development is not the best DX, right ? What if I told you there was a way that you could have all the power of the Vite dev server (i.e. hot reload) from within your go app ?&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Writing the Vite Proxy
&lt;/h3&gt;

&lt;p&gt;This is the (mostly) the whole code we'll need to do this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// this file could be called front_dev.go&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;createViteProxy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handler&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;proxy&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;httputil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewSingleHostReverseProxy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandlerFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c"&gt;// Update the request to point to the Vite server&lt;/span&gt;
        &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Host&lt;/span&gt;
        &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;URL&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Scheme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Scheme&lt;/span&gt;
        &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;URL&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Host&lt;/span&gt;
        &lt;span class="n"&gt;proxy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServeHTTP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Front&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mux&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServeMux&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;viteProxy&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;createViteProxy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http://localhost:5173"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;mux&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;viteProxy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"proxying requests to vite dev server"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's break it down!&lt;/p&gt;

&lt;p&gt;We have the same function signature for the &lt;code&gt;Front&lt;/code&gt; function&lt;br&gt;
:::callout&lt;br&gt;
Remember this, it will be important later on&lt;br&gt;
:::&lt;br&gt;
The magic happens with the &lt;code&gt;createViteProxy&lt;/code&gt; function, where we return a new &lt;code&gt;http.HandlerFunc&lt;/code&gt; that sends the request through a &lt;code&gt;SingleHostReverseProxy&lt;/code&gt; that points to the Vite dev server. This means that all requests the &lt;code&gt;Front&lt;/code&gt; function handles will be sent to the Vite server; this allows all the hot reload goodness that Vite provides to work out of the box.&lt;/p&gt;
&lt;h2&gt;
  
  
  How Do You Switch Between the Two ?
&lt;/h2&gt;

&lt;p&gt;Here's the fun part: you let the compiler do it!&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgkdv4h0ymy7gowyfwr0f.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgkdv4h0ymy7gowyfwr0f.gif" alt="gif" width="300" height="220"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On its latest versions, Go supports a feature called &lt;strong&gt;build constraints&lt;/strong&gt;. These are values that you can pass your &lt;code&gt;go build&lt;/code&gt; command and that can influence which files the compiler will include.&lt;br&gt;
When I shared the whole code file, I might have lied; there are actually three more lines you might need to have at the top of the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;//go:build dev&lt;/span&gt;
&lt;span class="c"&gt;//+build dev&lt;/span&gt;

&lt;span class="c"&gt;// the rest of the code goes here&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The two comments above are the two syntaxes for &lt;strong&gt;build constraints&lt;/strong&gt;. These tell the compiler to only include this file if this constraint is passed to the &lt;code&gt;build&lt;/code&gt; function: here's how you would run this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go build &lt;span class="nt"&gt;-tags&lt;/span&gt; dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you add the reverse condition on the "production" logic, you're done!&lt;br&gt;
:::callout&lt;br&gt;
you can just add a ! before the constraint you're checking&lt;br&gt;
:::&lt;/p&gt;

&lt;p&gt;This is why I said it would be important to name the &lt;code&gt;Front&lt;/code&gt; function the same in both files; if both files are in the same package, the rest of the program is completely oblivious to this switcharoo happening in dev or prod!&lt;/p&gt;

&lt;h2&gt;
  
  
  Should you do this ?
&lt;/h2&gt;

&lt;p&gt;While this might almost look like magic, there are cases in which this might not be the ideal choice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you need more complex routing that just redirecting to &lt;code&gt;index.html&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While I have addressed this a bit in the related section, I just want to point this out again.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;separation of concerns and scalability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This setup will obviously not work if you want to deploy your frontend code to a dedicated service (Vercel, Netflify etc), this will obviously not work. &lt;/p&gt;

&lt;p&gt;Still, there are some cases where you might want this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;offline-first&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Weirdly, this could work really well with an offline-first approach; download the binary, run it on your system and just browse to localhost:3000 (or any other port) and you have your whole application running, no internet required (except if you're connecting to external services, obviously)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;simple deploy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's a binary.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;great DX&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With this setup, you have a backend and frontend living on the same port (and/or domain), meaning you don't need to worry about CORS, proxy rules and whatever else causes mental breakdowns to developers nowadays.&lt;/p&gt;

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

</description>
      <category>go</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Custom markdown components with remark and web components</title>
      <dc:creator>Matteo</dc:creator>
      <pubDate>Fri, 21 Feb 2025 00:00:00 +0000</pubDate>
      <link>https://dev.to/matfire/custom-markdown-components-with-remark-and-web-components-43ho</link>
      <guid>https://dev.to/matfire/custom-markdown-components-with-remark-and-web-components-43ho</guid>
      <description>&lt;p&gt;&lt;a href="https://www.matteogassend.com/articles/remark-directives-web-components" class="ltag_cta ltag_cta--branded" rel="noopener noreferrer"&gt;this article uses custom markdown components. Expect some of them to break in places other than my blog&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;Recently, I ported my portfolio (again) to Astro from Hugo. While I really liked Hugo, I missed the choice JS gave me (plus, I was bored) so here we go!&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6go26ddknmxj3y6wu8qu.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6go26ddknmxj3y6wu8qu.gif" alt="gif" width="480" height="270"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Porting Data
&lt;/h2&gt;

&lt;p&gt;Everything is markdown in Hugo (mostly), so transitioning to and from was pretty simple; just import the data, build the collections and we're off to the races! The one thing I had to deal with was importing the cover images for the opengraph images, but that wasn't hard and went on without many issues.&lt;/p&gt;
&lt;h2&gt;
  
  
  Where are my shortcodes ?
&lt;/h2&gt;

&lt;p&gt;One thing that I really like about Hugo is how it handles shortcodes. This is how I could generate a stackblitz embed using Hugo: &lt;code&gt;{{&amp;lt;stackblitz url goes here&amp;gt;}}&lt;/code&gt; That line right there would then be mapped to a component called &lt;code&gt;stackblitz&lt;/code&gt; where I could describe the html element and I was done! But unfortunately, Astro does not handle shortcodes (or similar) natively, so I had to get creative.&lt;/p&gt;

&lt;p&gt;Astro uses &lt;a href="https://github.com/remarkjs/remark" rel="noopener noreferrer"&gt;remark&lt;/a&gt; and &lt;a href="https://github.com/rehypejs/rehype" rel="noopener noreferrer"&gt;rehype&lt;/a&gt; to transform your markdown collections into html.&lt;/p&gt;

&lt;p&gt;:::callout{type="info"}&lt;br&gt;
I've already written about the unified ecosystem, you can checkout that article &lt;a href="https://dev.to/blog/markdown-editor"&gt;here&lt;/a&gt;&lt;br&gt;
:::&lt;/p&gt;

&lt;p&gt;That means that we can hopefully build something within this pipeline and recreate the feeling of shortcodes.&lt;/p&gt;
&lt;h2&gt;
  
  
  Enter Remark Directive
&lt;/h2&gt;

&lt;p&gt;Turns out we (kind of) can! There's a remark plugin called &lt;a href="https://github.com/remarkjs/remark-directive" rel="noopener noreferrer"&gt;remark-directive&lt;/a&gt; that enables remark to parse directives (basically shortcodes, but maybe more powerful). With this plugin, we can then write a custom function that looks for these directives and "does something with them".&lt;/p&gt;
&lt;h3&gt;
  
  
  What do we do with a remark directive ?
&lt;/h3&gt;

&lt;p&gt;Let's take a small look at the source of one of my articles:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;Astro uses &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;remark&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;https://github.com/remarkjs/remark&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; and &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rehype&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;https://github.com/rehypejs/rehype&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; to transform your markdown collections into html.

:::callout(type="info")
I've already written about the unified ecosystem, you can checkout that article &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;here&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;/blog/markdown-editor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
:::
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The thing with the 3 &lt;code&gt;:&lt;/code&gt; is a remark directive. When I traverse the tree of the article (an abstract syntax tree describing the repo), I can check whether the node I'm looking at is a directive. If I find one, I can then check the type of directive (there are 3) and finally get its attributes.&lt;/p&gt;

&lt;p&gt;For example, the following snippet&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;::stackblitz{#mg-fbr-express}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Contains a node that is a &lt;code&gt;leafDirective&lt;/code&gt; with an id of &lt;code&gt;mg-fbr-express&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;:::callout{type="info"}&lt;br&gt;
the attributes section of the directive supports multiple elements separated by a whitespace like this: &lt;code&gt;::codesandbox{projectType="devbox" projectid="cwmtww"}&lt;/code&gt;&lt;br&gt;
:::&lt;/p&gt;

&lt;p&gt;Getting the node's attributes is pretty straightforward from there: they're in&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attributes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And now we have everything we need to create our custom elements!&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fke8j8jlexp0d3ic7ij8j.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fke8j8jlexp0d3ic7ij8j.gif" alt="gif" width="480" height="400"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Web Components
&lt;/h2&gt;

&lt;p&gt;Web Components are not new (they have been baseline available since 2021) but I don't think I've ever seen them much out in the wild. They have lots of advantages and will work pretty well in this use case, but they also come with some drawbacks.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Good
&lt;/h3&gt;

&lt;p&gt;Web components, as stated above, are pretty easy to get up and running and put in a page. Here's the code for my youtube embed web component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Youtube&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;HTMLElement&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;connectedCallback&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;iframe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;iframe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;iframe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`https://www.youtube.com/embed/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;videoid&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;div style="width: 100%; min-height: 500px;"&amp;gt;&amp;lt;iframe class="w-full min-h-[500px]" src="https://www.youtube.com/embed/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;videoId&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen&amp;gt;&amp;lt;/iframe&amp;gt;&amp;lt;/div&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;   
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;customElements&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;wc-youtube&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Youtube&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This little snippet does all the heavy lifting to generate an iframe tag with the provided videoid and all the necessary styles and (potentially) logic.&lt;/p&gt;

&lt;p&gt;Notice the last line; that's where we tell the browser that there's a new valid tag (called &lt;code&gt;wc-youtube&lt;/code&gt;) that is represented by the &lt;code&gt;Youtube&lt;/code&gt; class.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  The Bad
&lt;/h3&gt;

&lt;p&gt;Well, if web components are so nice, why is everyone bothering with frontend frameworks ?&lt;/p&gt;

&lt;p&gt;Well, because it’s not all roses. There are some design principles that you need to take into account if you want to use web components. If I had to choose one thing that "grinds my gears" about web components is that they are encapsulated; while this has some benefits in my use case this caused quite some headaches.&lt;/p&gt;

&lt;p&gt;I tried for quite some time to have TailwindCSS (which I'm using to style this whole website) pick up and generate the styling required by the web components. Because of this encapsulation (called the shadow DOM, by the way) this proved pretty hard - I probably would have needed to spend too much time looking into it (maybe next time?).&lt;/p&gt;

&lt;h2&gt;
  
  
  The Compromise
&lt;/h2&gt;

&lt;p&gt;So, what did I do ? I simply rewrote the styles in plain css. I didn't need that much styling to begin with, so why bother ?&lt;/p&gt;

&lt;h2&gt;
  
  
  Should you do this?
&lt;/h2&gt;

&lt;p&gt;Honestly I don't know; this "stack" fit my use case pretty well and is, by definition, portable (I could use these web components on basically any website). Could I have found another solution? Probably. Was this fun? Kind of. Was this interesting? Definitely.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>markdown</category>
    </item>
    <item>
      <title>How to rebuilt a personal website</title>
      <dc:creator>Matteo</dc:creator>
      <pubDate>Sun, 15 Dec 2024 00:00:00 +0000</pubDate>
      <link>https://dev.to/matfire/how-to-rebuilt-a-personal-website-238k</link>
      <guid>https://dev.to/matfire/how-to-rebuilt-a-personal-website-238k</guid>
      <description>&lt;p&gt;The first website I ever had was built on wordpress; it was a way for my friends and me to write small articles in things we liked at the time (like the Lavender Town Syndrome - yes, it was a long time ago) and share it with one another. Since then, said website has changed and evolved. Through it all it at some point became my professional portfolio and, as such, changed what was required of it. It became a place to showcase all I did (and help me land a job); and so wordpress was left behind. Throughout the last 4 years it went through some major rewrites; this article is a way to log those rewrites and what I've learned along the way.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Timeline
&lt;/h2&gt;

&lt;p&gt;I think there are mainly 3 major releases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Astro&lt;/li&gt;
&lt;li&gt;Laravel&lt;/li&gt;
&lt;li&gt;Hugo (the current one)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Astro
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://astro.build/" rel="noopener noreferrer"&gt;Astro&lt;/a&gt; is an awesome web framework that works really well with static content (which is baiscally all of my content). It worked pretty well with all my content collections and had pretty much all the extensions I needed, but I still needed to add custom extensions to remark to have it handle some &lt;code&gt;iframe&lt;/code&gt; elements I needed in some articles (codesandbox and stackblitz embeds, basically). This ultimately led me to look for a more customizable solution (where I could easily build some more custom stuff). This leads us to the first rewrite of this version.&lt;/p&gt;

&lt;h2&gt;
  
  
  Laravel
&lt;/h2&gt;

&lt;p&gt;Now, I hear you asking:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo34sk2gmwbbq13ahgzgz.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo34sk2gmwbbq13ahgzgz.gif" alt="gif" width="480" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The honest answer to that is: &lt;strong&gt;why not?&lt;/strong&gt; I believe that if you want to try new technologies (and can afford to - meaning probably don't do this in a work production app), you probably should. I had been hearing of Laravel for a while and finally gave it a shot.&lt;/p&gt;

&lt;p&gt;It was a really nice experience and it was really easy to add some more integrations like automatically adding &lt;a href="https://photoswipe.com/" rel="noopener noreferrer"&gt;photoswipe&lt;/a&gt; tags to each image in an article (I even made &lt;a href="https://github.com/matfire/commonmark-photoswipe?tab=readme-ov-file" rel="noopener noreferrer"&gt;an extension for it&lt;/a&gt;). It also allowed me extra space to add some more quality of life stuff, like a search function using &lt;a href="https://www.meilisearch.com/" rel="noopener noreferrer"&gt;meilisearch&lt;/a&gt; and its &lt;a href="https://laravel.com/docs/11.x/scout" rel="noopener noreferrer"&gt;laravel scout&lt;/a&gt; interface. Plus, Livewire is pretty cool to use (though I have my issues with it - to be discussed in a later post).&lt;/p&gt;

&lt;p&gt;Ultimately, though, it was probably too much for a personal website; it had too much overhead to simply publishing content, mainly just updating the website (without using Forge or Vapor); containerising the application definitely helped, but in my opinion it still took too long and, after some more reflections, it was needlessly overcomplicated for a blog/portfolio.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hugo
&lt;/h2&gt;

&lt;p&gt;And thus we get to &lt;a href="https://gohugo.io/" rel="noopener noreferrer"&gt;Hugo&lt;/a&gt;. I had heard of it in passing but had never really looked into it - so I finally did; it's pretty cool. It:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;supports md files natively&lt;/li&gt;
&lt;li&gt;can handle tags and series automatically (taxonomies FTW)
And best of all, it just produces a &lt;code&gt;public&lt;/code&gt; folder with all static html that I can send off to a cloudflare pages project and call it a day. This allows me to do as much (or as little) customization to the output as I want (more on that in the next section). I believe it is (for my use case, obviously) the most compelling solution at this time (as proven in this article, I'm not against rewriting all of this at some point)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Overengineering simplicity
&lt;/h3&gt;

&lt;p&gt;But it's not over yet! You know how I said above that you should be able to experiment and have fun, right ? Well, let's say I managed to find some fun stuff to do with this new setup. I'll just list them here, because some of this stuff is interesting enough to get its own articles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;client-only search using Lunr and AlpineJS&lt;/li&gt;
&lt;li&gt;automatic og (opengraph) image generation&lt;/li&gt;
&lt;li&gt;automatic photoswipe image rendering&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And these are the potential things I might add later:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;newsletter subscription section&lt;/li&gt;
&lt;li&gt;automatic bluesky post with the new article&lt;/li&gt;
&lt;li&gt;and so much more&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I will also look into having a self-hosted Github Runner if I end up reaching the limits of the free plan (though that should hopefully not happen)&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>astro</category>
      <category>hugo</category>
    </item>
    <item>
      <title>Dotfiles, the nix way</title>
      <dc:creator>Matteo</dc:creator>
      <pubDate>Wed, 30 Oct 2024 08:55:50 +0000</pubDate>
      <link>https://dev.to/matfire/dotfiles-the-nix-way-nob</link>
      <guid>https://dev.to/matfire/dotfiles-the-nix-way-nob</guid>
      <description>&lt;p&gt;I've recently started exploring the &lt;a href="https://nixos.org/" rel="noopener noreferrer"&gt;Nix&lt;/a&gt; ecosystem (I got hooked by &lt;a href="https://www.youtube.com/@vimjoyer" rel="noopener noreferrer"&gt;Vimjoyer's videos&lt;/a&gt;) and decided to give it a try (I even installed NixOS on my laptop, but that will come in a later article).&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Dotfiles
&lt;/h2&gt;

&lt;p&gt;First things first, what are dotfiles ? They are basically configuration files that are usually preceded by a &lt;code&gt;.&lt;/code&gt;. or that are usually kept inside a folder called &lt;code&gt;.config&lt;/code&gt; in your home directory. They are used to configure per user settings for apps that support it. For example, you could use it to change the keybindings in your i3 configuration, or pimp your neovim config or just change the color scheme for your favorite terminal. Why would you want to do that, I hear you ask? Why not, I would answer...&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd89kl003ub3gj9p50y6u.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd89kl003ub3gj9p50y6u.gif" alt="gif" width="500" height="338"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ok, let's be serious for a second; if you went through the (kind of) trouble that installing Linux is (though that obviously depends on the distro you choose), then you'd probably want to customize to be more "you" or to better fit your needs. Here's an example: I have pretty bad eyesight and tend to prefer some specific colors for windows, text and stuff; after a while, I found out that the Catppuccin Mocha theme is not as hard on my eyes than some other (or even the Gnome or KDE default dark mode, for that matter), so I like for as many of the applciations as I daily drive to use that same theme (plus, I think it looks pretty).&lt;/p&gt;

&lt;p&gt;Here's how you would configure &lt;code&gt;rofi&lt;/code&gt; to use Catppuccin Mocha:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;create a &lt;code&gt;mocha.rasi&lt;/code&gt; file that will contain all the theme customization rule&lt;/li&gt;
&lt;li&gt;create a &lt;code&gt;config.rasi&lt;/code&gt; file in &lt;code&gt;.config/rofi/&lt;/code&gt; that will contain the import rule for the theme you want: &lt;code&gt;@theme "catppuccin-mocha"&lt;/code&gt;
You might have noticed what my problem is with these kind of configuration files; most of them are custom file formats! This might be fine if you have one or two configuration files, but it gets old really quick if you have more than that.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  My Journey with Dotfiles
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Step 1: files ending in dots
&lt;/h3&gt;

&lt;p&gt;I started playing with dotfiles when I first installed Arch (btw), and the first approach I had was to just write my files directly in the &lt;code&gt;.config&lt;/code&gt; directory. That phase ended quickly once I mistakenly wiped my OS and lost all said files...&lt;/p&gt;

&lt;p&gt;Happens to the best of us, right?&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmihesrecgqirl49u3pij.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmihesrecgqirl49u3pij.gif" alt="gif" width="192" height="231"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 2: version control for the win
&lt;/h3&gt;

&lt;p&gt;Once I got my system up and running again, I decided to version control those files (you live you learn, right?)&lt;br&gt;
Here's the problem with that approach: you either version control the whole &lt;code&gt;.config&lt;/code&gt; directory, or you can create many small repos for each program because, spoiler alert, anything can write stuff in that directory.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 3: Stowing
&lt;/h3&gt;

&lt;p&gt;So now you have a git repo containing all your dotfiles but cannot directly clone into to replace your &lt;code&gt;.config&lt;/code&gt; folder, what do you do? You find out there's a tool that could fix all these problems: &lt;a href="https://www.gnu.org/software/stow/" rel="noopener noreferrer"&gt;GNU Stow&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;This tool allows you to create symlinks to anywhere and keep all your original files in a single folder (sound familiar?)&lt;/p&gt;

&lt;p&gt;I'd been on this step for a long time and kind of liked it, to be honest. But then I started hearing about Nix more and more often and so decided to take a look at it.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 4: Manage your home
&lt;/h3&gt;

&lt;p&gt;I'd read about Nix a while ago but had never explored it correctly. That changed when I got some time off work to tinker with some stuff. That's when I heard about &lt;a href="https://github.com/nix-community/home-manager" rel="noopener noreferrer"&gt;home-manager&lt;/a&gt; and decided to give it a try.&lt;/p&gt;
&lt;h2&gt;
  
  
  Home Manager
&lt;/h2&gt;

&lt;p&gt;Home-manager is a tool that allows you to declare your config files and put them wherever they need to be. You can declare almost anything directly using the Nix DSL, but you can also simply copy files into their correct directory and even clone directly a repo from Github (this is the method I use to get my neovim configuration instead of rewriting it to use Nix).&lt;/p&gt;

&lt;p&gt;Let's take a look at a simple home-manager &lt;code&gt;home.nix&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nix"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;home&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;stateVersion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"24.05"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;home&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;packages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;fzf&lt;/span&gt;
        &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;tmuxifier&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="nv"&gt;home&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s2"&gt;".config/waybar/mocha.css"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;source&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sx"&gt;./styles/mocha.css&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This little configuration will ensure that both fzf and tmuxifier are on your user's $PATH and copies over a css file from your directory into the appropriate &lt;code&gt;.config&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;But this is not all we can do with home-manager (again, I highly encourage you to read the documentation); let's see how we can configure some more stuff.&lt;br&gt;
This is how you can configure kitty, a tty:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nix"&gt;&lt;code&gt;  &lt;span class="nv"&gt;programs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;kitty&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;settings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="s2"&gt;"tab_bar_min_tabs"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="s2"&gt;"tab_bar_edge"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"bottom"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="s2"&gt;"tab_bar_style"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"powerline"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="s2"&gt;"tab_powerline_style"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"slanted"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="s2"&gt;"tab_title_template"&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"{title}{' :{}:'.format(num_windows) if num_windows &amp;gt; 1 else ''}"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nv"&gt;font&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"JetBrainsMono Nerd Font"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nv"&gt;themeFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Catppuccin-Mocha"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This enables kitty, sets some configuration stuff, like the topbar style title template string, but ti also sets up the font to use in the terminal and the theme the terminal will use.&lt;/p&gt;

&lt;p&gt;How about writing configs when home-manager does not have a module for it (or you can't find it)? Well, here is how I configure tmux using home-manager:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nix"&gt;&lt;code&gt;&lt;span class="nv"&gt;programs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;tmux&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;plugins&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kn"&gt;with&lt;/span&gt; &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;tmuxPlugins&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="nv"&gt;catppuccin&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="nv"&gt;extraConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;''&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;      set -g prefix C-s&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;      set -g mouse on&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;      set-option -g status-position top&lt;/span&gt;&lt;span class="err"&gt;

&lt;/span&gt;&lt;span class="s2"&gt;      set -g @plugin 'tmux-plugins/tpm'&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;      set -g @catppuccin_flavour 'macchiato'&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;      set -g @plugin 'kenos1/tmux-cht-sh'&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;      set -g @plugin 'jimeh/tmuxifier'&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;      set -g @catppuccin_window_left_separator ""&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;      set -g @catppuccin_window_right_separator " "&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;      set -g @catppuccin_window_middle_separator " █"&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;      set -g @catppuccin_window_number_position "right"&lt;/span&gt;&lt;span class="err"&gt;

&lt;/span&gt;&lt;span class="s2"&gt;      set -g @catppuccin_window_default_fill "number"&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;      set -g @catppuccin_window_default_text "#W"&lt;/span&gt;&lt;span class="err"&gt;

&lt;/span&gt;&lt;span class="s2"&gt;      set -g @catppuccin_window_current_fill "number"&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;      set -g @catppuccin_window_current_text "#W"&lt;/span&gt;&lt;span class="err"&gt;

&lt;/span&gt;&lt;span class="s2"&gt;      set -g @catppuccin_status_modules_right "directory user host session"&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;      set -g @catppuccin_status_left_separator  " "&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;      set -g @catppuccin_status_right_separator ""&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;      set -g @catppuccin_status_right_separator_inverse "no"&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;      set -g @catppuccin_status_fill "icon"&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;      set -g @catppuccin_status_connect_separator "no"&lt;/span&gt;&lt;span class="err"&gt;

&lt;/span&gt;&lt;span class="s2"&gt;      set -g @catppuccin_directory_text "#{pane_current_path}"&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;      run '~/.tmux/plugins/tpm/tpm'&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;    ''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This configuration here allows you to see how you can pass "raw" configuration values via &lt;strong&gt;extraConfig&lt;/strong&gt; and how to setup plugins taken directly from nixpackages.&lt;/p&gt;

&lt;p&gt;The last example I want to show you, is how to retrieve configuration files from a github repository and copy it over to the correct &lt;code&gt;.config&lt;/code&gt; folder. This goes in a bit deeper in the Nix DSL, but I think it still keeps pretty readable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nix"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt;
  &lt;span class="nv"&gt;neovim_config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;fetchFromGitHub&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;owner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"matfire"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;repo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"config.nvim"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;rev&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"044cb8670307bb3195607b90229f26ffcb61d46d"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;sha256&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"sha256-tzWcYrRGX82kBmPc50LjQmXfNjBoT/sLNanH470NuAE="&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="kn"&gt;in&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;home&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;".config/nvim"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;neovim_config&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will automatically clone and verify (with the sha256 you provide) the right repository and commit, and then clone (or copy) it over to the right place.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  The best of both worlds ?
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Personally&lt;/em&gt;, I really like doing configs like this; it lets me keep it all in one format (with the occasional exception, obviously) and be sure that whatever OS I use this on, I'll get the same programs &amp;amp; configs each time.&lt;/p&gt;

&lt;p&gt;But it still requires you to learn a new language (or the basics of it, at least) and it's a functional language, which may be a blocker for some. The first experience I ever had with a functional programming language was Haskell back in university, so I can understand the animosity with this principle.&lt;/p&gt;

&lt;p&gt;Still, I believe it worthwhile if you daily drive Linux and have a lot of config files to learn and use this kind of system.&lt;/p&gt;

</description>
      <category>nix</category>
    </item>
    <item>
      <title>Build a reverse ssh tunnel reverse proxy for (not) fun and (not) profit</title>
      <dc:creator>Matteo</dc:creator>
      <pubDate>Mon, 03 Jun 2024 14:58:51 +0000</pubDate>
      <link>https://dev.to/matfire/build-a-reverse-ssh-tunnel-reverse-proxy-for-not-fun-and-not-profit-50kl</link>
      <guid>https://dev.to/matfire/build-a-reverse-ssh-tunnel-reverse-proxy-for-not-fun-and-not-profit-50kl</guid>
      <description>&lt;p&gt;A few years ago, a team I was part of was working on &lt;a href="https://matteogassend.com/projects/vivi/" rel="noopener noreferrer"&gt;vivi&lt;/a&gt;, our master thesis's project. You can read more about it in the link above if you'd like, but the gist of it is; we were trying to build a solution to handle internet traffic and routing for ephemeral events (think something like comic-con etc) with lots of features and a user interface non-technical users could use and understand.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fby32dyoyr9bld7do00n6.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fby32dyoyr9bld7do00n6.gif" alt="Didnt Work The Blob GIF by Max" width="480" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Where does a reverse ssh tunnel reverse proxy fit into this
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Context
&lt;/h3&gt;

&lt;p&gt;When the time came to built the box that would live between the router and the rest of the network, we went (al least for the POC - proof of concept) with a RaspberryPi 4. We tried different solutions to handle communications between our backend server and all the machines, including using &lt;a href="https://www.balena.io/" rel="noopener noreferrer"&gt;balena os&lt;/a&gt; - which would have worked fine had we not needed to work with the network stack of the device.&lt;/p&gt;

&lt;h3&gt;
  
  
  Build it yourself
&lt;/h3&gt;

&lt;p&gt;So that's why we ended up building this kind of Frankenstein monster of a service. We needed a way to remotely connect to the device to check its status and perform maintenance operations (i.e. reboots, firmware updates etc). And before you ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;couldn't you have just used the ip address of the machine or exposed a port on the network to connect to it?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  NAT and local ip addresses
&lt;/h3&gt;

&lt;p&gt;Fasterthanlime has an excellent video describing how the internet works, you should check it out&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/jjKFXlFNR4E?start=4"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  A (forward) ssh tunnel
&lt;/h3&gt;

&lt;p&gt;A normal (or forward) SSH tunnel allows you to securely access a remote service over an encrypted SSH connection.&lt;/p&gt;

&lt;p&gt;For example, let's say you want to access a web service running on a remote server's port 8000, but that port is blocked by a firewall. You can create an SSH tunnel by connecting to the remote server (assuming port 22 is open) and forwarding a local port, say 9000, to the remote port 8000:&lt;br&gt;
you can run the command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="nt"&gt;-L&lt;/span&gt; 9000:localhost:8000 user@remote_server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;with this command, you can then open the address &lt;code&gt;localhost:9000&lt;/code&gt; and the request will then be forwarded to the remote machine's ssh port and then finally reach the destination port (8000 in this case). This is also called &lt;strong&gt;Tcp Forwarding&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0u3awbdh98arklcs0zzj.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0u3awbdh98arklcs0zzj.webp" alt="a schema of the logic behind ssh tunneling" width="800" height="418"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Let's reverse that
&lt;/h3&gt;

&lt;p&gt;Now, what happens if the machine I need to connect to is behind a firewall and/or a NAT? It would most likely not work because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I don't know the machine's actual IP address&lt;/li&gt;
&lt;li&gt;I cannot (usually) initiate a connection from outside the local network without an exposed port or something similar&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And so, enters the reverse ssh tunnel.&lt;br&gt;
A reverse ssh tunnel is based on the same principle as the normal ssh tunnel, except it contains an additional step, with the remote machine using the initial connection from the other host to create a new "tunnel" toward the initiating machine. This allows to bypass the issues with firewalls and NATs listed above.&lt;/p&gt;

&lt;p&gt;For example, to tunnel the SSH port (22) of a remote machine to port 8022 on your local machine, the command would be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="nt"&gt;-R&lt;/span&gt; 8022:localhost:22 user@local_host &lt;span class="nt"&gt;-N&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How we built it (attention: do not try this at home, there are easier ways to do this)
&lt;/h2&gt;

&lt;p&gt;There are a couple of part to this setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The http server onboard each device&lt;/li&gt;
&lt;li&gt;The systemd service responsible for the ssh tunnel on each device&lt;/li&gt;
&lt;li&gt;The Server handling all of these connections and all the reverse proxy logic&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Http Server
&lt;/h3&gt;

&lt;p&gt;The Http server is the API we built to manage the device; it allowed us to remotely restart the device, update the dependencies, retrieve its logs etc...&lt;/p&gt;

&lt;p&gt;It was a simple NestJS application.&lt;/p&gt;

&lt;h3&gt;
  
  
  Systemd Service
&lt;/h3&gt;

&lt;p&gt;The systemd service was responsible for receiving a port from the server and configuring the reverse ssh-tunnel to connect to said port.&lt;/p&gt;

&lt;h3&gt;
  
  
  Remote Server
&lt;/h3&gt;

&lt;p&gt;The remote server was the one receiving all the connections and handling the domain name registration for each device and its corresponding reverse proxy configuration.&lt;/p&gt;

&lt;p&gt;The code is available &lt;a href="https://github.com/vivitek/OpenVivi" rel="noopener noreferrer"&gt;here&lt;/a&gt; if you want to take a look at it (though I would not recommend it)&lt;/p&gt;

&lt;p&gt;The steps were:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A device would upload its ssh public key to the remote server&lt;/li&gt;
&lt;li&gt;The remote server would then generate an nginx config and associate a domain name to a port number&lt;/li&gt;
&lt;li&gt;The port number would be sent to the device to configure its service&lt;/li&gt;
&lt;li&gt;THe connection was then started on boot&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why you shouldn't do it this way
&lt;/h2&gt;

&lt;p&gt;The main issue with the way we did this is security; there really is no central authority managing this.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The device uploads its ssh key and registers itself with the server&lt;/li&gt;
&lt;li&gt;The server never verifies that the request it coming to a legitimate source.&lt;/li&gt;
&lt;li&gt;The server also doesn't check to see if the ssh connection for a specific host is aiming for the right port&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Alternatives
&lt;/h2&gt;

&lt;p&gt;A simpler way to handle this, given the infrastructure requirements, would probably have been a mesh vpn like tailscale or zerotier - or even plain old wireguard. This would still allow all reverse proxy goodies, but would provide a central authority to handle authentication (the server provides and checks for correct credentials on a new connection).&lt;/p&gt;

&lt;p&gt;Really, a vpn would probably have simplified a lot of things for this use case&lt;/p&gt;

</description>
      <category>networking</category>
      <category>ssh</category>
      <category>vpn</category>
      <category>nginx</category>
    </item>
    <item>
      <title>Using Shadcn-ui with Inertia and AdonisJS</title>
      <dc:creator>Matteo</dc:creator>
      <pubDate>Tue, 26 Mar 2024 14:01:34 +0000</pubDate>
      <link>https://dev.to/matfire/using-shadcn-ui-with-inertia-and-adonisjs-42b3</link>
      <guid>https://dev.to/matfire/using-shadcn-ui-with-inertia-and-adonisjs-42b3</guid>
      <description>&lt;p&gt;Since &lt;a href="https://adonisjs.com" rel="noopener noreferrer"&gt;AdonisJS&lt;/a&gt; V6 has experimental support for &lt;a href="https://inertiajs.com" rel="noopener noreferrer"&gt;Inertia&lt;/a&gt;, I thought it might be a good idea to see if I could configure &lt;a href="//ui.shadcn.com"&gt;shadcn/ui&lt;/a&gt; to run with the aforementioned stack.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring Inertia with AdonisJS
&lt;/h2&gt;

&lt;p&gt;Since support is experimental, I'll refer you to &lt;a href="https://docs.adonisjs.com/guides/inertia#installation" rel="noopener noreferrer"&gt;Adonis&lt;/a&gt;'s documentation on this procedure. If you're starting from scratch, it may be as simple as installing their inertia starter kit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing Shadcn/ui
&lt;/h2&gt;

&lt;p&gt;To install &lt;a href="//ui.shadcn.com"&gt;shadcn/ui&lt;/a&gt;, you can follow the basic instructions for Vite-based projects, but we'll need to adapt a few steps.&lt;br&gt;
The one thing to keep in mind is that you need to specifiy inertia's css file as the &lt;code&gt;global CSS file&lt;/code&gt; during setup.&lt;/p&gt;
&lt;h3&gt;
  
  
  Where to store components and libs
&lt;/h3&gt;

&lt;p&gt;I would personally advice (also, this is what I tested) to store all shadcn related files inside the &lt;code&gt;inertia&lt;/code&gt; folder; i personally created a &lt;code&gt;lib&lt;/code&gt; folder and had shadcn install everything in there.&lt;/p&gt;
&lt;h3&gt;
  
  
  tsconfig
&lt;/h3&gt;

&lt;p&gt;The documentation asks us to create aliases in both the &lt;code&gt;tsconfig&lt;/code&gt; file and the &lt;code&gt;vite.config.{js|ts}&lt;/code&gt; file. If you only change those in the root directory, shadcn will work fine, but your inertia pages will not be able to resolve the imports using those aliases.&lt;br&gt;
You can definitely stop here for this step and modify the imported components to use a normal path instead of the aliases it includes.&lt;/p&gt;

&lt;p&gt;But if you want to avoid that headache, there is one more tsconfig file you'll need to modify; the one inside the &lt;code&gt;inertia&lt;/code&gt; folder. Just copy the same aliases you just set up in the root project.&lt;/p&gt;

&lt;p&gt;Here's, for example, my root project tsconfig:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;compilerOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;//other paths can go here&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./inertia/lib/*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which then translates inside the &lt;code&gt;inertia&lt;/code&gt; folder to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;compilerOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// other paths here&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./lib/*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;as for the vite config, here's the interesting bit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;alias&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// other aliases can go here&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;getDirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;/inertia/lib`&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Tailwind Config
&lt;/h3&gt;

&lt;p&gt;After running the installation commands, we need to change a few settings from those created by default in our tailwind config file. This is because shadcn makes certain assumptions regarding the location of all our components and files.&lt;br&gt;
The only thing to modify is the &lt;code&gt;content&lt;/code&gt; array. If you're only using inertia (with React, in this case), you can probably use something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./inertia/**/*.{js,ts,jsx,tsx}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;There you go! Now you can enjoy using shadcn/ui with Inertia and AdonisJS. To demonstrate this, I built a small example you can find below:&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://codesandbox.io/embed/rntkn3"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

</description>
      <category>adonisjs</category>
      <category>react</category>
      <category>inertiajs</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Building a dynamic form with Svelte and Typescript</title>
      <dc:creator>Matteo</dc:creator>
      <pubDate>Fri, 23 Feb 2024 13:48:57 +0000</pubDate>
      <link>https://dev.to/matfire/building-a-dynamic-form-with-svelte-and-typescript-4b6o</link>
      <guid>https://dev.to/matfire/building-a-dynamic-form-with-svelte-and-typescript-4b6o</guid>
      <description>&lt;p&gt;In the quest to make &lt;a href="https://magiedit.magitools.app" rel="noopener noreferrer"&gt;magiedit&lt;/a&gt; always better, I figured out that people may want to publish to multiple "accounts" on the same platform. So, the goal for the next feature was to allow just that. A user should be able to select the platform they want to create a &lt;strong&gt;Publisher&lt;/strong&gt; for, fill in the required data, and be able to publish to it.&lt;br&gt;
The problem was in the second to last step; the form. How do you handle the fact that different platforms require different authenticating elements to send requests?&lt;/p&gt;
&lt;h2&gt;
  
  
  Generating a form from a class
&lt;/h2&gt;

&lt;p&gt;As mentioned in a previous article, all the platforms have a decorator that adds them to a list that can be imported in other files, meaning I could get a list of supported platforms and generate a &lt;code&gt;select&lt;/code&gt;, allowing the user to select the platform they wanted to configure. The next step consisted of having a way for the class to describe the input it needs to handle publishing an article.&lt;/p&gt;
&lt;h3&gt;
  
  
  Describing form elements
&lt;/h3&gt;

&lt;p&gt;After some deliberation, I settled on the following structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;IPlatformSetting&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;htmlFor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nl"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;type defines what form element will be rendered (i.e. input)&lt;/li&gt;
&lt;li&gt;name: the name of the element (pretty self explanatory, I know)&lt;/li&gt;
&lt;li&gt;label: the settings for the label (both the &lt;code&gt;for&lt;/code&gt; attribute and the actual text inside it)&lt;/li&gt;
&lt;li&gt;settings: an object of settings for the html element; as an example, here's what the Dev.to platform settings look like:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;api_token&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;htmlFor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;api_key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;API Key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nx"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There still is a bit of information duplication, but for now it works pretty well.&lt;/p&gt;

&lt;h3&gt;
  
  
  Displaying the form elements
&lt;/h3&gt;

&lt;p&gt;Once the user has selected the platforms they want to publish on, the next step is to display all the fields necessary for that specific platform. Remember how we had a method that described precisely this? This is where we get to use it.&lt;br&gt;
With Sveltekit, this could look somthing like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nx"&gt;setting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Label&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;setting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;htmlFor&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;setting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Label&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Input&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;setting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;setting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;{...&lt;/span&gt;&lt;span class="nx"&gt;setting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sr"&gt;/if&lt;/span&gt;&lt;span class="err"&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you ever need to do more field types, just add them as available types for each setting element and modify the if condition (hoping someday to have a switch statement in svelte - or pattern matching).&lt;/p&gt;

&lt;h3&gt;
  
  
  Handling submission
&lt;/h3&gt;

&lt;p&gt;This next step is probably the easiest. Since you're also sending the select platform &lt;strong&gt;template&lt;/strong&gt;, you can reference that to determine if the data is valid (why not try &lt;a href="https://superforms.rocks" rel="noopener noreferrer"&gt;superforms&lt;/a&gt;? I made an adapter for it).&lt;/p&gt;

&lt;p&gt;With no validation whatsoever, it could look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;publisher_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;publisher_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromEntries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userPublications&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;publisherName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;publisher_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;publisherData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;publisher_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ok&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>typescript</category>
      <category>svelte</category>
      <category>javascript</category>
    </item>
    <item>
      <title>How filesystem-based routers work: building one for express</title>
      <dc:creator>Matteo</dc:creator>
      <pubDate>Sun, 22 Oct 2023 09:55:45 +0000</pubDate>
      <link>https://dev.to/matfire/how-filesystem-based-routers-work-building-one-for-express-1p26</link>
      <guid>https://dev.to/matfire/how-filesystem-based-routers-work-building-one-for-express-1p26</guid>
      <description>&lt;p&gt;Have you ever wondered how Next, Sveltekit, Nuxt (even Expo now) do their routing? What's the magic that makes it so when you create a file called &lt;code&gt;+page.svelte&lt;/code&gt; in a directory called &lt;code&gt;routes&lt;/code&gt;, just for it to magically works? Well, wonder no more! I'll show you how to build your own file-based router for &lt;a href="https://expressjs.com/" rel="noopener noreferrer"&gt;express&lt;/a&gt; in Javascript, though the concepts we'll see are usable in other frameworks and other languages. This article will focus on parsing files, ignoring folders (because that is a whole of grenades I'm not yet ready to step - and write - on). Let's get to it!&lt;/p&gt;

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

&lt;h2&gt;
  
  
  How file routing works
&lt;/h2&gt;

&lt;p&gt;First, let's start by saying what file-based routing is and how it works.&lt;br&gt;
Usually, a function will loop through the &lt;code&gt;routes&lt;/code&gt; folder (ever noticed how you always have a single place where you place your &lt;code&gt;pages&lt;/code&gt;? this avoid crawling the whole project directory every time your app starts), getting all the files matching a certain pattern (for svelte, that will be &lt;code&gt;+page.svelte&lt;/code&gt;) and, based on their location in the folder, determines what requests should be sent to it. The first part of this series, as specified above, will not be handling folders; instead, we will just do a basic file router; here's how it'll work:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe4hstlvq7pnak7c3gmou.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe4hstlvq7pnak7c3gmou.png" alt="file based structure explainer" width="334" height="190"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This means that when we'll call our function, it will step through our route directory and generate a separate route for each filename: &lt;code&gt;user.js&lt;/code&gt; will create &lt;code&gt;/user&lt;/code&gt; for example.&lt;/p&gt;
&lt;h2&gt;
  
  
  Building a file-based router
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Initial Setup
&lt;/h3&gt;

&lt;p&gt;First of all, we should setup our base structure. For this example, I'll have an &lt;code&gt;index.js&lt;/code&gt; file at the root of my project that will instanciate the express application and call the function to generate the routing. Then I'll have a &lt;code&gt;routes&lt;/code&gt; folder that will contain - you guessed it - our routing files. The only dependency you should need is the &lt;code&gt;express&lt;/code&gt; package; if you haven't already done so, you should install it in a new project using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--save&lt;/span&gt; express
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and let's initialize the express application in &lt;code&gt;index.js&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;listening&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This should be enough to have a basic http server listening on port 4000 (btw, you should probably use an environment variable here instead of hardcoding it like this). Next, let's see how we should define our route file; inside our &lt;code&gt;routes&lt;/code&gt; folder, let's create a &lt;code&gt;user.route.js&lt;/code&gt; file; this suffix (.route) allows us to filter only the files we are sure belong to our application thanks to the power of regex (oh yes, we'll be writing a regex - just one, I promise)!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvreqpruw2u5n99d2207r.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvreqpruw2u5n99d2207r.gif" alt="Hacker Deal With It GIF by Sleeping Giant Media" width="1024" height="1024"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Making a route
&lt;/h3&gt;

&lt;p&gt;Let's start by defining a simple rule: &lt;strong&gt;every route file should have a Router instance as its main export&lt;/strong&gt;. This will allow us to do simple programmatic requires to load our route files (we'll look at how to do more specific filtering and imports in a following article, don't worry).&lt;/p&gt;

&lt;p&gt;So, let's say we have a &lt;code&gt;user.route.js&lt;/code&gt; file containing the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nc"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/:id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`test &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When our loader will be finished, this should generate two routes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;/user&lt;/li&gt;
&lt;li&gt;/user/:id (this one will match /user/1, /user/2 etc)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Making the loader
&lt;/h3&gt;

&lt;p&gt;Now it's time to make the actual loader:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fokbs9snjl3iwubqy8iv0.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fokbs9snjl3iwubqy8iv0.gif" alt="Its Time Vegas GIF by BPONGofficial" width="480" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's recap what our loader needs to do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;walk the &lt;code&gt;routes&lt;/code&gt; folder&lt;/li&gt;
&lt;li&gt;for each file matching the pattern (routeName).route.js, add a route to  our express app that looks like &lt;code&gt;/routeName&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, i'll make a new file called &lt;code&gt;router.js&lt;/code&gt; which exports an asynchronous function. This function will take as an argument the express application and define a regex we will use to match our route name and save it as a group.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fileNameRegex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;.*&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;.routes.js$/&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And in this function we'll begin by walking the whole &lt;code&gt;routes&lt;/code&gt; folder&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* add this at the top of the file */&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node:fs/promises&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;folders&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./routes&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then check each file to see if it matches the regex we defined earlier:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="nx"&gt;folders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;regexName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fileNameRegex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;regexName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And finally, we add the route to our application with a little log line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`[+] Router file &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; loaded under /&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;regexName&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;regexName&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`./routes/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last step is to use our loader function: your index.js should look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;loadRoutes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./router&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nf"&gt;loadRoutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;listening&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;This is the simplest way to make a file-based router in express js. This logic can obviously be translated to be used in other framework, like &lt;a href="https://github.com/GiovanniCardamone/fastify-autoroutes" rel="noopener noreferrer"&gt;this plugin&lt;/a&gt; for &lt;a href="https://fastify.dev/" rel="noopener noreferrer"&gt;fastify&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can see a working example below:&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://stackblitz.com/edit/fbr-express-umc23i?embed=1&amp;amp;file=index.js" width="100%" height="500"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>node</category>
    </item>
    <item>
      <title>test everywhere with dagger.io</title>
      <dc:creator>Matteo</dc:creator>
      <pubDate>Sun, 15 Oct 2023 09:28:43 +0000</pubDate>
      <link>https://dev.to/matfire/test-everywhere-with-daggerio-1fac</link>
      <guid>https://dev.to/matfire/test-everywhere-with-daggerio-1fac</guid>
      <description>&lt;p&gt;Have you ever had to implement a CI/CD pipeline and then proceed to push to a repository 10/20 times just to see if the pipeline works correctly? Well, this ends today!&lt;/p&gt;

&lt;h2&gt;
  
  
  Dagger (not the stabby kind)
&lt;/h2&gt;

&lt;p&gt;Dagger's tagline is&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;CI/CD as Code that Runs Anywhere&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It is a way for developers to create pipelines that run everywhere, be it locally, as Github Actions or anywhere where you can spawn containers; it is what I use to do a test build on &lt;a href="https://magiedit.magitools.app" rel="noopener noreferrer"&gt;magiedit&lt;/a&gt; and what I'll use when I eventually write unit tests (it'll come one day, I promise.)&lt;/p&gt;

&lt;h3&gt;
  
  
  How do you use it?
&lt;/h3&gt;

&lt;p&gt;Dagger provides different sdks (for Javascript, Python, Go etc) and they also have an HTTP and GraphQL api, so you can use with whatever coding language you prefer. Then you just run it wherever your pipelines run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a Pipeline
&lt;/h2&gt;

&lt;p&gt;In this article I'll be using the Javascript sdk, because that is the one I know better (and because it is my article, after all). We'll be looking at how to create a simple pipeline script with some extra configuration like setting some env variables.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Container engine
&lt;/h4&gt;

&lt;p&gt;Dagger requires a container engine for it run wherever it needs to run (at this stage, at least, locally); the recommended one is Docker (I have an article on the basics of Docker &lt;a href="https://matteogassend.com/blog/taming-the-whale" rel="noopener noreferrer"&gt;here&lt;/a&gt;), but other runtimes are compatible (podman, containerd etc): here's &lt;a href="https://docs.dagger.io/541047/alternative-runtimes" rel="noopener noreferrer"&gt;an article&lt;/a&gt; detailing how to use them, though I would still recommend Docker.&lt;/p&gt;

&lt;h4&gt;
  
  
  Install Dagger CLI
&lt;/h4&gt;

&lt;p&gt;You can find the installation instructions &lt;a href="https://docs.dagger.io/quickstart/729236/cli" rel="noopener noreferrer"&gt;here&lt;/a&gt;, but the gist is (for Linux / WSL) to execute this line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /usr/local
curl &lt;span class="nt"&gt;-L&lt;/span&gt; https://dl.dagger.io/dagger/install.sh | sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Install Dagger SDK
&lt;/h4&gt;

&lt;p&gt;For this example I'll be using the NodeJS sdk which you can install like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @dagger.io/dagger &lt;span class="nt"&gt;--save-dev&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Writing the actual pipeline
&lt;/h3&gt;

&lt;p&gt;The main thing to do is import the &lt;code&gt;connect&lt;/code&gt; function from the Dagger sdk, because everything will happen in there:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;connect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@dagger.io/dagger&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once we call this function, we can pass it a callback that gets as its argument the client instance; this is what we will use to define our pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;LogOutput&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stdout&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see from the snippet above, you can also specify where the output logs will be sent; in this case, the standard output is good enough, but you could also sent them to a file that could, for example, be uploaded as an artifact for the pipeline run.&lt;/p&gt;

&lt;p&gt;Then you can declare a runner with a base image and run all the commands you need with it;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;container&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:18&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withDirectory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;host&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;directory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;exclude&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node_modules&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ci&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;runner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withWorkdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;withExec&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;npm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;install&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above snippet, we declare a container based on node 18 and we copy the contents of the current directory (excluding the folders called node_modules and ci), then set the working directory to /app (the directory we copied our project to) and execute npm install inside it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxbod6dke6l59fulg32df.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxbod6dke6l59fulg32df.gif" alt="The Walking Dead Easy Peasy GIF" width="480" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, how about running a build script and getting the error output (if any)? Simple, we just continue adding calls to our connect callback:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;runner&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withExec&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;npm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;run&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;build&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Running the pipeline
&lt;/h3&gt;

&lt;p&gt;To run pipeline, you simply execute it like any other NodeJS script; assuming you have saved your script as &lt;code&gt;build.mjs&lt;/code&gt; in a &lt;code&gt;ci&lt;/code&gt; folder, you can just run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node ci/build.mjs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffp01m28cxjnz64f5n2vs.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffp01m28cxjnz64f5n2vs.gif" alt="Weird Al GIF by The Roku Channel" width="480" height="266"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And that's how you can create and run a basic pipeline; note that you can create multi-stage builds, publish images, using existing Dockerfiles and much more!&lt;/p&gt;

&lt;h3&gt;
  
  
  Bonus Round: Github Actions
&lt;/h3&gt;

&lt;p&gt;Running a Dagger pipeline in Github Actions is pretty straightforward; you need to setup your environment to be able to run Dagger, which means basically installing nodejs js and installing dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;master&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;feature/*&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pnpm/action-setup@v2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;18&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install dependencies&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pnpm install&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node ci/build.mjs&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, I'm using pnpm to install my dependencies, and then simply execute the pipeline.&lt;/p&gt;

&lt;p&gt;If you want to learn more about Dagger, check out &lt;a href="https://dagger.io/" rel="noopener noreferrer"&gt;their website&lt;/a&gt;; they have some very nice real case articles on how to use Dagger to build more complex pipelines.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>devops</category>
      <category>dagger</category>
      <category>javascript</category>
    </item>
    <item>
      <title>building a basic markdown editor: unified, trees and data</title>
      <dc:creator>Matteo</dc:creator>
      <pubDate>Mon, 02 Oct 2023 12:06:54 +0000</pubDate>
      <link>https://dev.to/matfire/building-a-basic-markdown-editor-unified-trees-and-data-588a</link>
      <guid>https://dev.to/matfire/building-a-basic-markdown-editor-unified-trees-and-data-588a</guid>
      <description>&lt;p&gt;As you have probably guessed from the previous articles in this series (go read them if you haven't, btw), the main thing when building a markdown publishing webapp, is the markdown editor itself.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F996jmbs3wi6q0wtramxu.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F996jmbs3wi6q0wtramxu.gif" alt="Season 3 Walk GIF by The Simpsons" width="480" height="366"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But not all editors are born equal; there are many ways of building one, with benefits and tradeoffs. In this article I'll try to explain what led me to my current solution (not yet completed, but still I'd say it's about 80% done).&lt;/p&gt;

&lt;h2&gt;
  
  
  Markdown
&lt;/h2&gt;

&lt;p&gt;if you don't know what markdown is, think of it as kind of like html; it is a type of text with special characters or combination of characters that get interpreted and displayed according to a determined specification; for instance, an html &lt;code&gt;&amp;lt;h2&amp;gt;&lt;/code&gt; tag would be translated in markdown with &lt;code&gt;##&lt;/code&gt; and vice versa. It is especially useful if you want to quickly write a README, some documentation or, turns out, articles for certain platforms. You can find a more complete introduction that I can provide &lt;a href="https://www.markdownguide.org/" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I needed to build
&lt;/h2&gt;

&lt;p&gt;I needed to build a markdown editor with a live preview. The first part is not that difficult; put down a textarea on a page and you have your editor! The problem was with the live preview; turns out Markdown is not natively supported by browsers...&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyhp63eiodazcbexusfhb.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyhp63eiodazcbexusfhb.gif" alt="Excuse Me Wow GIF by Mashable" width="195" height="229"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But you know what is? HTML! So I just needed to build (or find, in this case) a tool that allowed me to convert Markdown to HTML!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb3g104v2qi4uws5k6qdm.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb3g104v2qi4uws5k6qdm.gif" alt="Tyler Perry Problem Solved GIF by Nickelodeon" width="270" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Unified
&lt;/h3&gt;

&lt;p&gt;To build the Markdown editor (and the preview, mostly), I decided to use &lt;a href="https://unifiedjs.com/" rel="noopener noreferrer"&gt;unified&lt;/a&gt;, an ecosystem of tools allowing the developer to parse a format into an abstract tree and back into another format (for example, markdown to html) and modify said tree (for example, to add specific classes to certain html elements before they are converted to an actual html string. The basics of how to do so can be found in &lt;a href="https://unifiedjs.com/learn/guide/using-unified/" rel="noopener noreferrer"&gt;this article&lt;/a&gt;, but they mostly consist of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;reading the markdown content (from the textarea in this case)&lt;/li&gt;
&lt;li&gt;using &lt;code&gt;remarkParse&lt;/code&gt; to convert the markdown into a syntax tree&lt;/li&gt;
&lt;li&gt;using &lt;code&gt;remarkRehype&lt;/code&gt; to convert the markdown syntax tree to an html syntax tree&lt;/li&gt;
&lt;li&gt;using &lt;code&gt;rehypeStringify&lt;/code&gt; to convert the html syntax tree to an html string&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnufucaxbh0mqmuhbgue6.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnufucaxbh0mqmuhbgue6.gif" alt="GIF by South Park " width="480" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The real magic is what happens once you generate the syntax trees; at that point, you can modify them with the existing plugins (or make you own, if you really want to). For instance, I use a plugin to add specific css classes to certain elements so they integrate better with the visual design of the website another to add code highlighting with &lt;a href="https://highlightjs.org/" rel="noopener noreferrer"&gt;highlight.js&lt;/a&gt; and some others for generating a js object from the frontmatter of a Markdown file and to add support for Github flavored Markdown. I could do a lot more with these, like add support for videos, embeds and more, but for now this is enough for a simple preview.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NB: remark and rehype are also used by &lt;a href="https://astro.build" rel="noopener noreferrer"&gt;astro&lt;/a&gt; to render the collection's markdown content, so any plugins you use there can also be used here&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting it all together
&lt;/h2&gt;

&lt;p&gt;Now that we have a basic understanding of how unified works, let's practice! I have already created a stackblitz example you can see just below this paragraph: try writing something in the textarea and it should be parsed and displayed in the preview next to it!&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://stackblitz.com/edit/mg-markdown-parser?embed=1&amp;amp;file=src%2FApp.svelte" width="100%" height="500"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

</description>
      <category>svelte</category>
      <category>sveltekit</category>
      <category>markdown</category>
      <category>unified</category>
    </item>
    <item>
      <title>end-to-end encryption with sveltekit</title>
      <dc:creator>Matteo</dc:creator>
      <pubDate>Mon, 25 Sep 2023 12:49:36 +0000</pubDate>
      <link>https://dev.to/matfire/end-to-end-encryption-with-sveltekit-4gm5</link>
      <guid>https://dev.to/matfire/end-to-end-encryption-with-sveltekit-4gm5</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Now that Magiedit is kind of done (except for user experience, I guess), I figured I had to tackle the security / sync of notes; at the beginning of this project, I thought I could store everything locally (first versions did not even have authentication), and later on implement a solution to sync back and forth with a remote server using something like couchdb and pouchdb. When I tried implementing things like those, however, I ran into all kinds of problems, namely making it play nice with sveltekit (and vite) and syncing different models (articles and settings for each user). And so I decided, at least for now, to ditch storing articles locally, and simply store them on a remote db (right now &lt;a href="//turso.tech"&gt;turso&lt;/a&gt;, btw). The thing is, I didn't want to store raw article data for reasons like data privacy and privacy in general (I don't want to know what you write using Magiedit). To solve this problem, I started looking into cryptography, specifically the web-cryptography api.&lt;/p&gt;

&lt;h2&gt;
  
  
  How this all works
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Genering a master password
&lt;/h3&gt;

&lt;p&gt;When a user creates an account, it is now required to create a master password; this will then be stored (hashed, obviously) on the server and at each new session they will need to enter it again. This master password is the origin for every cryptographic key used to encrypt and decrypt the articles.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NB: except for the master password creation and unlocking, the clear password is never sent to the server and is instead stored in the browser's session storage&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  How and where things happen
&lt;/h3&gt;

&lt;h4&gt;
  
  
  How it works (algorithms and stuff)
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;DISCLAIMER: I'm not an expert in cryptography, so take everything I say with a grain of salt&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The key is based on a hash of the master password using SHA-256&lt;/li&gt;
&lt;li&gt;The algorithm used for encryption is AES-CBC, since it is symmetric and does not make me manage private and public keys (they would work better, but let's be honest; they are just articles, after all)&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Where it works
&lt;/h4&gt;

&lt;p&gt;Let's first take a look at this schema:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4enoxwa2ri4zna3dg7fw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4enoxwa2ri4zna3dg7fw.png" alt="magiedit's data flow diagram" width="759" height="526"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When a user creates, loads (from a file) or saves an article, the same steps happen:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A cryptographic key is generated using the master password hashed using SHA-256
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;keyBytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subtle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SHA-256&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TextEncoder&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;keyData&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subtle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;importKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;raw&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;keyBytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AES-CBC&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;encrypt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;We generate an iv (initialization vector, kind of like a seed)
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;generateIv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRandomValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The text is then encrypted using the key and the iv
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;encodedContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subtle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encrypt&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AES-CBC&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;iv&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TextEncoder&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;write here&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;It is then converted to base64
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;base64&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;btoa&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromCharCode&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;encodedContent&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;and finally sent to the server (along with its iv) to get it stored securely&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As for decrypting, it is the same process, but in reverse:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;get the article from the db&lt;/li&gt;
&lt;li&gt;convert the base64 string to a buffer&lt;/li&gt;
&lt;li&gt;generate a key from the master password&lt;/li&gt;
&lt;li&gt;decrypt the buffer&lt;/li&gt;
&lt;li&gt;convert the buffer back to a string&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbwunsj0d54cu9ju0ref7.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbwunsj0d54cu9ju0ref7.gif" alt="Art Drawing GIF by GEICO" width="1024" height="1024"&gt;&lt;/a&gt;&lt;br&gt;
I really like this gif, you know?&lt;/p&gt;

&lt;h2&gt;
  
  
  Why end to end encryption ?
&lt;/h2&gt;

&lt;p&gt;Because I wanted users to know that even if I wanted to (and I don't) read what they wrote before publishing, I couldn't, because I wanted to be in line with current regulations about data privacy and, most of all, because it looked like it could be fun!&lt;/p&gt;

</description>
      <category>sveltekit</category>
      <category>svelte</category>
      <category>typescript</category>
      <category>cryptography</category>
    </item>
  </channel>
</rss>
