<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Matteo&apos;s Log</title><description>All the articles from matteogassend.com</description><link>https://matteogassend.com/</link><item><title>Better Release PRs with brel</title><link>https://matteogassend.com/articles/better-releases-brel/</link><guid isPermaLink="true">https://matteogassend.com/articles/better-releases-brel/</guid><pubDate>Mon, 16 Mar 2026 00:00:00 GMT</pubDate><content:encoded>import Callout from &apos;../../components/mdx/Callout.astro&apos;

I really like [release-plz](https://release-plz.dev/) for my Rust projects ([balzac](https://balzac.rs) uses it); it&apos;s easy to configure, it automatically updates the relevant version fields (in cargo.toml) and can even update the `CHANGELOG.md` of your repo. There&apos;s only one problem: it only works with Rust. So I decided to make my own.

## Better Releases

[better releases](https://better-releases.com) is a tool that works on any Github repo, with any language and any stack. It uses a simple-ysh `brel.toml` file and:

- reads the commit from the previous tag (if it exists)
- figures out the next tag based on conventional commits
- opens/updates a release pr

Once the pr is merged, it can optionally publish the detected tag to potentially trigger other workflows; in the [drizzle-sluggable](https://github.com/matfire/drizzle-sluggable) repo, this is what I use to trigger publishing the package to npm.

## Getting Started

To get started, you need to install `brel` (better-releases&apos; main tool); if you don&apos;t want to, you can also run it with

```bash
npx brel@latest init
```

The init command will ask some question to setup the base config and will generate the base workflow for Github Actions. Here&apos;s what the config file might look like

```toml
default_branch = &quot;main&quot;
```

But we can do so much better than that.

&lt;Callout&gt;
  When modifying the config, you will need to rerun the init command to update
  the workflow file
&lt;/Callout&gt;

### Bumping Package versions

Honestly, this feature is why I decided to build this tool: I wanted to be able to update versions across toml and json files (Rust, Javascript, PHP) with a single tool. Let&apos;s see how we can do this.

For a **package.json** file, we might want to bump the **version** field; with Brel, you can just do:

```toml
[release_pr.version_updates]
&quot;package.json&quot; = [&quot;version&quot;]
```

But that&apos;s not all! Imagine you want to update the version inside a lock file (though you probably shouldn&apos;t do this in CI), you can also pass selectors when selecting an array:

```toml
[release_pr.version_updates]
&quot;Cargo.toml&quot; = [&quot;package.version&quot;]
&quot;Cargo.lock&quot; = [&quot;package[name=brel].version&quot;]
```

You might notice there&apos;s a **.lock** file in there; you can override the filetype like this:

```toml
[release_pr.format_overrides]
&quot;Cargo.lock&quot; = &quot;toml&quot;
```

### Pushing a Tag

Let&apos;s say we want to add automatic tag creation when merging the release branch; we can do so with a few config changes

```toml
[release_pr.tagging]
enabled = true
tag_template = &quot;{version}&quot;
```

With this setup, merging the branch will create a tag corresponding to the version, like **0.7.0**

&lt;Callout type=&apos;warning&apos;&gt;
  To actually enable release tagging, you need to provide a token allowed to do
  so under the secret name BREL_TAG_PUSH_TOKEN
&lt;/Callout&gt;

### Generating/Updating a Changelog

When enabled, Brel integrates with [Git Cliff](https://git-cliff.org/) to automatically update a **CHANGELOG.MD** file. Here&apos;s how you might configure this:

```toml
[release_pr.changelog]
enabled = true
output_file = &quot;CHANGELOG.md&quot;
```

This will automatically update the **CHANGELOG.md** file using [Git Cliff](https://git-cliff.org)

### There&apos;s more

I will not bore you with everything Brel can do (you can even customize the template used for the pr body), but if you want to give it a try here&apos;s [the official website](https://better-releases.com) and the [github repo](https://github.com/better-releases/brel); do try it out if you have need of a tool like this and let me know what you think!</content:encoded></item><item><title>Making an RSS Feed for a Nuxt Website</title><link>https://matteogassend.com/articles/nuxt-rss-feed/</link><guid isPermaLink="true">https://matteogassend.com/articles/nuxt-rss-feed/</guid><pubDate>Thu, 18 Sep 2025 00:00:00 GMT</pubDate><content:encoded>I recently remade my website (I know, I know) and I got a surprise when getting to reimplement an rss feed because, while [Astro](https://astro.build) has a module that helps with generating an rss feed, Nuxt doesn&apos;t - at least not for V3 and consequently V4. But worry not, for making one is easy enough !

## What is an RSS Feed ?

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&apos;s author and more info (wikipedia entry [here](https://en.wikipedia.org/wiki/RSS) if you want to learn more).

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

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:

- json feeds
- atom feeds

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

### The structure of an RSS Feed

Let&apos;s take a look at what an rss feed looks like (you can find mine [here](https://www.matteogassend.com/rss.xml)) - specifically the one for my website

```xml
&lt;rss version=&quot;2.0&quot;&gt;
  &lt;channel&gt;
    &lt;title&gt;Matteo&apos;s Log&lt;/title&gt;
    &lt;link&gt;https://www.matteogassend.com&lt;/link&gt;
    &lt;generator&gt;Nuxt &amp; Feed&lt;/generator&gt;
    &lt;language&gt;en&lt;/language&gt;
    &lt;atom:link href=&quot;https://www.matteogassend.com/rss.xml&quot; rel=&quot;self&quot; type=&quot;application/rss+xml&quot;/&gt;
    &lt;item&gt;
      &lt;title&gt;T3Chat Cloneathon Postmortem&lt;/title&gt;
      &lt;link&gt;
      https://www.matteogassend.com/articles/t3-cloneathon-postmortem
      &lt;/link&gt;
      &lt;guid isPermaLink=&quot;false&quot;&gt;t3-cloneathon-postmortem&lt;/guid&gt;
      &lt;pubDate&gt;Mon, 14 Jul 2025 00:00:00 GMT&lt;/pubDate&gt;
      &lt;description&gt;
      I participated in the T3Chat cloneathon; here&apos;s how it went
      &lt;/description&gt;
      &lt;content:encoded&gt;
        ...
      &lt;/content:encoded&gt;
    &lt;/item&gt;
  &lt;/channel&gt;
&lt;/rss&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).
We then get to the **channel** 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.
Then, for each element we want to add to our **channel**, we have a **item** 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).

## How to build one in Nuxt

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

### Declaring the API route

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

```ts
export default defineEventHandler(async (event) =&gt; {
  //generate the feed
  ...
  //return the actual feed data here
  return {}
})
```

### The RSS Feed enter the game

We **could** write the xml document by hand, but why bother? There exist the perfect package for this: [feed](https://github.com/jpmonette/feed). It allows use to programmatically build a feed and return it in different formats. Let&apos;s see how we can integrate it with Nitro.

First things first, let&apos;s install the dependency:

```sh
npm install feed
```

And let&apos;s start creating the feed itself; we&apos;ll begin by setting all the basic info needed for the feed and leave the items for later:

```ts
  const feed = new Feed({
    title: &quot;Matteo&apos;s Log&quot;,
    description: &quot;All the articles from matteogassend.com&quot;,
    id: &lt;a unique id&gt;,
    link: &lt;url of your website&gt;,
    language: &quot;en&quot;,
    copyright: `All rights reserved ${new Date().getFullYear()}, Matteo Gassend`,
    updated: new Date(),
    generator: &quot;Nuxt &amp; Feed&quot;,
    feedLinks: {
      rss: `&lt;url of your website&gt;/rss.xml`,
    },
    author: {
      name: &lt;name of the author&gt;,
    },
  });
```

With just this, we could generate an xml document and return it with the endpoint we made above. Let&apos;s do just that

```ts
export default defineEventHandler(async (event) =&gt; {
  ... // the feed is here

  return feed.rss2()
})
```

We only need two other things before we have a fully functional rss feed:

- informing the browser of the type of data we&apos;re sending
- inserting the actual items in the feed

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

```ts
setResponseHeader(event, &apos;content-type&apos;, &apos;text/xml&apos;)
```

This tells the browser we&apos;re sending back an xml document.

### Adding Items to the feed

Now that we have our basis covered, let&apos;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&apos;s do just that:

```ts
const articles = await queryCollection(event, &apos;articles&apos;)
  .order(&apos;publishDate&apos;, &apos;DESC&apos;)
  .all()
```

If you don&apos;t have a collection, just pretend this is an array of objects.

After that, it&apos;s as simple as looping over your data array and calling the `addItem` method on your feed instance:

```ts
articles.forEach((article) =&gt; {
  feed.addItem({
    title: article.title,
    id: article.slug,
    description: article.summary,
    date: new Date(article.publishDate),
    link: `${BASE_URL}/articles/${article.slug}`,
    content: article.rawbody,
  })
})
```

And that&apos;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 `rss2` call with `atom1` and you&apos;re done!

## Adding the Feed to the Website

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&apos;s say our rss feed lives at `/rss.xml`: if so, we&apos;ll need to add the following `link` tag to our page&apos;s `head`:

```html
&lt;link rel=&quot;alternate&quot; type=&quot;application/rss+xml&quot; title=&quot;Your feed&apos;s title&quot; href=`${BASE_URL}/rss.xml`
```

If you&apos;re using `unhead`, then this can also be done in a useHead composable hook using something like this:

```ts
useHead({
  link: [
    {
      rel: &apos;alternate&apos;,
      type: &apos;application/rss+xml&apos;,
      title: &quot;Matteo&apos;s Log&quot;,
      href: &apos;/rss.xml&apos;,
    },
    {
      rel: &apos;alternate&apos;,
      type: &apos;application/atom+xml&apos;,
      title: &quot;Matteo&apos;s Log&quot;,
      href: &apos;/atom&apos;,
    },
  ],
})
```

And you&apos;re officially done!</content:encoded></item><item><title>T3Chat Cloneathon Postmortem</title><link>https://matteogassend.com/articles/t3-cloneathon-postmortem/</link><guid isPermaLink="true">https://matteogassend.com/articles/t3-cloneathon-postmortem/</guid><pubDate>Mon, 14 Jul 2025 00:00:00 GMT</pubDate><content:encoded>## T3-Chat Hackathon

I decided to participate in the T3Chat cloneathon for two main reasons:

- I was bored and looking for something to do
- Money is always nice
  Although I didn&apos;t end up winning this experience still taught me a lot - both in software architecture and on Nuxt - and so I would like to share all of this in this article; enjoy!

You can find all the code for my submission [here](https://github.com/matfire/m3-chat)

## Stack

Because I wanted to challenge myself - and because I wanted an excuse to learn more about this framework - I decided to built the whole thing using Nuxt. This would allow me to not only learn by reading the docs but also learn by building something with the framework - which is what I usually prefer.

There are some other elements to this project that made it all possible:

- [Better Auth](https://better-auth.com): to handle authentication
- [Drizzle](https://orm.drizzle.team/): the ORM I used
- [Turso](https://turso.tech/): the db provider I chose
- [Zod](https://zod.dev/): for validation
- [Pusher](https://pusher.com/): for realtime notifications
- [Sockudo](https://sockudo.app/): the webserver I used locally
  If I had to justify them, here&apos;s what I would say. When learning something new, I usually try to only have to learn that thing - meaning I will try and use (where possible, obviously) tools I already know so that I don&apos;t have to split my attention too many ways. Amongst the tools I mentioned above, the only one I hadn&apos;t used in a &quot;production&quot; project yet were better-auth and sockudo; both of them were a breeze to setup and use, so no complaints there.

## Architecting the solution

### The different sections

Let&apos;s take a look at the objectives I had when building my submission:

- multiple providers and models (required)
- authentication and chat sync (required)
- syntax highlighting (nice to have)
- resumable stream (nice to have)
- bring your own key (nice to have)
  Based on all of these requirements, here&apos;s what I came up with architecture-wise.
  ![architecture schema of m3chat with data flows in each direction](https://cdn.blog.matteogassend.com/m3chat_parts_schema.webp)

Let&apos;s break this down; we have the two obvious sections that you would expect in this kind of scenario: the app and the database. But there was one issue that I couldn&apos;t (or wouldn&apos;t) solve with just these two elements; realtime and resumable streams. To achieve this I needed a way to decouple the data received by the client from the http request and response they would receive when hitting that endpoint - there needed to be a third service that would handle receiving data and transmitting it to the right user (or channel).

This ended up being a pusher-compatible service called sockudo in development and Pusher Channels in production.

### What happens when a message is generated

When a message is generated a few things happen:

- the message sent by the user is stored in the database
- we fire off the request to the select provider with all the data
- we return the status to the user immediately

But I hear you asking:

&gt; but how can you return immediately if the provider is not done generating the response?

This is thanks to Nuxt&apos;s `waitUntil` method that allows us to return earlier but continue executing in the background. This means the client can continue doing its thing (i.e. redirect to the chat page) while still doing it needs to do. This allows, in turn, to allow for resubscribing to the realtime events for a chat thread after navigation or a page reload.

### Converting the markdown

Honestly, this section should have been easy. As you might have already read on this blog, I really like the [unified](https://unifiedjs.com/) ecosystem, so I though I&apos;d just bring in my setup and call it a day. Unfortunately, there were some performance issues (probably due to the rate of change of the text) that caused me to spend way too long on this than initially planned. I even tried moving the whole rendering logic to a web worker but couldn&apos;t manage to make it work in a timely manner. Fortunately that was a problem _only_ when rendering new text, as when the new responses were done generating they were rendered to html and stored in the database - otherwise the app would have frozen for a lot longer than it ended up doing (silver lining, I guess?)

## What I would do differently

Honestly, I had a lot of fun doing this cloneathon! It forced me to solve problems I don&apos;t normally worry about in my day job - or my side projects for that matter - and to start learning new things. And because hindsight is always 20/20, I thought it might be interesting to recap some of the &quot;lessons&quot; I learnt while working on M3Chat

### Too Much Too Young Too Fast

While I&apos;m happy with the solution I came up with for having multiple providers and models, it was way too overkill and I spent way too much time on it. While this point might not stick if I were making a product I&apos;d take to market, the solution I came up with was way too complex and caused some issues with type safety when crossing from client to server and from server to client. I could have definitely used the time to work on something else

### The Writing on The Wall

The markdown rendering section is one of the things I&apos;m less proud of in this project. While the markdown renders correctly - most of the time - it takes way too long on the client and causes the app to freeze during the parser initialization. I can see two ways in which I could have avoided this:

- moving the parser to a separate process - a separate web worker
- initializing the parser on app load, where the freezing might not have been as apparent
  If I had to do it again, I would definitely choose the first option

### Separate Ways (Worlds Apart)

This point probably also stems from the time limit and - though I&apos;m not using that as an excuse - I do hope I could have done this differently. When writing the code for generating the llm response I decided to put most of it - including broadcasting the data back to the user - inside the api handler. This is a **bad** design decision that I regret to this day, because it made it way harder than it should have been to change this logic. If I had to redo this in Nuxt, I&apos;d probably use their event system to isolate all these different behaviors (you can read more about it [here](https://nuxt.com/docs/guide/going-further/events))</content:encoded></item><item><title>Developing and Compiling Webapps with Vite and Go</title><link>https://matteogassend.com/articles/go-webapp-vite/</link><guid isPermaLink="true">https://matteogassend.com/articles/go-webapp-vite/</guid><pubDate>Sat, 10 May 2025 00:00:00 GMT</pubDate><content:encoded>import Callout from &apos;../../components/mdx/Callout.astro&apos;

I&apos;ve recently been playing around with Go (and RPC) and have stumbled upon the `embed` package, which allows us to specify files and directory to be **embedded** (hence the name) into the final binary. You would usually use this to embed something like the db schema / migrations, but there&apos;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 ?

## Bundling the assets

Let&apos;s consider that we already have a Vite application and that we have already run an `npm run build` to generate its bundle and so we have a `dist` folder where all our front assets are.

If we have a standard http mux, we could simply add a `Handle` function. Let&apos;s see what that would look like:

```go
// this file could be called front_prod.go

//go:embed dist/*
var embedFS embed.FS

func Front(mux *http.ServeMux) {
	staticFiles, err := fs.Sub(embedFS, &quot;dist&quot;)
	if err != nil {
		log.Fatal(err)
	}
	fileServer := http.FileServer(http.FS(staticFiles))
	mux.Handle(&quot;/&quot;, fileServer)
	log.Println(&quot;Serving static files from embedded filesystem&quot;)
}
```

Let&apos;s break this down: most of the magic happens on these lines:

```go
//go:embed dist/*
var embedFS embed.FS
```

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

![gif](https://media2.giphy.com/media/v1.Y2lkPWYyOTYwODM3Mmd1ZGxyY3Y3NDFsYjFrMnQ3cDYweW52azdnbmN2bDh2ajRlZTl3ZyZlcD12MV9naWZzX3NlYXJjaCZjdD1n/8UF0EXzsc0Ckg/giphy.gif)

### Caveats

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

## What about during development ?

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 ?

![gif](https://media4.giphy.com/media/v1.Y2lkPWYyOTYwODM3ZGp2NHU2MXRvOWp6dm5wc2ZlNW8zYTNoYTk4MXdibGd5dm85bW8yaSZlcD12MV9naWZzX3NlYXJjaCZjdD1n/QqdyVT8H6uJ32/giphy.gif)

### Writing the Vite Proxy

This is the (mostly) the whole code we&apos;ll need to do this:

```go
// this file could be called front_dev.go

func createViteProxy(target string) http.Handler {
	url, err := url.Parse(target)
	if err != nil {
		log.Fatal(err)
	}
	proxy := httputil.NewSingleHostReverseProxy(url)
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// Update the request to point to the Vite server
		r.Host = url.Host
		r.URL.Scheme = url.Scheme
		r.URL.Host = url.Host
		proxy.ServeHTTP(w, r)
	})
}

func Front(mux *http.ServeMux) {
	viteProxy := createViteProxy(&quot;http://localhost:5173&quot;)
	mux.Handle(&quot;/&quot;, viteProxy)
	log.Info(&quot;proxying requests to vite dev server&quot;)
}
```

Let&apos;s break it down!

We have the same function signature for the `Front` function

&lt;Callout&gt;Remember this, it will be important later on&lt;/Callout&gt;
The magic happens with the `createViteProxy` function, where we return a new
`http.HandlerFunc` that sends the request through a `SingleHostReverseProxy`
that points to the Vite dev server. This means that all requests the `Front`
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.

## How Do You Switch Between the Two ?

Here&apos;s the fun part: you let the compiler do it!
![gif](https://media1.giphy.com/media/v1.Y2lkPWYyOTYwODM3eTkwYzAwbW45N2NhbzB3OW5jODVkZXdoYnd6eW5jcmE4cjd0MWQ4cCZlcD12MV9naWZzX3NlYXJjaCZjdD1n/w36LqLo57gmvXa7wjf/giphy.gif)

On its latest versions, Go supports a feature called **build constraints**. These are values that you can pass your `go build` command and that can influence which files the compiler will include.
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:

```go
//go:build dev
//+build dev

// the rest of the code goes here
```

The two comments above are the two syntaxes for **build constraints**. These tell the compiler to only include this file if this constraint is passed to the `build` function: here&apos;s how you would run this:

```bash
go build -tags dev
```

Once you add the reverse condition on the &quot;production&quot; logic, you&apos;re done!

&lt;Callout&gt;you can just add a ! before the constraint you&apos;re checking&lt;/Callout&gt;

This is why I said it would be important to name the `Front` 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!

## Should you do this ?

While this might almost look like magic, there are cases in which this might not be the ideal choice:

- If you need more complex routing that just redirecting to `index.html`

While I have addressed this a bit in the related section, I just want to point this out again.

- separation of concerns and scalability

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.

Still, there are some cases where you might want this:

- offline-first

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&apos;re connecting to external services, obviously)

- simple deploy

It&apos;s a binary.

- great DX

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

![gif](https://media2.giphy.com/media/v1.Y2lkPWYyOTYwODM3Z2hmYnV6c25jbG15OWxuYjhlN3BhNG4xdWlhN2UzOWo4anJ6Zmg5dSZlcD12MV9naWZzX3NlYXJjaCZjdD1n/u11Wy6B5XaR2ZPcbgP/giphy.gif)</content:encoded></item><item><title>Custom markdown components with remark and web components</title><link>https://matteogassend.com/articles/remark-directives-web-components/</link><guid isPermaLink="true">https://matteogassend.com/articles/remark-directives-web-components/</guid><pubDate>Fri, 21 Feb 2025 00:00:00 GMT</pubDate><content:encoded>import Callout from &apos;../../components/mdx/Callout.astro&apos;

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!
![gif](https://media1.giphy.com/media/6ILjOfJ1oL7NAc9SQ7/giphy.gif?cid=f2960837etcgu0a7li1snlbmpcitttn8gwa3uuallr2l3cnr&amp;ep=v1_gifs_search&amp;rid=giphy.gif&amp;ct=g)

## Porting Data

Everything is markdown in Hugo (mostly), so transitioning to and from was pretty simple; just import the data, build the collections and we&apos;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&apos;t hard and went on without many issues.

## Where are my shortcodes ?

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;stackblitz url goes here&gt;}}` That line right there would then be mapped to a component called `stackblitz` 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.

Astro uses [remark](https://github.com/remarkjs/remark) and [rehype](https://github.com/rehypejs/rehype) to transform your markdown collections into html.

&lt;Callout type=&apos;info&apos;&gt;
  I&apos;ve already written about the unified ecosystem, you can checkout that
  article [here](/articles/markdown-editor)
&lt;/Callout&gt;

That means that we can hopefully build something within this pipeline and recreate the feeling of shortcodes.

## Enter Remark Directive

Turns out we (kind of) can! There&apos;s a remark plugin called [remark-directive](https://github.com/remarkjs/remark-directive) 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 &quot;does something with them&quot;.

### What do we do with a remark directive ?

Let&apos;s take a small look at the source of one of my articles:

```md
Astro uses [remark](https://github.com/remarkjs/remark) and [rehype](https://github.com/rehypejs/rehype) to transform your markdown collections into html.

:::callout(type=&quot;info&quot;)
I&apos;ve already written about the unified ecosystem, you can checkout that article [here](/articles/markdown-editor)
:::
```

The thing with the 3 `:` 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&apos;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.

For example, the following snippet

```md
::stackblitz{#mg-fbr-express}
```

Contains a node that is a `leafDirective` with an id of `mg-fbr-express`

&lt;Callout type=&quot;info&quot;&gt;
the attributes section of the directive supports multiple elements separated by a whitespace like this: `::codesandbox{projectType=&quot;devbox&quot; projectid=&quot;cwmtww&quot;}`
&lt;/Callout&gt;

Getting the node&apos;s attributes is pretty straightforward from there: they&apos;re in

```js
node.attributes
```

And now we have everything we need to create our custom elements!
![gif](https://media0.giphy.com/media/JqDeI2yjpSRgdh35oe/giphy.gif?cid=f2960837ca3fze65qalodvef567jdndkvphsm3vgoj9s7pn4&amp;ep=v1_gifs_search&amp;rid=giphy.gif&amp;ct=g)

## Web Components

Web Components are not new (they have been baseline available since 2021) but I don&apos;t think I&apos;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.

### The Good

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

```ts
class Youtube extends HTMLElement {
  constructor() {
    super()
  }

  connectedCallback() {
    this.render()
  }

  render() {
    const iframe = document.createElement(&apos;iframe&apos;)
    iframe.src = `https://www.youtube.com/embed/${this.getAttribute(&apos;videoid&apos;)}`
    this.innerHTML = `&lt;div style=&quot;width: 100%; min-height: 500px;&quot;&gt;&lt;iframe class=&quot;w-full min-h-[500px]&quot; src=&quot;https://www.youtube.com/embed/${this.getAttribute(
      &apos;videoId&apos;,
    )}&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; allowfullscreen&gt;&lt;/iframe&gt;&lt;/div&gt;`
  }
}

customElements.define(&apos;wc-youtube&apos;, Youtube)
```

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.

Notice the last line; that&apos;s where we tell the browser that there&apos;s a new valid tag (called `wc-youtube`) that is represented by the `Youtube` class.

![gif](https://media0.giphy.com/media/LxPsfUhFxwRRC/giphy.gif?cid=f2960837b5zq4z3p6qik8aq7hbhzs9kfqigcpoglab2662be&amp;ep=v1_gifs_search&amp;rid=giphy.gif&amp;ct=g)

### The Bad

Well, if web components are so nice, why is everyone bothering with frontend frameworks ?

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 &quot;grinds my gears&quot; about web components is that they are encapsulated; while this has some benefits in my use case this caused quite some headaches.

I tried for quite some time to have TailwindCSS (which I&apos;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?).

## The Compromise

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

## How can you do this?

### The Markdown

Using Astro, you can modify the config file to load any custom remark and rehype extensions, so first thing we&apos;ll need to do is install [remark-directive](https://github.com/remarkjs/remark-directive) and add it to your astro config, which could look something like this:

```json
  markdown: {
    remarkPlugins: [
      remarkDirective
    ]
  }
```

Once that is done, we can start adding our custom detection logic

&lt;Callout type=&apos;warning&apos;&gt;
  make sure you add this logic after the remarkDirective inclusion!
&lt;/Callout&gt;

Let&apos;s take the example I made above for Stackblitz: the detection logic could be something like this:

```js
function() {
    return function(tree, file) {
        visit(tree, function (node) {
            if (
            node.type == &apos;containerDirective&apos; ||
            node.type == &apos;leafDirective&apos;
        ) {
            const data = node.data || (node.data = {})
            const attributes = node.attributes || {}
            const id = attributes.id
            switch (node.name) {
            //you could add more types right here
            case &apos;stackblitz&apos;:
                if (!id) {
                    file.fail(&apos;Unexpected missing `id` on `stackblitz` directive&apos;, node)
                }
                data.hName = &apos;wc-stackblitz&apos;
                data.hProperties = {
                projectid: id
                }
                break
            }
        }
        })
    }
}
```

And that&apos;s all you need to detect a basic directive in markdown to a custom html tag!

### The HTML

Now that the element is inserted into the abstract tree of the page, it will be renderer with all the data you have attached to it (in case of the stackblitz example, just an id). All that&apos;s left is to write the webcomponent to handle that tag. Once that is done, you just need to make sure to include it in the required pages.

&lt;Callout type=&apos;info&apos;&gt;
  in my case, I just added them to the page responsible for showing any blog
  post
&lt;/Callout&gt;

![](https://media0.giphy.com/media/v1.Y2lkPTc5MGI3NjExMXg5eWhqaWUycXI1YjN4amZjbXB1cDVkNGdqdnJyOGtsdTZ1eXF2bCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/8UF0EXzsc0Ckg/giphy.gif)

## Should you do this?

Honestly I don&apos;t know; this &quot;stack&quot; 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.</content:encoded></item><item><title>How to rebuilt a personal website</title><link>https://matteogassend.com/articles/how-to-rebuild-a-personal-website/</link><guid isPermaLink="true">https://matteogassend.com/articles/how-to-rebuild-a-personal-website/</guid><pubDate>Sun, 15 Dec 2024 00:00:00 GMT</pubDate><content:encoded>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&apos;ve learned along the way.

![gif](https://media2.giphy.com/media/hvd92D03IenPrCpzWR/giphy.gif?cid=f2960837tltc3535kqhvy887jxy6o62qs41erkbxs83qetdi&amp;ep=v1_gifs_search&amp;rid=giphy.gif&amp;ct=g)

## Timeline

I think there are mainly 3 major releases:

- Astro
- Laravel
- Hugo (the current one)

## Astro

[Astro](https://astro.build/) 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 `iframe` 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.

## Laravel

Now, I hear you asking:
![gif](https://media3.giphy.com/media/3cXmze4Y8igXdnkc3U/giphy.gif?cid=f2960837qsynhqxw9po9vp14n1eobkxmvjyrzmrmdpunidzl&amp;ep=v1_gifs_search&amp;rid=giphy.gif&amp;ct=g)

The honest answer to that is: **why not?** I believe that if you want to try new technologies (and can afford to - meaning probably don&apos;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.

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

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.

## Hugo

And thus we get to [Hugo](https://gohugo.io/). I had heard of it in passing but had never really looked into it - so I finally did; it&apos;s pretty cool. It:

- supports md files natively
- can handle tags and series automatically (taxonomies FTW)
  And best of all, it just produces a `public` 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&apos;m not against rewriting all of this at some point)

### Overengineering simplicity

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

- client-only search using Lunr and AlpineJS
- automatic og (opengraph) image generation
- automatic photoswipe image rendering

And these are the potential things I might add later:

- newsletter subscription section
- automatic bluesky post with the new article
- and so much more

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)</content:encoded></item><item><title>Dotfiles, the nix way</title><link>https://matteogassend.com/articles/dotfiles-the-nix-way/</link><guid isPermaLink="true">https://matteogassend.com/articles/dotfiles-the-nix-way/</guid><pubDate>Wed, 30 Oct 2024 00:00:00 GMT</pubDate><content:encoded>I&apos;ve recently started exploring the [Nix](https://nixos.org/) ecosystem (I got hooked by [Vimjoyer&apos;s videos](https://www.youtube.com/@vimjoyer)) and decided to give it a try (I even installed NixOS on my laptop, but that will come in a later article).

![gif](https://media4.giphy.com/media/8eA8fB7hHdChEhIMgr/giphy.gif?cid=f2960837j91h7gj7gjmnpzlz379mtvmm0o0y1uujhrz408v1&amp;ep=v1_gifs_search&amp;rid=giphy.gif&amp;ct=g)

## Dotfiles

First things first, what are dotfiles ? They are basically configuration files that are usually preceded by a `.`. or that are usually kept inside a folder called `.config` 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...
![gif](https://media2.giphy.com/media/l3V0H7bYv5Ml5TOfu/giphy.gif?cid=f296083778rbz3p1i0jpneymxumsf63m8vanqzgokqm2avn0&amp;ep=v1_gifs_search&amp;rid=giphy.gif&amp;ct=g)

Ok, let&apos;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&apos;d probably want to customize to be more &quot;you&quot; or to better fit your needs. Here&apos;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).

Here&apos;s how you would configure `rofi` to use Catppuccin Mocha:

- create a `mocha.rasi` file that will contain all the theme customization rule
- create a `config.rasi` file in `.config/rofi/` that will contain the import rule for the theme you want: `@theme &quot;catppuccin-mocha&quot;`
  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.

## My Journey with Dotfiles

### Step 1: files ending in dots

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 `.config` directory. That phase ended quickly once I mistakenly wiped my OS and lost all said files...

Happens to the best of us, right?
![gif](https://media3.giphy.com/media/G5X63GrrLjjVK/giphy.gif?cid=f2960837rk8zjuyd07iq78uy6hgawi3k2x1ymhupvlxuewg3&amp;ep=v1_gifs_search&amp;rid=giphy.gif&amp;ct=g)

### Step 2: version control for the win

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

### Step 3: Stowing

So now you have a git repo containing all your dotfiles but cannot directly clone into to replace your `.config` folder, what do you do? You find out there&apos;s a tool that could fix all these problems: [GNU Stow](https://www.gnu.org/software/stow/)!

This tool allows you to create symlinks to anywhere and keep all your original files in a single folder (sound familiar?)

I&apos;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.

### Step 4: Manage your home

I&apos;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&apos;s when I heard about [home-manager](https://github.com/nix-community/home-manager) and decided to give it a try.

## Home Manager

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).

Let&apos;s take a look at a simple home-manager `home.nix` file:

```nix
{config, pkgs, ...}
{
	home.stateVersion = &quot;24.05&quot;;
	home.packages = [
		pkgs.fzf
		pkgs.tmuxifier
	];
	home.file = [
		&quot;.config/waybar/mocha.css&quot;.source= ./styles/mocha.css;
	];
}
```

This little configuration will ensure that both fzf and tmuxifier are on your user&apos;s $PATH and copies over a css file from your directory into the appropriate `.config` directory.

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

```nix
  programs.kitty = {
    enable = true;
    settings = {
      &quot;tab_bar_min_tabs&quot; = 1;
      &quot;tab_bar_edge&quot; = &quot;bottom&quot;;
      &quot;tab_bar_style&quot; = &quot;powerline&quot;;
      &quot;tab_powerline_style&quot; = &quot;slanted&quot;;
      &quot;tab_title_template&quot;= &quot;{title}{&apos; :{}:&apos;.format(num_windows) if num_windows &gt; 1 else &apos;&apos;}&quot;;
    };
    font = {
	name = &quot;JetBrainsMono Nerd Font&quot;;
    };
    themeFile = &quot;Catppuccin-Mocha&quot;;
  };
```

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.

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

```nix
programs.tmux = {
    enable = true;
    plugins = with pkgs.tmuxPlugins; [
      catppuccin
    ];
    extraConfig = &apos;&apos;
      set -g prefix C-s
      set -g mouse on
      set-option -g status-position top

      set -g @plugin &apos;tmux-plugins/tpm&apos;
      set -g @catppuccin_flavour &apos;macchiato&apos;
      set -g @plugin &apos;kenos1/tmux-cht-sh&apos;
      set -g @plugin &apos;jimeh/tmuxifier&apos;
      set -g @catppuccin_window_left_separator &quot;&quot;
      set -g @catppuccin_window_right_separator &quot; &quot;
      set -g @catppuccin_window_middle_separator &quot; █&quot;
      set -g @catppuccin_window_number_position &quot;right&quot;

      set -g @catppuccin_window_default_fill &quot;number&quot;
      set -g @catppuccin_window_default_text &quot;#W&quot;

      set -g @catppuccin_window_current_fill &quot;number&quot;
      set -g @catppuccin_window_current_text &quot;#W&quot;

      set -g @catppuccin_status_modules_right &quot;directory user host session&quot;
      set -g @catppuccin_status_left_separator  &quot; &quot;
      set -g @catppuccin_status_right_separator &quot;&quot;
      set -g @catppuccin_status_right_separator_inverse &quot;no&quot;
      set -g @catppuccin_status_fill &quot;icon&quot;
      set -g @catppuccin_status_connect_separator &quot;no&quot;

      set -g @catppuccin_directory_text &quot;#{pane_current_path}&quot;
      run &apos;~/.tmux/plugins/tpm/tpm&apos;
    &apos;&apos;;
  };
```

This configuration here allows you to see how you can pass &quot;raw&quot; configuration values via **extraConfig** and how to setup plugins taken directly from nixpackages.

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 `.config` folder. This goes in a bit deeper in the Nix DSL, but I think it still keeps pretty readable:

```nix
let
  neovim_config = pkgs.fetchFromGitHub {
    owner = &quot;matfire&quot;;
    repo = &quot;config.nvim&quot;;
    rev = &quot;044cb8670307bb3195607b90229f26ffcb61d46d&quot;;
    sha256 = &quot;sha256-tzWcYrRGX82kBmPc50LjQmXfNjBoT/sLNanH470NuAE=&quot;;
  };
in
{
	home.file = {
		&quot;.config/nvim&quot;.source = neovim_config;
	};
}
```

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.

![gif](https://media4.giphy.com/media/BWBinNlvWh1oQpTk4c/giphy.gif?cid=f2960837os6ydzc8iqv7ae64hpqvkaed7cye2ilf2n4hgjjo&amp;ep=v1_gifs_search&amp;rid=giphy.gif&amp;ct=g)

## The best of both worlds ?

_Personally_, 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&apos;ll get the same programs &amp; configs each time.

But it still requires you to learn a new language (or the basics of it, at least) and it&apos;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.

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.</content:encoded></item><item><title>Build a reverse ssh tunnel reverse proxy for (not) fun and (not) profit</title><link>https://matteogassend.com/articles/build-a-reverse-ssh-tunnel-reverse-proxy-for-(not)-fun-and-(not)-profit/</link><guid isPermaLink="true">https://matteogassend.com/articles/build-a-reverse-ssh-tunnel-reverse-proxy-for-(not)-fun-and-(not)-profit/</guid><pubDate>Mon, 03 Jun 2024 00:00:00 GMT</pubDate><content:encoded>import Youtube from &apos;../../components/mdx/Youtube.astro&apos;

A few years ago, a team I was part of was working on [vivi](https://matteogassend.com/projects/vivi/), our master thesis&apos;s project. You can read more about it in the link above if you&apos;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.

![Didnt Work The Blob GIF by Max](https://media1.giphy.com/media/JXOl4rFwoJJks7xLAr/giphy.gif?cid=bcfb6944oirknboxyvfaahrhp1g2itqk326lebymiiz76mhf&amp;ep=v1_gifs_search&amp;rid=giphy.gif&amp;ct=g)

## Where does a reverse ssh tunnel reverse proxy fit into this

### Context

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 [balena os](https://www.balena.io/) - which would have worked fine had we not needed to work with the network stack of the device.

### Build it yourself

So that&apos;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:

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

### NAT and local ip addresses

Fasterthanlime has an excellent video describing how the internet works, you should check it out

&lt;Youtube id=&apos;jjKFXlFNR4E&apos; /&gt;

### A (forward) ssh tunnel

A normal (or forward) SSH tunnel allows you to securely access a remote service over an encrypted SSH connection.

For example, let&apos;s say you want to access a web service running on a remote server&apos;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:
you can run the command:

```bash
ssh -L 9000:localhost:8000 user@remote_server
```

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

![a schema of the logic behind ssh tunneling](https://cdn.blog.matteogassend.com/reverse-ssh_ssh-tunnel-schema.webp)

### Let&apos;s reverse that

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:

- I don&apos;t know the machine&apos;s actual IP address
- I cannot (usually) initiate a connection from outside the local network without an exposed port or something similar

And so, enters the reverse ssh tunnel.
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 &quot;tunnel&quot; toward the initiating machine. This allows to bypass the issues with firewalls and NATs listed above.

For example, to tunnel the SSH port (22) of a remote machine to port 8022 on your local machine, the command would be:

```bash
ssh -R 8022:localhost:22 user@local_host -N
```

## How we built it (attention: do not try this at home, there are easier ways to do this)

There are a couple of part to this setup:

- The http server onboard each device
- The systemd service responsible for the ssh tunnel on each device
- The Server handling all of these connections and all the reverse proxy logic

### Http Server

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...

It was a simple NestJS application.

### Systemd Service

The systemd service was responsible for receiving a port from the server and configuring the reverse ssh-tunnel to connect to said port.

### Remote Server

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.

The code is available [here](https://github.com/vivitek/OpenVivi) if you want to take a look at it (though I would not recommend it)

The steps were:

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

## Why you shouldn&apos;t do it this way

The main issue with the way we did this is security; there really is no central authority managing this.

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

## Alternatives

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).

Really, a vpn would probably have simplified a lot of things for this use case</content:encoded></item><item><title>Using Shadcn-ui with Inertia and AdonisJS</title><link>https://matteogassend.com/articles/using-shadcn-ui-with-inertia-and-adonisjs/</link><guid isPermaLink="true">https://matteogassend.com/articles/using-shadcn-ui-with-inertia-and-adonisjs/</guid><pubDate>Tue, 26 Mar 2024 00:00:00 GMT</pubDate><content:encoded>import CodeSandbox from &apos;../../components/mdx/CodeSandbox.astro&apos;

Since [AdonisJS](https://adonisjs.com) V6 has experimental support for [Inertia](https://inertiajs.com), I thought it might be a good idea to see if I could configure [shadcn/ui](https://ui.shadcn.com) to run with the aforementioned stack.

## Configuring Inertia with AdonisJS

Since support is experimental, I&apos;ll refer you to [Adonis](https://docs.adonisjs.com/guides/inertia#installation)&apos;s documentation on this procedure. If you&apos;re starting from scratch, it may be as simple as installing their inertia starter kit.

## Installing Shadcn/ui

To install [shadcn/ui](https://ui.shadcn.com), you can follow the basic instructions for Vite-based projects, but we&apos;ll need to adapt a few steps.
The one thing to keep in mind is that you need to specifiy inertia&apos;s css file as the `global CSS file` during setup.

### Where to store components and libs

I would personally advice (also, this is what I tested) to store all shadcn related files inside the `inertia` folder; i personally created a `lib` folder and had shadcn install everything in there.

### tsconfig

The documentation asks us to create aliases in both the `tsconfig` file and the `vite.config.{js|ts}` 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.
You can definitely stop here for this step and modify the imported components to use a normal path instead of the aliases it includes.

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

Here&apos;s, for example, my root project tsconfig:

```js
{
  compilerOptions: {
    paths: {
      //other paths can go here
      &quot;@/*&quot;: [&quot;./inertia/lib/*&quot;]
    }
  }
}
```

which then translates inside the `inertia` folder to:

```js
compilerOptions: {
  paths: {
    // other paths here
    &quot;@/*&quot;: [&quot;./lib/*&quot;]
  }
}
```

as for the vite config, here&apos;s the interesting bit:

```js
{
  resolve: {
    alias: {
      // other aliases can go here
      &apos;@&apos;: `${getDirname(import.meta.url)}/inertia/lib`
    }
  }
}
```

### Tailwind Config

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.
The only thing to modify is the `content` array. If you&apos;re only using inertia (with React, in this case), you can probably use something like this:

```js
{
  content: [&apos;./inertia/**/*.{js,ts,jsx,tsx}&apos;]
}
```

## Conclusion

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;CodeSandbox projectType=&apos;devbox&apos; projectid=&apos;adonis-shadcn-rntkn3&apos; /&gt;</content:encoded></item><item><title>Building a dynamic form with Svelte and Typescript</title><link>https://matteogassend.com/articles/building-a-dynamic-form-with-svelte-and-typescript/</link><guid isPermaLink="true">https://matteogassend.com/articles/building-a-dynamic-form-with-svelte-and-typescript/</guid><pubDate>Fri, 23 Feb 2024 00:00:00 GMT</pubDate><content:encoded>In the quest to make [magiedit](https://magiedit.magitools.app) always better, I figured out that people may want to publish to multiple &quot;accounts&quot; 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 **Publisher** for, fill in the required data, and be able to publish to it.
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?

## Generating a form from a class

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 `select`, 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.

### Describing form elements

After some deliberation, I settled on the following structure:

```typescript
type IPlatformSetting = {
  type: string
  name: string
  label: { htmlFor: string; value: string }
  settings: Record&lt;string, unknown&gt;
}
```

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

```typescript
{
	type: &apos;input&apos;,
	name: &apos;api_token&apos;,
	label: { htmlFor: &apos;api_key&apos;, value: &apos;API Key&apos; },
	settings: { type: &apos;text&apos;, required: true }
}
```

There still is a bit of information duplication, but for now it works pretty well.

### Displaying the form elements

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.
With Sveltekit, this could look somthing like this:

```js
{#if setting.type === &apos;input&apos;}
  &lt;Label for={setting.label.htmlFor}&gt;{setting.label.value}&lt;/Label&gt;
  &lt;Input id={setting.name} name={setting.name} {...setting.settings} /&gt;
{/if}
```

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).

### Handling submission

This next step is probably the easiest. Since you&apos;re also sending the select platform **template**, you can reference that to determine if the data is valid (why not try [superforms](https://superforms.rocks)? I made an adapter for it).

With no validation whatsoever, it could look something like this:

```js
const formData = await request.formData()
const { publisher_id, publisher_name, ...args } = Object.fromEntries(formData)
await db.insert(userPublications).values({
  publisherName: publisher_id,
  publisherData: args,
  name: publisher_name,
  userId: user.id,
})
return { message: &apos;ok&apos; }
```</content:encoded></item><item><title>How filesystem-based routers work: building one for express</title><link>https://matteogassend.com/articles/file-based-router/</link><guid isPermaLink="true">https://matteogassend.com/articles/file-based-router/</guid><pubDate>Sun, 22 Oct 2023 00:00:00 GMT</pubDate><content:encoded>import Stackblitz from &apos;../../components/mdx/Stackblitz.astro&apos;

Have you ever wondered how Next, Sveltekit, Nuxt (even Expo now) do their routing? What&apos;s the magic that makes it so when you create a file called `+page.svelte` in a directory called `routes`, just for it to magically works? Well, wonder no more! I&apos;ll show you how to build your own file-based router for [express](https://expressjs.com/) in Javascript, though the concepts we&apos;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&apos;m not yet ready to step - and write - on). Let&apos;s get to it!

![GIF by MIRAMAX](https://media0.giphy.com/media/W5WwFpEtd5Tvq/giphy.gif?cid=bcfb6944t52lnri18evg0ro1r8f5vl3fjaxm54dhbeqpceom&amp;ep=v1_gifs_search&amp;rid=giphy.gif&amp;ct=g)

## How file routing works

First, let&apos;s start by saying what file-based routing is and how it works.
Usually, a function will loop through the `routes` folder (ever noticed how you always have a single place where you place your `pages`? 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 `+page.svelte`) 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&apos;s how it&apos;ll work:
![file based structure explainer](https://cdn.blog.matteogassend.com/file-based-router-structure.png)

This means that when we&apos;ll call our function, it will step through our route directory and generate a separate route for each filename: `user.js` will create `/user` for example.

## Building a file-based router

### Initial Setup

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

```sh
npm install --save express
```

and let&apos;s initialize the express application in `index.js`

```js
const express = require(&apos;express&apos;)

const app = express()

app.listen(4000, () =&gt; {
  console.log(&apos;listening&apos;)
})
```

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&apos;s see how we should define our route file; inside our `routes` folder, let&apos;s create a `user.route.js` 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&apos;ll be writing a regex - just one, I promise)!

![Hacker Deal With It GIF by Sleeping Giant Media](https://media1.giphy.com/media/mYhd1NHQkHmZLiqN7M/giphy.gif?cid=bcfb6944cxheq2gck8p5iorudqqvis1ad0z2o4aathprma1b&amp;ep=v1_gifs_search&amp;rid=giphy.gif&amp;ct=g)

### Making a route

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

So, let&apos;s say we have a `user.route.js` file containing the following:

```js
const router = require(&apos;express&apos;).Router()

router.get(&apos;/&apos;, (req, res) =&gt; {
  res.send(&apos;test&apos;)
})

router.get(&apos;/:id&apos;, (req, res) =&gt; {
  res.send(`test ${req.params.id}`)
})

module.exports = router
```

When our loader will be finished, this should generate two routes:

- /user
- /user/:id (this one will match /user/1, /user/2 etc)

### Making the loader

Now it&apos;s time to make the actual loader:

![Its Time Vegas GIF by BPONGofficial](https://media2.giphy.com/media/SKcxqI1GiASU783uT2/giphy.gif?cid=bcfb69440p5hlnpm71c54qwekk0jbebhx4qwp6q7v02oa60c&amp;ep=v1_gifs_search&amp;rid=giphy.gif&amp;ct=g)

Let&apos;s recap what our loader needs to do:

- walk the `routes` folder
- for each file matching the pattern (routeName).route.js, add a route to our express app that looks like `/routeName`

So, i&apos;ll make a new file called `router.js` 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.

```js
module.exports = async (app) =&gt; {
  const fileNameRegex = /(.*).routes.js$/
}
```

And in this function we&apos;ll begin by walking the whole `routes` folder

```js
/* add this at the top of the file */ const fs = require(&apos;node:fs/promises&apos;)

const folders = await fs.readdir(&apos;./routes&apos;)
```

And then check each file to see if it matches the regex we defined earlier:

```js
folders.forEach((file) =&gt; {
  const regexName = fileNameRegex.exec(file)
  if (!regexName) return
})
```

And finally, we add the route to our application with a little log line:

```js
console.log(`[+] Router file ${file} loaded under /${regexName[1]}`)
app.use(`/${regexName[1]}`, require(`./routes/${file}`))
```

The last step is to use our loader function: your index.js should look something like this:

```js
const express = require(&apos;express&apos;)

const loadRoutes = require(&apos;./router&apos;)

const app = express()
loadRoutes(app).then(() =&gt; {
  app.listen(4000, () =&gt; {
    console.log(&apos;listening&apos;)
  })
})
```

![](https://media1.giphy.com/media/vN3fMMSAmVwoo/giphy.gif?cid=bcfb694476ov89l4f91ymnaon8r1wwg13pgrqobo6ijfnke7&amp;ep=v1_gifs_search&amp;rid=giphy.gif&amp;ct=g)

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 [this plugin](https://github.com/GiovanniCardamone/fastify-autoroutes) for [fastify](https://fastify.dev/)

You can see a working example below:

&lt;Stackblitz id=&apos;mg-fbr-express&apos; /&gt;</content:encoded></item><item><title>test everywhere with dagger.io</title><link>https://matteogassend.com/articles/test-with-dagger/</link><guid isPermaLink="true">https://matteogassend.com/articles/test-with-dagger/</guid><pubDate>Sun, 15 Oct 2023 00:00:00 GMT</pubDate><content:encoded>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!

## Dagger (not the stabby kind)

Dagger&apos;s tagline is

&gt; CI/CD as Code that Runs Anywhere

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 [magiedit](https://magiedit.magitools.app) and what I&apos;ll use when I eventually write unit tests (it&apos;ll come one day, I promise.)

### How do you use it?

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.

## Creating a Pipeline

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

### Prerequisites

#### Container engine

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 [here](/articles/taming-the-whale)), but other runtimes are compatible (podman, containerd etc): here&apos;s [an article](https://docs.dagger.io/541047/alternative-runtimes) detailing how to use them, though I would still recommend Docker.

#### Install Dagger CLI

You can find the installation instructions [here](https://docs.dagger.io/quickstart/729236/cli), but the gist is (for Linux / WSL) to execute this line:

```sh
cd /usr/local
curl -L https://dl.dagger.io/dagger/install.sh | sh
```

#### Install Dagger SDK

For this example I&apos;ll be using the NodeJS sdk which you can install like this:

```sh
npm install @dagger.io/dagger --save-dev
```

### Writing the actual pipeline

The main thing to do is import the `connect` function from the Dagger sdk, because everything will happen in there:

```javascript
import { connect } from &apos;@dagger.io/dagger&apos;
```

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:

```javascript
connect(async (client) =&gt; {}, { LogOutput: process.stdout })
```

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.

Then you can declare a runner with a base image and run all the commands you need with it;

```javascript
const node = await client
  .container()
  .from(&apos;node:18&apos;)
  .withDirectory(&apos;/app&apos;, client.host().directory(&apos;.&apos;), {
    exclude: [&apos;node_modules&apos;, &apos;ci&apos;],
  })
const runner = node.withWorkdir(&apos;/app&apos;).withExec([&apos;npm&apos;, &apos;install&apos;])
```

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.

![The Walking Dead Easy Peasy GIF](https://media0.giphy.com/media/NaboQwhxK3gMU/giphy.gif?cid=bcfb6944db5rkmw2adjnr7wtx95a7veo71t5t5zl7aj2gs9h&amp;ep=v1_gifs_search&amp;rid=giphy.gif&amp;ct=g)

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

```javascript
await runner.withExec([&apos;npm&apos;, &apos;run&apos;, &apos;build&apos;]).stderr()
```

### Running the pipeline

To run pipeline, you simply execute it like any other NodeJS script; assuming you have saved your script as `build.mjs` in a `ci` folder, you can just run:

```sh
node ci/build.mjs
```

![Weird Al GIF by The Roku Channel](https://media1.giphy.com/media/iFCmbYrTnj96luPXhE/giphy.gif?cid=bcfb69441tcje7xshjfsao2o95j0e18ft09mbqt9mfjs0g6f&amp;ep=v1_gifs_search&amp;rid=giphy.gif&amp;ct=g)

And that&apos;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!

### Bonus Round: Github Actions

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:

```yaml
name: build
on:
  push:
    branches:
      - master
      - feature/*

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: pnpm/action-setup@v2
        with:
          version: 8
      - uses: actions/setup-node@v3
        with:
          node-version: 18
      - name: Install dependencies
        run: pnpm install
      - name: Build
        run: node ci/build.mjs
```

In this example, I&apos;m using pnpm to install my dependencies, and then simply execute the pipeline.

If you want to learn more about Dagger, check out [their website](https://dagger.io/); they have some very nice real case articles on how to use Dagger to build more complex pipelines.</content:encoded></item><item><title>building a basic markdown editor: unified, trees and data</title><link>https://matteogassend.com/articles/markdown-editor/</link><guid isPermaLink="true">https://matteogassend.com/articles/markdown-editor/</guid><pubDate>Mon, 02 Oct 2023 00:00:00 GMT</pubDate><content:encoded>import Stackblitz from &apos;../../components/mdx/Stackblitz.astro&apos;

As you have probably guessed from the previous articles in this series (go read them if you haven&apos;t, btw), the main thing when building a markdown publishing webapp, is the markdown editor itself.

![Season 3 Walk GIF by The Simpsons](https://media1.giphy.com/media/3orif1pbMEL5VJnmwM/giphy.gif?cid=bcfb6944ona09wkpkujnf1j7yvcqraf3kve1b8t4wnih6ick&amp;ep=v1_gifs_search&amp;rid=giphy.gif&amp;ct=g)

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

## Markdown

if you don&apos;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;h2&gt;` tag would be translated in markdown with `##` 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 [here](https://www.markdownguide.org/)

## What I needed to build

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...

![Excuse Me Wow GIF by Mashable](https://media0.giphy.com/media/l3q2K5jinAlChoCLS/giphy.gif?cid=bcfb6944zbg8lkbkejj0satp1tv1mrykzw8vogwf2sjr4vz7&amp;ep=v1_gifs_search&amp;rid=giphy.gif&amp;ct=g)

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!

![Tyler Perry Problem Solved GIF by Nickelodeon](https://media2.giphy.com/media/LJbberzGLVChcsdNOv/giphy.gif?cid=bcfb6944h5e0uf8r7n2yyktj224ougmnxeycs0uu8pbdp5qb&amp;ep=v1_gifs_search&amp;rid=giphy.gif&amp;ct=g)

### Unified

To build the Markdown editor (and the preview, mostly), I decided to use [unified](https://unifiedjs.com/), 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 [this article](https://unifiedjs.com/learn/guide/using-unified/), but they mostly consist of:

- reading the markdown content (from the textarea in this case)
- using `remarkParse` to convert the markdown into a syntax tree
- using `remarkRehype` to convert the markdown syntax tree to an html syntax tree
- using `rehypeStringify` to convert the html syntax tree to an html string

![GIF by South Park ](https://media4.giphy.com/media/3o7ypD3Ho7jMArHDxe/giphy.gif?cid=bcfb6944tegi9dndgxq1mo0tz531rpudh84d657kl2llua1n&amp;ep=v1_gifs_search&amp;rid=giphy.gif&amp;ct=g)

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 [highlight.js](https://highlightjs.org/) 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.

**NB: remark and rehype are also used by [astro](https://astro.build) to render the collection&apos;s markdown content, so any plugins you use there can also be used here**

## Putting it all together

Now that we have a basic understanding of how unified works, let&apos;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;Stackblitz id=&apos;mg-markdown-parser&apos; /&gt;</content:encoded></item><item><title>end-to-end encryption with sveltekit</title><link>https://matteogassend.com/articles/end-to-end-sveltekit/</link><guid isPermaLink="true">https://matteogassend.com/articles/end-to-end-sveltekit/</guid><pubDate>Mon, 25 Sep 2023 00:00:00 GMT</pubDate><content:encoded>## Introduction

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 [turso](https://turso.tech), btw). The thing is, I didn&apos;t want to store raw article data for reasons like data privacy and privacy in general (I don&apos;t want to know what you write using Magiedit). To solve this problem, I started looking into cryptography, specifically the web-cryptography api.

## How this all works

### Genering a master password

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.

**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&apos;s session storage**

### How and where things happen

#### How it works (algorithms and stuff)

**DISCLAIMER: I&apos;m not an expert in cryptography, so take everything I say with a grain of salt**

- The key is based on a hash of the master password using SHA-256
- 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&apos;s be honest; they are just articles, after all)

#### Where it works

Let&apos;s first take a look at this schema:
![magiedit&apos;s data flow diagram](https://cdn.blog.matteogassend.com/magiedit-encryption-flow.png)

When a user creates, loads (from a file) or saves an article, the same steps happen:

- A cryptographic key is generated using the master password hashed using SHA-256

```js
const keyBytes = await crypto.subtle.digest(
  &apos;SHA-256&apos;,
  new TextEncoder().encode(keyData),
)
const key = await crypto.subtle.importKey(&apos;raw&apos;, keyBytes, &apos;AES-CBC&apos;, false, [
  &apos;encrypt&apos;,
])
```

- We generate an iv (initialization vector, kind of like a seed)

```js
function generateIv() {
  const data = new Uint8Array(16)
  crypto.getRandomValues(data)
  return data
}
```

- The text is then encrypted using the key and the iv

```js
const encodedContent = await crypto.subtle.encrypt(
  { name: &apos;AES-CBC&apos;, iv },
  key,
  new TextEncoder().encode(&apos;write here&apos;),
)
```

- It is then converted to base64

```js
const base64 = btoa(String.fromCharCode(...new Uint8Array(encodedContent)))
```

- and finally sent to the server (along with its iv) to get it stored securely

As for decrypting, it is the same process, but in reverse:

- get the article from the db
- convert the base64 string to a buffer
- generate a key from the master password
- decrypt the buffer
- convert the buffer back to a string

![Art Drawing GIF by GEICO](https://media4.giphy.com/media/MXM5QQ3jY7WmcmPwTI/giphy.gif?cid=bcfb6944sukc5vegbsl0xkey1dmb6kxar5fen0smog3hhr32&amp;ep=v1_gifs_search&amp;rid=giphy.gif&amp;ct=g)
I really like this gif, you know?

## Why end to end encryption ?

Because I wanted users to know that even if I wanted to (and I don&apos;t) read what they wrote before publishing, I couldn&apos;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!</content:encoded></item><item><title>Publishing articles to multiple platforms with decorators and interfaces</title><link>https://matteogassend.com/articles/publishing-articles-to-multiple-platforms-with-decorators-and-interfaces/</link><guid isPermaLink="true">https://matteogassend.com/articles/publishing-articles-to-multiple-platforms-with-decorators-and-interfaces/</guid><pubDate>Mon, 18 Sep 2023 00:00:00 GMT</pubDate><content:encoded>I have been working on [magiedit](https://magiedit.magitools.app) for a while now, and a few days ago I finally merged the pull request implementing one of the main ideas I had when I started building this tool; publishing an article to different platforms at once. For now, the only supported platforms are [dev.to](https://dev.to) and [hashnode](https://hashnode.com), but the api allows for easily adding more providers if needed (medium, the fediverse, etc); that api is what I want to talk about, because it is one of the things that took me the longer to work out (and it still isn&apos;t exactly done).

## API Design

This is the code for the basic api for the platform interface:

```js
interface IBasePlatform&lt;T&gt; {
	settings: Record&lt;string, string&gt;;
	publish(article: Article): void;
	setSettings(settings: UserPreferences[]): T;
	getRequiredSettings(): string[];
	getPlatformName(): string;
}
```

It is kind of built on the builder pattern (I&apos;ll eventually write an article on that), and allows to generalize the declaration and use of any implementation of this class because each of these has the same exact invocation method (the basics of an interface, I know, but sometimes it&apos;s necessary to restate the basics).

- settings: holds all the specific settings required to use this implementation (api keys, tokens and whatnot)
- setSettings: loops over the settings and gets those required by the implementation (not very safe, but right now it what I found)
- getRequiredSettings: returns a list of keys required for the integration to work (this usually works within setSettings)
- getPlatformName: pretty self explanatory, usually used for logging of successful or unsuccesful publishing
- publish: actually handles all the publishing step (mostly handling the api call to the platform with required formatting, headers and body)

Let&apos;s take a look at a concrete example; the dev.to implementation:

```js
class DevPlatform implements IBasePlatform&lt;DevPlatform&gt; {
	settings: Record&lt;string, string&gt; = {};
	public getRequiredSettings(): string[] {
		return [&apos;dev&apos;];
	}

	getPlatformName(): string {
		return &apos;dev.to&apos;;
	}

	setSettings(settings: UserPreferences[]) {
		settings.forEach((e) =&gt; {
			if (this.getRequiredSettings().includes(e.key.split(&apos;:&apos;)[1])) {
				this.settings[e.key.split(&apos;:&apos;)[1]] = e.value;
			}
		});
		return this;
	}

	public async publish(article: Article) {
		const setting = this.settings[&apos;dev&apos;];
		if (!setting) throw new Error(&apos;could not find required settings&apos;);
		const res = await fetch(&apos;https://dev.to/api/articles&apos;, {
			method: &apos;post&apos;,
			body: JSON.stringify({
				article: {
					title: article.title,
					body_markdown: article.content,
					published: article.published
				}
			}),
			headers: {
				accept: &apos;application/vnd.forem.api-v1+json&apos;,
				&apos;content-type&apos;: &apos;application/json&apos;,
				&apos;api-key&apos;: setting
			}
		});
		if (!res.ok) {
			throw new Error(&apos;something went wrong&apos;);
		}
	}
}
```

**NB: this does not yet support tags matching for the different platforms: that will come in a future release**

## Registering a new platform provider

### Typescript Decorators

Now that we have a (mostly) working platform implementation, how do let the program know? By using decorators!

![Season 6 What GIF by The Office](https://media3.giphy.com/media/ghuvaCOI6GOoTX0RmH/giphy.gif?cid=bcfb69442kgj0xwn63swq7t1t97b3irfiuh163t7c92wwgcn&amp;ep=v1_gifs_search&amp;rid=giphy.gif&amp;ct=g)

Decorators are a way to &quot;decorate&quot; methods and classes; what this means is it can execute functions on classes and methods (more info [here](https://www.typescriptlang.org/docs/handbook/decorators.html)). The way I decided to use it is by creating a list of registered implementations that gets an element pushed each time a class gets &quot;decorated&quot;; let&apos;s take a look.

```js
const supportedPlatforms: (new () =&gt; IBasePlatform&lt;any&gt;)[] = [];
function RegisterPlatform(constructor: new () =&gt; IBasePlatform&lt;any&gt;) {
	supportedPlatforms.push(constructor);
}
```

These are the two sections used (NB: this is still not final, but for now it works enough)

- supportedPlatforms: the list of platforms with said decorator; the typing of the variables specifies that they must be something having a `new` method that returns an implementation of the interface we saw above.
- RegisterPlatform: the actual decorator function that can be used like this:

```js
@RegisterPlatform
class DevPlatform implements IBasePlatform&lt;DevPlatform&gt;
```

Having defined these methods, if we decorate the dev.to implementation we saw above, it will get added to the list.

**ATTENTION**: there is a caveat here, because you need to import the file containing the implementation somewhere, otherwise it will just get skipped. In magiedit, this was solved by having an index file importing all the needed implementations.

## Putting it all together

Now we have a list of all the possible implementations in a single variable and all with the same methods, but how do we use them?
For each element of the list (here `platform`), we instantiate it, call getSettings() and run publish:

```js
await new platform().setSettings(tokens).publish()
```

And voilà!

![Season 9 Thank You GIF by The Office](https://media3.giphy.com/media/1BFEEIo4h1BuTH8eqP/giphy.gif?cid=bcfb69442ubf7xftc9bpq6n6h9a2q3d5gi0q7ch5tr0w2sn3&amp;ep=v1_gifs_search&amp;rid=giphy.gif&amp;ct=g)</content:encoded></item><item><title>File Uploads with Sveltekit and Cloudflare</title><link>https://matteogassend.com/articles/file-uploads-with-sveltekit-and-cloudflare-cover/</link><guid isPermaLink="true">https://matteogassend.com/articles/file-uploads-with-sveltekit-and-cloudflare-cover/</guid><pubDate>Tue, 12 Sep 2023 00:00:00 GMT</pubDate><content:encoded>I am currently building [magiedit](https://github.com/magitools/magiedit), a markdown editor that also allows publishing to different platforms (like [Hashnode](https://hashnode.com) and [Dev.to](https://dev.to)) and decided to add some shortcuts/commands to make my life simpler (since I am building this tool primarily for myself xD), including adding images from unsplash and gifs and giphy because
![Old Man What GIF by Amazon Prime Video](https://media0.giphy.com/media/FEBDBbLFT9px3da0vT/giphy.gif?cid=bcfb6944bd46uy7laheqck7cr13jjhkkjlsv9lkz8j64mnz6&amp;ep=v1_gifs_search&amp;rid=giphy.gif&amp;ct=g)

For one of the features I&apos;m working on that will allow users to use dall-e to generate images (and cover images based on their article&apos;s content as soon I can get that to working) I needed to store the selected images (since openais urls only last for 1 hour and storing 4mb of base64 data in an article makes editing it awful. For this, I turned to [cloudflare&apos;s r2 storage](https://www.cloudflare.com/fr-fr/developer-platform/r2/), because I already use their solution for my blog&apos;s images and my dns management (plus, their free tier is pretty generous).

![Jimmy Fallon Free Stuff GIF by The Tonight Show Starring Jimmy Fallon](https://media3.giphy.com/media/3tpzkqpbVdshXX1By7/giphy.gif?cid=bcfb69442ar464da1u8i5rc10qqbj90qmbbtmr2g269feyn6&amp;ep=v1_gifs_search&amp;rid=giphy.gif&amp;ct=g)

## Using Cloudflare R2 Storage

Cloudflare&apos;s R2 Storage is an S3 (the one from Amazon, exactly) compatible storage solution, meaning you can interact with it in &quot;almost&quot; the say way you would with an S3 bucket (there are some methods that are not supported and that you can find [here](https://developers.cloudflare.com/r2/).

So, for my use case, I created a bucket and paired it with a domain name and then generated an access key to that specific bucket (so that I could upload things from my sveltekit api function). Again, the link above should provide all instructions on how to get these informations and what they all mean. Now that I had a bucket, let&apos;s get to the heart of this article, which is how to interact with a bucket using sveltekit (try saying that sentence out of context).

## Sveltekit integration

Now for the fun part; using all this in sveltekit. Let me preface this section by saying that I had specific needs in this application that required something of an unorthodox architecture. Since the application is storing user articles, I wanted those to be offline first, as well as other potential informations required for using the application (like tokens for publishing and stuff); this means that I had to create api routes instead of handling all of this in form actions. Having said all this, let&apos;s see how it all works.

![Lets Go Start GIF](https://media3.giphy.com/media/3aGZA6WLI9Jde/giphy.gif?cid=bcfb69440en0a56he59l99bvpxc33352js92uu1b4j031q3u&amp;ep=v1_gifs_search&amp;rid=giphy.gif&amp;ct=g)

### Getting the images

For my specific use case, I needed to store images generated by dall-e (openai), so that meant calling that service; said service returns either a base64 string containing the image or an url (that expires after 1 hour). Passing around a 4mb string didn&apos;t sound like a good idea (trust me, I tried), so I was left with the url.

This is the (hopefully) final code I came up with:

```js
export const POST: RequestHandler = async ({ locals, request }) =&gt; {
	const session = await locals.auth.validate();
	if (!session) {
		throw error(401, { message: &apos;not authorized&apos; });
	}
	const formData = await request.formData();
	const { content, description } = Object.fromEntries(formData);
	if (!content) {
		throw error(500, { message: &apos;invalid input&apos; });
	}
	const arrayBuffer = await (await fetch(content.toString())).arrayBuffer();
	const data = Buffer.from(new Uint8Array(arrayBuffer));
	const key = `${session?.user?.userId}_${uuidv4()}`;
	const url = await saveToBucket(data, key);
	// get data from function
	await db.insert(userImages).values({
		url,
		userId: session?.user.userId,
		description: description?.toString()
	});
	return json({ message: &apos;ok&apos;, url });
};
```

These two lines are what took me a long time to figure out because I kept running into heap problems (my program was using too much memory on this single call, which is not normal)

```js
const arrayBuffer = await (await fetch(content.toString())).arrayBuffer()
const data = Buffer.from(new Uint8Array(arrayBuffer))
```

This allows me to download the image from the url openai provides, store it as a buffer and then do whatever I want with it (in this case, store it in a bucket). Then I generate a key using the user&apos;s id a random uuid (I will always have access to the user, because this is one of the few commands requiring authentication) which will become the file name, and then call the `saveToBucket` function.

### Saving the images (or any other file, really)

You may have noticed I had a call to a functionn `saveToBucket` in the previous code sample; well, as the name suggests, this is where the buffer we got earlier gets saved into the bucket and we get a url back:

```js
export async function saveToBucket(data: Buffer, key: string) {
	const S3 = new S3Client({
		region: &apos;auto&apos;,
		endpoint: `https://${CLOUDFLARE_ACCOUNT_ID}.r2.cloudflarestorage.com`,
		credentials: {
			accessKeyId: CLOUDFLARE_ACCESS_KEY_ID,
			secretAccessKey: CLOUDFLARE_SECRET_ACCESS_KEY
		}
	});
	await S3.send(
		new PutObjectCommand({
			ACL: &apos;public-read&apos;,
			Key: key,
			Body: data,
			Bucket: CLOUDFLARE_BUCKET_NAME
		})
	);
	return `${CLOUDFLARE_BUCKET_URL}/${key}`;
}
```

Again, most of this stuff comes from the cloudflare documentation I linked above, like how to setup the S3Client. This function basically puts together a bunch of environment variables and call a single function with the command needed to upload a file to a specific bucket. And just like that, we have image upload !

![Art Drawing GIF by GEICO](https://media3.giphy.com/media/MXM5QQ3jY7WmcmPwTI/giphy.gif?cid=bcfb6944pnewgj5qvmk7rqd9rizxnherrb1klviruuvl5jfc&amp;ep=v1_gifs_search&amp;rid=giphy.gif&amp;ct=g)

## What&apos;s left after uploading

The one thing I wanted was for users to be able to retrieve all the images they had generated (and be able to download them at some point), so I simply stored the url for the new image with the user&apos;s in my database, and retrieve all those belonging to a specific user id when necessary.</content:encoded></item><item><title>Building an Astro Blog with View Transitions</title><link>https://matteogassend.com/articles/astro-blog-md/</link><guid isPermaLink="true">https://matteogassend.com/articles/astro-blog-md/</guid><pubDate>Thu, 24 Aug 2023 00:00:00 GMT</pubDate><content:encoded>I&apos;ve recently decided to recentralize all my articles on my own blog instead of publishing them to [Hashnode](https://hashnode.com) (nothing wrong with Hashnode, just something that I had wanted to do for a while) and since I have a wondeful brand new [personal site](https://matteogassend.com) built with [astro](https://astro.build) I decided to take advantage of their collection system and the new **experimental** (at least at the time of writing this article) view transitions api to build something a bit more tailored to what I like. Here&apos;s how it went.

## Astro Collections

### Introduction

Astro content collection are as simple as a folder containing a bunch of Markdown (or Markdoc or MDX) files if that&apos;s the only thing you need, but they can also do relationship matching between different collections, frontmatter validation using [zod](https://zod.dev) and you can also customize how the markdown is parsed and translated to html using [rehype](https://github.com/rehypejs/rehype) and [remark](https://github.com/remarkjs/remark) and their plugin ecosystem.

Let&apos;s look at an example, shall we? (btw, documentation for what I&apos;m about to talk about is [here](https://docs.astro.build/en/guides/content-collections/))

Taking my blog as an example, we can see that (for now) the &quot;articles&quot; content collection has a bit of frontmatter validation; I am requiring that each article have a **title**, a **cover** image, a **publish date** and a list of tags. (Maybe I&apos;ll add something like article series in a future update? who knows?)

With this &quot;schema&quot; defined then, all my articles will need to have a frontmatter section looking kind of like this:

```yaml
---
title: Some title here
date: 2023-06-10
cover: https://example.com/image.webp
tags:
  - tag1
  - tag2
---
```

Then you can do a bunch of operations, like retrieving all the collection&apos;s &quot;entries&quot; (in this case, each article) and they can be handled like any other array in javascript or typescript (map over them, sort them by publication date etc).

### Displaying articles and more

When you nativate to a blog post on my website, I have a route that catches the article&apos;s slug (a human readable name that can be used in a url, basically), fetches the corresponding article and displays it along with its frontmatter data.

The code for it would look a bit like this:

```js
const { slug } = Astro.params
if (slug === undefined) {
  throw new Error(&apos;Slug is required&apos;)
}
// 2. Query for the entry directly using the request slug
const entry = await getEntry(&apos;articles&apos;, slug)
// 3. Redirect if the entry does not exist
if (entry === undefined) {
  return Astro.redirect(&apos;/404&apos;)
}
```

and you would store in a file called `[...slug].astro`.

Then to display the markdown content, you can call the render method and then display the content on the page:

```javascript
---
const { Content, headings } = await entry.render();
---

&lt;Content /&gt;
```

That should take care of displaying the content you wrote in your markdown file; you want to get the frontmatter data (like a title, cover image and such), you can do so using `entry.data.title` and so on.

## Adding Functionalities

### Table of Contents

But what if I wanted to add a summary ?

You could obviously write by hand each time, but you could also leverage the data Astro gives us; you may have noticed that I destructured 2 properties from the renderer entry: we have already used content, so let&apos;s look at the headings.

The headings variable is an array of all the headings in a file (think `#`, `##` etc) as well as their level (`##` is 2, `###` is 3 etc). With these informations, we can build a structure displaying each section and subsection and display it accordingly (more info on this article from [Kevin Drum](https://kld.dev/building-table-of-contents/) and add it our page.

### Embeds and markdown customization

You may also notice I have some embeds on my articles even though Markdown natively does not support embedding content. This is done by using a Remark plugin. Remark is a tool that can be used to parse and transform Markdown. In this case, I used a plugin called [remark-embedder](https://github.com/remark-embedder/core) to add custom logic to replace links from specific websites (in this case, Youtube and CodeSandbox) with `&lt;iframe&gt;`s containing the actual page; without the plugin, those would simply be text links and would make for a much less pleasing lecture, wouldn&apos;t you agree ?
You can obviously do more with remark than just that, so take a look at the plugins they offer. But how do you use with Astro? You simply add the plugins in your `astro.config.mjs` file (documentation on how to do that is [here](https://docs.astro.build/en/reference/configuration-reference/#markdownremarkplugins)).

This post was a bit chaotic, but I hope I was able to share a bit of what I did on my blog section (where you are hopefully reading this right now).</content:encoded></item><item><title>Appwrite Hackaton: MoviePlay</title><link>https://matteogassend.com/articles/appwrite-hackaton-movieplay/</link><guid isPermaLink="true">https://matteogassend.com/articles/appwrite-hackaton-movieplay/</guid><pubDate>Sat, 10 Jun 2023 00:00:00 GMT</pubDate><content:encoded>import Youtube from &apos;../../components/mdx/Youtube.astro&apos;

Well, I recently quit my job so I got free time and this hackathon between [Hashnode](https://hashnode.com) and [Appwrite](https://appwrite.io) is announced. This is clearly a sign. So I decided to build [MoviePlay](https://appwrite.io). Before we get into the trials and tribulations of this project, let&apos;s get all the technical stuff out of the way.

## The Idea

Have you ever had a debate with someone as to what is the correct order to watch Star Wars? There&apos;s the chronological order, the release order, the machete order

![enough already](https://media.giphy.com/media/SRka2MLKzpzE6K24al/giphy.gif)

## Team

- Me

## Tech Stack

- ReactJS &amp; Typescript (ViteJS)

* TailwindCSS &amp; DaisyUI

* [Appwrite Cloud](https://cloud.appwrite.io)
  - Database

  - Account

  - Functions
    - NodeJS

* Vercel

* [The-Movie-Wrapper](https://www.npmjs.com/package/@matfire/the_movie_wrapper) (I made this one, but still...)

## Look at the code

You can see the code [here](https://github.com/matfire/movieplay) and see it live [here](https://movieplay.nirah.tech)

## Demo

&lt;Youtube id=&apos;8GJyqRNkZrA&apos; /&gt;

## On the using of Appwrite

I had already used a self-hosted version of Appwrite and have been tinkering with the closed cloud beta for a bit, so I was already familiar with the tool and will not really talk about onboarding.

### Database

I was a bit disappointed relationships (as of 06/04/2023) are not yet supported on Cloud, so you still need to do all the foreign key constraints by hand, which makes fetching client-side a bit of a hassle.

### Account

I really like how accounts are handled, especially oauth2 stuff; the fact that you can connect multiple providers for the same account seamlessly is pretty cool

### Functions

I needed to use functions to store the number of views a playlist could get. I would have loved a trigger on **database read operations**, but I can see it would probably be too much performance overhead; the way I solved this is when navigating to a page, the page loader function triggers the function to increment the views number.</content:encoded></item><item><title>Taming the whale: introduction to Docker</title><link>https://matteogassend.com/articles/taming-the-whale/</link><guid isPermaLink="true">https://matteogassend.com/articles/taming-the-whale/</guid><pubDate>Thu, 09 Mar 2023 00:00:00 GMT</pubDate><content:encoded>import CodeSandbox from &apos;../../components/mdx/CodeSandbox.astro&apos;
import Youtube from &apos;../../components/mdx/Youtube.astro&apos;

Have you ever had to work on a project that requires lots of parts that need to be installed separately? And one of those parts refuses to work because maybe the other developer worked on Windows and you are on Linux? Well, what I told you that those problems can be (relatively) easily solved? Let&apos;s take a look at what Docker is and how we can use it.

## What is Docker?

Docker is a suite of tools allowing you to run containers on your system.

### But what is a container?

according to [docker.com](https://docker.com), a container is _a sandboxed process on your machine that is isolated from all other processes on the host machine_; this feature has been available on Linux for some time, but Docker managed to standardize and make it available on other operating systems.

## Why would you use a container?

One of the reasons Docker is so popular is that it allows people to get started and run projects without necessarily needing to install a whole environment. This usually allows for faster onboarding and testing while also simplifying the deployment of services; by using a container you needn&apos;t worry about the exact system-specific settings you might need to handle for (most) of the production applications (a single container can be run on (basically) any operating system).

## Images? Containers?

I have mentioned both containers and images so far, but what&apos;s the difference?

- An image is the piece of software that contains all the instructions to run your program; the installed programs, the start command etc...

* A container is what runs an Image; it also handles transmitting environment variables, port forwarding with the host, volumes etc... (more details on those later)

## Docker Cheatsheet

Now that we know (kind of) how Docker works, let&apos;s create our first image.

### Installing Docker

on most systems, you can install [Docker Desktop](https://docker.com) to have a nice GUI to help you. If you can&apos;t or don&apos;t want to use that, you can also only install the command line tool by running

```bash
curl https://get.docker.com | bash -E
```

this should download and install docker on your machine (if this does not work, head on over to the [documentation](https://docs.docker.com/get-started/) for system-specific information).

### Creating an Image

For the sake of an example, we will be using this example

&lt;CodeSandbox projectType=&apos;devbox&apos; projectid=&apos;cwmtww&apos; /&gt;

It&apos;s a simple application running an HTTP server. **Let&apos;s dockerize it!**

To do so, we need to first create a Dockefile; this is a specific file format that enables us to describe how an Image should be created.
The simplest Dockerfile for this example project would be:

```bash
FROM node:lts
COPY package.json .
COPY index.js .
RUN npm install
CMD [&quot;node&quot;, &quot;index.js&quot;]
```

let&apos;s look a bit more into this file:

- FROM: this line describes the base of our image. We need to tell Docker what to base our image on. To do so, you can specify the image **name** and a **tag** separated by **:**

* COPY: copies a file or folder from our computer&apos;s filesystem to the image&apos;s.

* RUN: executes a command when building the image

* CMD: this is the command that gets executed when the image runs.

to build this image we can run:

```bash
docker build -t learn-docker .
```

the -t option allows us to specify a tag for the image to find it more easily.

### Running an Image

Once we have an image tagged, we can run it by saying:

```bash
docker run learn-docker
```

### Detached Mode

You&apos;ll notice that our terminal window is stuck on the output from our terminal; that&apos;s all well and good, but it&apos;d be nice if we wouldn&apos;t have to open a new terminal for each container we want to run: enter **detached mode**.

```bash
docker run -d learn-docker
```

Running a container in detached mode means putting the process in the background which in turn means we get our terminal back.

### Stopping a container

To stop a container, we first need to know its id. To get a list of all your running containers, you can run the command:

```bash
docker ps
```

The first element of each line is the container id. Then you can just run:

```bash
docker stop &lt;the_id_of_the_container&gt;
```

and it will stop your container.

### Ports

you may have noticed that, at least for now, we are unable to access our api. This is because, to put it simply, Docker is sandboxed in its own network, so we need to explicitly map the container&apos;s ports to those of our system&apos;s. Let&apos;s take the command we had before and add a little option

```bash
docker run -p 8000:4000 learn-docker
```

this should expose port 3000 of our container on port 8000 of our system. If you visit [localhost:8000](http://127.0.0.1:8000), you should see our hello world message.

### Volumes

now that we have a basic application running, let&apos;s see if we can get a bit more out of it. If you take a look at the code, you&apos;ll notice that I&apos;m writing my logs into a file inside a **logs** folder; wouldn&apos;t it be neat if we could read that data? This is where volumes come in.

But before we get started, let&apos;s make our life a bit simpler; in our Dockerfile, let&apos;s specify the **working directory.** This means that when Docker executes the instruction `COPY index.js .`, **.** will be replaced by the working directory we specified earlier. Our new Dockerfile should look something like this:

```bash
FROM node:lts
WORKDIR /app
COPY package.json .
COPY index.js .
RUN npm install
CMD [&quot;node&quot;, &quot;index.js&quot;]
```

Our logs should be stored in `/app/logs`, right? If so, we could define that folder as a **volume**; a volume is a path that is designated as **persistent**, meaning if the container stops the data inside the volume will not be lost. As an additional bonus, a volume can also mirror files and folders in the container to the host&apos;s disk. You could, let&apos;s say setup a development environment with hot reloading with Dockefiles and maybe a Docker-Compose (maybe a topic for another article).
let&apos;s revise our execution line a bit:

```bash
docker run -p 8000:4000 -v ./logs:/app/logs learn-docker
```

this new addition will tell Docker to use (or create) the folder logs in our terminal&apos;s current directory as the volume for the path **/app/logs** of our container. This means that if we look inside our logs folder, we should see a file called **log.txt.** If now we send a request to [http://localhost:8000](http://127.0.0.1:8000), we should a new line get appended to that file.

This is especially useful for databases because you most likely do not want to lose all your customer&apos;s data every time you update/stop/restart a container.

## This is the end...

This has hopefully been a useful introduction to Docker. If you want to learn more about this technology, the first step would be to visit [the official documentation](https://docs.docker.com); it is pretty well written.
Other useful resources may be:

- Learn Docker in 7 Easy Steps - Full Beginner&apos;s Tutorial - Fireship

&lt;Youtube id=&apos;gAkwW2tuIqE&apos; /&gt;

- Docker Tutorial for Beginners - Programming with Mosh

&lt;Youtube id=&apos;pTFZFxd4hOI&apos; /&gt;</content:encoded></item><item><title>Hook, line, and sinker</title><link>https://matteogassend.com/articles/hook-line-sinker/</link><guid isPermaLink="true">https://matteogassend.com/articles/hook-line-sinker/</guid><pubDate>Tue, 25 Jan 2022 00:00:00 GMT</pubDate><content:encoded>Following up on the [Beginner&apos;s guide to React](/articles/react-for-beginners), this article will cover the basic &quot;hooks&quot; that React offers.

## What are hooks?

Hooks are a functionality introduced in React 16.8. They allow you to create stateful functional components.

![the hell?](https://media.giphy.com/media/N25nrRX4rsnkY/giphy.gif)

If that last phrase made you have the same reaction as Al here, then this article&apos;s for you!
Let me try to explain it this way. Before, if we wanted to have _state_ in a component, we&apos;d need to use a `class component` that would look something like this:

```js
export default class TodoList extends React.Component {
  state = {
    todos: [],
  }

  render() {
    return (
      &lt;ul&gt;
        {this.state.todos.map((e) =&gt; (
          &lt;li&gt;{e}&lt;/li&gt;
        ))}
      &lt;/ul&gt;
    )
  }
}
```

But now there&apos;s no need to write something so long! With React hooks functional components can have state! Whereas before you would just use a functional component as a _stateless_ one (where data comes from _props_, basically), you can now give _state_ to those components. Let&apos;s take the previous example; using hooks, we can rewrite it like this:

```js
function TodoList() {
  const [todos] = useState([])
  return (
    &lt;ul&gt;
      {todos.map((e) =&gt; (
        &lt;li&gt;{e}&lt;/li&gt;
      ))}
    &lt;/ul&gt;
  )
}

// Export the function here
```

_NB: I prefer to write functional components this way for readability, but you could also export the function directly._

Is it just me or did that code become a bit more readable just now? If you&apos;re wondering about the `[todos]` part, I&apos;ll talk more about it in the next section. Speaking (or writing, such as it is) about that, let&apos;s dive into React hooks!

## UseState

the `useState` hook replaces the `state` in a class component. It is used to define part of a component&apos;s state. Why do I say part of? Because you can (and _should_) use `useState` multiple times in a single component. This allows you to have more control over the state and, when used in conjunction with the next hook (spoiler), will allow you to rerun code or rerender of your app on a specific state change.

Let&apos;s take the last bit of code and continue from there:

```js
function TodoList() {
  const [todos] = useState([])
  return (
    &lt;ul&gt;
      {todos.map((e) =&gt; (
        &lt;li&gt;{e}&lt;/li&gt;
      ))}
    &lt;/ul&gt;
  )
}
```

At this moment, the component renders a list of _todos_, but you might notice a problem; we have no way to **set** those _todos_.

To do that, we need to understand the return values of `useState`: this function returns an array containing the state&apos;s value and a function to update said values. We can call it (and the value, for that matter) whatever we want, but let&apos;s stick to the conventions for now.
Let&apos;s update the code we&apos;ve written before:

```js
function TodoList() {
  const [todos, setTodos] = useState([])
  return (
    &lt;ul&gt;
      {todos.map((e) =&gt; (
        &lt;li&gt;{e}&lt;/li&gt;
      ))}
    &lt;/ul&gt;
  )
}
```

Now we have a function to set our todos. Let&apos;s add an input and a button to add todos. In the input, we&apos;ll listen to the `onChange` event to modify some other piece of state.

```js
function TodoList() {
  const [todos, setTodos] = useState([])
  const [newTodo, setNewTodo] = useState(&apos;&apos;)
  return (
    &lt;div&gt;
      &lt;ul&gt;
        {todos.map((e) =&gt; (
          &lt;li&gt;{e}&lt;/li&gt;
        ))}
      &lt;/ul&gt;
      &lt;input
        onChange={(e) =&gt; {
          setNewTodo(e.target.value)
        }}
        value={newTodo}
      /&gt;
      &lt;button
        onClick={() =&gt; {
          setTodos((oldTodos) =&gt; [...oldTodos, newTodo])
          setNewTodo(&apos;&apos;)
        }}&gt;
        Add Todo
      &lt;/button&gt;
    &lt;/div&gt;
  )
}
```

![woah woah woah](https://media.giphy.com/media/RXKCMLmch5W2Q/giphy.gif)

Yeah, that&apos;s a lot of new stuff to cover, I know. Let&apos;s break it down

```js
const [newTodo, setNewTodo] = useState(&apos;&apos;)
```

This is just a new useState. Notice how this time I put **&quot;&quot;** as a first argument. The first argument in a useState function call defines the starting value of the state and is also used to determine its _type_. This can be used by us (the developers) to get IntelliSense on the state (autocompletion etc).

```js
&lt;input
  onChange={(e) =&gt; {
    setNewTodo(e.target.value)
  }}
  value={newTodo}
/&gt;
```

This input does two things: it reads the state _newTodo_ and uses it as value while also updating it with its new value (when a user types in the input, the state will change to reflect that change).

```js
&lt;button
  onClick={() =&gt; {
    setTodos((oldTodos) =&gt; [...oldTodos, newTodo])
    setNewTodo(&apos;&apos;)
  }}&gt;
  Add Todo
&lt;/button&gt;
```

This button add the _newTodo_ to the list and resets the input&apos;s text (i.e. _newTodo_). You might have noticed something...

```js
setTodos((oldTodos) =&gt; [...oldTodos, newTodo])
setNewTodo(&apos;&apos;)
```

![different](https://media.giphy.com/media/IwX8XVO9mx3SmncyF5/giphy.gif)

When you _set_ a state, you can either directly pass the new value as an argument, or use an arrow function to get a reference to the current state&apos;s value (useful for updating arrays).

And that&apos;s it for the useState hook !!

## useEffect

Now that we know how to set state in a component, let&apos;s see how to replace a `componentDidUpdate` using hooks. the `componentDidUpdate` method of a class component was used to trigger effects based on state and props change.

Let&apos;s say we&apos;re building a photo search app and we need to update the search results based on the current value of an input field. As the user types in the input field, we want to update the result. We could do it in the `onChange` callback, but that would make the rerender dependent on the onChange and the app would become laggy (at least it used to, don&apos;t quote me on that).

Let&apos;s setup our basic component:

```js
function PhotoSearch() {
  const [searchValue, setSearchValue] = useState(&apos;&apos;)
  const [photoResult, setPhotoResult] = useState([])

  return (
    &lt;div&gt;
      &lt;input
        value={searchValue}
        onChange={(e) =&gt; {
          setSearchValue(e.target.value)
        }}
      /&gt;
      &lt;div&gt;
        {photoResult.map((e) =&gt; (
          &lt;img src={e} /&gt;
        ))}
      &lt;/div&gt;
    &lt;/div&gt;
  )
}
```

So, nothing new for now; we have to states, searchValue and photoResult, the former to store the input&apos;s value and the second to store the photos matching that search. Now let&apos;s use the `useEffect` hook to update the photoResult array with our search results (here I&apos;m using a basic example, a real API would have more complex data formats (like [unsplash](https://unsplash.com/developers))

```js
function PhotoSearch() {
  const [searchValue, setSearchValue] = useState(&apos;&apos;)
  const [photoResult, setPhotoResult] = useState([])

  useEffect(() =&gt; {
    if (searchValue) {
      fetchImages(searchValue).then((data) =&gt; {
        setPhotoResult(data)
      })
    }
  }, [searchValue])

  return (
    &lt;div&gt;
      &lt;input
        value={searchValue}
        onChange={(e) =&gt; {
          setSearchValue(e.target.value)
        }}
      /&gt;
      &lt;div&gt;
        {photoResult.map((e) =&gt; (
          &lt;img src={e} /&gt;
        ))}
      &lt;/div&gt;
    &lt;/div&gt;
  )
}
```

_NB: fetchImages acts as a function that returns an array of links to images from an API_

Let&apos;s break it down:

`useEffect` takes two arguments (the second being optional): the former is a callback function and the latter is a dependency array.
The callback function gets executed every time the values inside the dependency array change. In our example, we want to rerun that function each time the `searchValue` changes, so we add `searchValue` to the dependency array. Notice also the condition inside the callback:

```js
if (searchValue)
```

Since the value could be empty after a user deletes its input or when the page first loads, we need to check before executing a code that depends on it.

There are other quirks with useEffect, and you can read more about them on the [official React documentation](https://reactjs.org/docs/hooks-effect.html).

## Final words

These are just the two hooks you&apos;ll use the most. Others exist (useContext, useCallback, useMemo), and you can even make your own. We&apos;ll probably look at those another time, on another article, but let&apos;s stop here for the time being.</content:encoded></item><item><title>React for Beginners: Build a To-Do List</title><link>https://matteogassend.com/articles/react-for-beginners/</link><guid isPermaLink="true">https://matteogassend.com/articles/react-for-beginners/</guid><pubDate>Sat, 15 Aug 2020 00:00:00 GMT</pubDate><content:encoded>So...
You heard of this framework called React and want to know what all the fuss is about ? Well, wander no more ! Let&apos;s go for a spin with ReactJS and build a simple application that will let us see why everyone is talking about it.

## Installing React

### Prerequisites

Before installing React, make sure you have a correct development environment. I&apos;m not talking about your IDE (Integrated Development Environment) - though I personally prefer Visual Studio Code, but whether or not you have NodeJS installed on your machine. If you do, that&apos;s great 👍! If you don&apos;t, I&apos;d suggest using [nvm](https://github.com/nvm-sh/nvm) to install the latest LTS (long term support) version; you shouldn&apos;t need anything else for this guide.

## Creating a Project

Now that we have a **recent** version on NodeJS, and consequently npm, we can start bootstrapping our new React project. After you get to know better the framework you can have your own starter template, but for today&apos;s guide let&apos;s just use `create-react-app`. To start a new react project, just open up a terminal and type: `npx create-react-app &lt;folderName&gt;` where `&lt;folderName&gt;` is the name of the folder that will be created with your react project in it.
**Here&apos;s a trick I found recently** : If you insert `.` instead of a folder name (`.` is the current directory), then the project will be instantiated in the current directory, without creating an addition subfolder.

Once that is done, you can move to that folder and run `npm start` to launch the development server. It should open a browser window to localhost:3000 and you should see the starter page.

Congrats ! You have created your first React project

## Folder Structure

![FolderStructure.PNG](https://cdn.hashnode.com/res/hashnode/image/upload/v1597411603872/6l9tHWSz8.png)
This is the usual folder structure generated by create-react-app

Let&apos;s now take a look at some particular files:

- src/App.js : this is the root component all your application will be rendered inside of. If you look at the code inside it, you will see that it&apos;s what you&apos;ve seen when you launched your application
- src/index.js : this is the file that actually renders the `App` component onto the page by attaching it to a `div` with an id of &quot;root&quot;.
- public/index.html : the scheleton of the page your React component will live in: in here you can see the `div` I mentioned above

### To Do

Now that we have had a look at how a React project is structured, we can start creating our To Do app.

**NB: for the sake of brevity, we will have everything happen inside one component. Perhaps in a following article we will advance this project to make it resemble more a real life use case**

#### Task list

The first step is to create a list of tasks. This list will be updated every time a new items gets added or an item is removed. It will composed of a simple unordered list, with each list item corresponding to a task.

## Creating a component

For this guide, we will be using functional components and the latest addition to the React arsenal, hooks. To create a functional component you must first import React at the top of the file and create a function that returns some `JSX`.

### What is JSX ?

From React&apos;s official documentation

Consider this variable declaration:

```js
const element = &lt;h1&gt;Hello, world!&lt;/h1&gt;
```

This funny tag syntax is neither a string nor HTML.

It is called JSX, and it is a syntax extension to JavaScript. We recommend using it with React to describe what the UI should look like. JSX may remind you of a template language, but it comes with the full power of JavaScript.

You can learn more [here](https://reactjs.org/docs/introducing-jsx.html)

### Let&apos;s get back to the &apos;task&apos; 🤣 at hand

Let&apos;s remove all the code inside the return of the `App` component. It should now look like this:

![empty app component](https://cdn.hashnode.com/res/hashnode/image/upload/v1597414689914/XlMMnaVw0.png)

let&apos;s begin by adding a `div` as the root of our component: **All React element must have only one root element** and inside of it let&apos;s add our unordered list

![app with div and ul tag](https://cdn.hashnode.com/res/hashnode/image/upload/v1597414589017/FrdzYdWpa.png)

#### Let&apos;s display some taks

Now that we have a list element, we need to add elements inside of it. We could add them one by one, but that is not the point of using React. Let&apos;s instead declare an array with some data in it:

![ul with data no state](https://cdn.hashnode.com/res/hashnode/image/upload/v1597414874885/EJyk2JrSJ.png)

Now that we have some data, all that is left is to display them to the user. Let me introduce you to one of your Javascript bestfriends: **the map function**. It allows us to iterate over an array and return a value for each element (keeping it brief, of course). So, can you guess what we&apos;re going to use it for ?

![right you are](https://media.giphy.com/media/U56VoSyFD8MFcie2k8/giphy.gif) We&apos;ll use `map` to display our list items !

so why don&apos;t we do just that ?

![app with items no state](https://cdn.hashnode.com/res/hashnode/image/upload/v1597421004860/ouR1gXFSE.png)

If everything went well, then you should see 2 elements appear on your page.

![congrats](https://media.giphy.com/media/XreQmk7ETCak0/giphy.gif)

#### Adding a task

In order to add a task, we need to introduce a `Form` tag. Within the form we&apos;ll have an `input` and a `button`. Clicking the button will submit the form.

![adding form input](https://cdn.hashnode.com/res/hashnode/image/upload/v1597423178884/eKDC5Bv5N.png)

If you try typing in something and then submitting the form you&apos;ll notice something strange... The page reloads! This is because the `form` is acting like a form, in that it sends a post request while reloading the page. But we don&apos;t want that, do we?

What we need to do is provide a substitute function that will run when the form gets submitted. We can do this with the `onSubmit` prop (think of props as passing values to another component from their parent).

The first thing we want to do is prevent the form from actually submitting (i.e. reloading the page). For that, we need to prevent the default behaviour associated with the event originating from the form. If this last sentence seems a bit complicated, it&apos;s not; we&apos;re simply doing this:

![onSubmit preventDefault](https://cdn.hashnode.com/res/hashnode/image/upload/v1597490032250/qq8EIdkqv.png)

If you try this out, you&apos;ll see that the form doesn&apos;t reload the page!

##### Getting the task&apos;s name

Now we need to get the input&apos;s value in order to add it to the list. And it&apos;s in this section that you&apos;ll get to meet the **one** thing that I love about React: state!

##### React State

React state is a collection of variables that you define on a component basis, that forces the component showing them to the screen to re-render itself. It means that for example we can dynamically add rows to a table and it will update automatically (without us needing to manipulate the dom direclty). In order to do so, we&apos;ll also get to know a &quot;new&quot; concept in React, called hooks (more info [here](https://reactjs.org/docs/hooks-intro.html)). In this paricular guide, we&apos;ll only use the `useState` hook, which allows us to define a variable and a function to update said variable. The syntax is as follows:

```js
const [value, setValue] = useState()

setValue(3) // updates the variable value
```

you can also pass as an argument to `useState` the default value of the variable

In our case, we need to keep track of the input&apos;s value, and then add it to our table. For the task&apos;s id, I&apos;ll increment the length of task array by 1.

Our component should look like this, now:

![App with onChange input](https://cdn.hashnode.com/res/hashnode/image/upload/v1597502204110/NI61Yls6T.png)

All that is left is add the `taskValue` variable to our tasks array when the form is submitted:

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1597502349666/Mj6V6f1TE.png)

If you try and run this code, you&apos;ll notice that it doesn&apos;t really update the table the user sees. Can you guess why ? (hint: the table doesn&apos;t really _react_ to changes)

### Conclusion

Thanks for reading this article! If you have any questions, feel free to ask them in the comments below.</content:encoded></item></channel></rss>