<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Bagas Hizbullah</title>
    <description>The latest articles on DEV Community by Bagas Hizbullah (@bagashiz).</description>
    <link>https://dev.to/bagashiz</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1003362%2F307fba50-0dbb-4f84-8ff7-d7b11968cced.png</url>
      <title>DEV Community: Bagas Hizbullah</title>
      <link>https://dev.to/bagashiz</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bagashiz"/>
    <language>en</language>
    <item>
      <title>Crafting A Minimalist Portfolio Website with SvelteKit and Pico CSS</title>
      <dc:creator>Bagas Hizbullah</dc:creator>
      <pubDate>Sat, 14 Oct 2023 10:50:20 +0000</pubDate>
      <link>https://dev.to/bagashiz/crafting-a-minimalist-portfolio-website-with-sveltekit-and-pico-css-4230</link>
      <guid>https://dev.to/bagashiz/crafting-a-minimalist-portfolio-website-with-sveltekit-and-pico-css-4230</guid>
      <description>&lt;p&gt;Creating a portfolio website can be a practical way to showcase resumes, projects, and blogs. As someone who leans more towards backend development, I found myself in the situation of wanting to establish an online presence without drowning myself into the complexities of &lt;a href="https://react.dev" rel="noopener noreferrer"&gt;React&lt;/a&gt; or dealing with the manual development with plain HTML, CSS, and JavaScript. It was in the search for a simpler alternatives that I found &lt;a href="https://svelte.dev" rel="noopener noreferrer"&gt;Svelte&lt;/a&gt;, and its meta-framework, &lt;a href="https://kit.svelte.dev" rel="noopener noreferrer"&gt;SvelteKit&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The simplicity of Svelte's syntax, its templates, and its approach to frontend development, which relies on &lt;a href="https://svelte.dev/blog/virtual-dom-is-pure-overhead" rel="noopener noreferrer"&gt;compilation rather than a virtual DOM&lt;/a&gt;, has won me over. And then there is SvelteKit, an extension of Svelte that simplifies the process of building websites, which made it a great choice for my portfolio website project. It offered a perfect balance between simplicity and powerful functionality that aligns well with my preferences.&lt;/p&gt;

&lt;p&gt;However, the question of CSS styling remained. The vast ecosystem of &lt;a href="https://tailwindcss.com" rel="noopener noreferrer"&gt;Tailwind CSS&lt;/a&gt;, with its extensive class-based utility system and configurations, felt kind of overwhelming for my needs. On the other hand, creating styles entirely from scratch using pure CSS felt time-consuming and inefficient.&lt;/p&gt;

&lt;p&gt;My search for a middle ground led me to &lt;a href="https://picocss.com" rel="noopener noreferrer"&gt;Pico CSS&lt;/a&gt;, a minimalist CSS framework with a unique approach to styling. What makes Pico CSS different is its focus on elegant styling without the use of classes. Instead, it encourages the use of semantic HTML tags to implement predefined styles rather than abusing &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; tags with CSS classes, making it an interesting choice for those who prefer to keep their HTML clean and meaningful.&lt;/p&gt;

&lt;p&gt;In this blog post, I will try to explain the technical aspects of how I utilized SvelteKit and Pico CSS to create a portfolio website that effectively presents my resume, projects, and blogs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Initiate SvelteKit Project
&lt;/h2&gt;

&lt;p&gt;Getting started with a SvelteKit project is a straight-forward process. The easiest way is to run &lt;code&gt;npm create&lt;/code&gt; command:&lt;/p&gt;

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

npm create svelte@latest my-app


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

&lt;/div&gt;
&lt;p&gt;It will scaffold a new project in the &lt;code&gt;my-app&lt;/code&gt; directory, asking questions to set up some basic tooling such as TypeScript. However, I'm using JSDoc for this project, because it is good enough for my needs.&lt;/p&gt;

&lt;p&gt;SvelteKit follows conventions that encourage efficient code organization. There are two basic concepts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each page is a Svelte component.&lt;/li&gt;
&lt;li&gt;Create more pages by adding files to the &lt;code&gt;src/routes&lt;/code&gt; directory of the project.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I used the index page for my resume. Then, I created 2 specific directories, &lt;code&gt;blogs&lt;/code&gt; and &lt;code&gt;projects&lt;/code&gt;, along with their mandatory &lt;code&gt;+page.svelte&lt;/code&gt; and &lt;code&gt;+page.server.js&lt;/code&gt; files. These files served as the components that rendered the content for my &lt;code&gt;/blogs&lt;/code&gt; and &lt;code&gt;/projects&lt;/code&gt; pages respectively.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

src/routes/
├── blogs
│   ├── +page.server.js
│   └── +page.svelte
├── +error.svelte
├── +layout.svelte
├── +page.svelte
└── projects
    ├── +page.server.js
    └── +page.svelte

3 directories, 7 files


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

&lt;/div&gt;
&lt;p&gt;Additionally, I added a &lt;code&gt;+error.svelte&lt;/code&gt; file for rendering custom error messages when needed.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$app/stores&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="cm"&gt;/** @type {Record&amp;lt;number, string&amp;gt;} map of status codes to emojis */&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;emojis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;❌&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// bad request&lt;/span&gt;
        &lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;🔒&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// unauthorized&lt;/span&gt;
        &lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;🚫&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// forbidden&lt;/span&gt;
        &lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;🔍&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// not found&lt;/span&gt;
        &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;🛠&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// internal server error&lt;/span&gt;
        &lt;span class="mi"&gt;502&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;🔌&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// bad gateway&lt;/span&gt;
        &lt;span class="mi"&gt;503&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;🔧&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// service unavailable&lt;/span&gt;
        &lt;span class="mi"&gt;504&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;⏳&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="c1"&gt;// gateway timeout&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;section&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"error"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"container"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&amp;lt;strong&amp;gt;&lt;/span&gt;{$page.status}&lt;span class="nt"&gt;&amp;lt;/strong&amp;gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;{$page.error?.message}&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"emoji"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        {emojis[$page.status] ?? emojis[500]}
    &lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="nt"&gt;h2&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;margin-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;#error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;flex-direction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;justify-content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;align-items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;text-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;#emoji&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;


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

&lt;/div&gt;
&lt;p&gt;For static data, I put them as a JSON object inside &lt;code&gt;src/lib/store.js&lt;/code&gt; file.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="cm"&gt;/**
 * Experience data for the resume.
 *
 * @type {Array&amp;lt;{
 *   job: string,
 *   url: string,
 *   company: string,
 *   start: string,
 *   end: string,
 *   description: Array&amp;lt;string&amp;gt;
 * }&amp;gt;}
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;experiences&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Volunteering data for the resume.
 *
 * @type {Array&amp;lt;{
 *   job: string,
 *   url: string,
 *   company: string,
 *   start: string,
 *   end: string,
 *   description: Array&amp;lt;string&amp;gt;
 * }&amp;gt;}
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;volunteering&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Social data for the resume.
 *
 * @type {Array&amp;lt;{
 *   href: string,
 *   rel: string,
 *   fa_class: string,
 * }&amp;gt;}
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;socials&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Education data for the resume.
 *
 * @type {Array&amp;lt;{
 *  school: string,
 * url: string,
 * major: string,
 * start: string,
 * end: string,
 * description: string
 * }&amp;gt;}
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;educations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="cm"&gt;/** Font Awesome icons name for the skills data. */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;skills&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="cm"&gt;/** Workflows data for the resume. */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;workflows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Awards data for the resume.
 *
 * @type {Array&amp;lt;{
 * place: number,
 * suffix: string,
 * host: string,
 * competition: string,
 * translation: string
 * }&amp;gt;}
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;awards&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Certification data for the resume.
 *
 * @type {Array&amp;lt;{
 *  title: string,
 * credential_id: string,
 * credential_url: string
 * }&amp;gt;}
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;certifications&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;


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

&lt;/div&gt;
&lt;p&gt;I also put all the necessary component files inside the &lt;code&gt;src/lib/components&lt;/code&gt; directory.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

src/lib/components/
├── BlogCard.svelte
├── EduCard.svelte
├── Footer.svelte
├── JobCard.svelte
├── Navbar.svelte
├── ProjectCard.svelte
├── SkillsCard.svelte
└── SocialIcon.svelte

1 directory, 8 files


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

&lt;/div&gt;
&lt;p&gt;And for JavaScript files, I put them in the &lt;code&gt;src/lib/scripts&lt;/code&gt; directory.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

src/lib/scripts/
├── minimal-theme-switcher.js
└── redis.js

1 directory, 2 files


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

&lt;/div&gt;
&lt;h2&gt;
  
  
  Styling with Pico CSS
&lt;/h2&gt;

&lt;p&gt;To get started with Pico CSS, all I had to do was install it via npm by using the straight-forward command:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; @picocss/pico


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

&lt;/div&gt;
&lt;p&gt;After that, I included it into my project by importing it in the &lt;code&gt;+layout.svelte&lt;/code&gt; file, specifically inside the &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@picocss/pico&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;


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

&lt;/div&gt;
&lt;p&gt;And that was basically it. I was able to use Pico CSS's elegant styles for all native HTML elements without the need for classes, enhancing the overall aesthetics of my website.&lt;/p&gt;

&lt;p&gt;I also &lt;em&gt;borrowed&lt;/em&gt; some code from Pico CSS's example code for a theme switcher by manipulating the &lt;code&gt;data-theme&lt;/code&gt; attribute for the &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; tag using JavaScript. I placed the code inside &lt;code&gt;src/lib/scripts/minimal-theme-switcher.js&lt;/code&gt; file.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="cm"&gt;/*!
 * Minimal theme switcher
 *
 * Pico.css - https://picocss.com
 * Copyright 2019-2023 - Licensed under MIT
 */&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Minimal theme switcher
 *
 * @namespace
 * @typedef {Object} ThemeSwitcher
 * @property {string} _scheme - The current color scheme ("auto", "light", or "dark").
 * @property {string} menuTarget - The selector for the menu element that contains theme switchers.
 * @property {string} buttonsTarget - The selector for theme switcher buttons.
 * @property {string} buttonAttribute - The attribute name used for theme switcher buttons.
 * @property {string} rootAttribute - The attribute name used for the root HTML element to store the selected theme.
 * @property {string} localStorageKey - The key used to store the preferred color scheme in local storage.
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ThemeSwitcher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Config&lt;/span&gt;
    &lt;span class="na"&gt;_scheme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;auto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;menuTarget&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;details[role='list']&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;buttonsTarget&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;a[data-theme-switcher]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;buttonAttribute&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data-theme-switcher&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;rootAttribute&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data-theme&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;localStorageKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;picoPreferredColorScheme&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * Initialize the theme switcher.
     *
     * @function
     * @memberof ThemeSwitcher
     */&lt;/span&gt;
    &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scheme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;schemeFromLocalStorage&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;preferredColorScheme&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;initSwitchers&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * Get the color scheme from local storage or use the preferred color scheme.
     *
     * @function
     * @memberof ThemeSwitcher
     * @returns {string|null} The color scheme ("light", "dark", or null).
     */&lt;/span&gt;
    &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nf"&gt;schemeFromLocalStorage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;localStorage&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;undefined&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;localStorageKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;localStorageKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_scheme&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * Get the preferred color scheme based on user preferences.
     *
     * @function
     * @memberof ThemeSwitcher
     * @returns {string} The preferred color scheme ("light" or "dark").
     */&lt;/span&gt;
    &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nf"&gt;preferredColorScheme&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;matchMedia&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;(prefers-color-scheme: dark)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;matches&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;light&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * Initialize the theme switcher buttons and their click events.
     *
     * @function
     * @memberof ThemeSwitcher
     */&lt;/span&gt;
    &lt;span class="nf"&gt;initSwitchers&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;buttons&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;buttonsTarget&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;buttons&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
                    &lt;span class="c1"&gt;// Set scheme&lt;/span&gt;
                    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scheme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;buttonAttribute&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;auto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                    &lt;span class="c1"&gt;// Close dropdown&lt;/span&gt;
                    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;menuTarget&lt;/span&gt;&lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="nf"&gt;removeAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;open&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="kc"&gt;false&lt;/span&gt;
            &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * Set the selected color scheme and update the UI.
     *
     * @function
     * @memberof ThemeSwitcher
     * @param {string} scheme - The color scheme to set ("auto", "light", or "dark").
     */&lt;/span&gt;
    &lt;span class="kd"&gt;set&lt;/span&gt; &lt;span class="nf"&gt;scheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scheme&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scheme&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;auto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;preferredColorScheme&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_scheme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_scheme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;light&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scheme&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;scheme&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;light&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_scheme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;scheme&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;applyScheme&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;schemeToLocalStorage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * Get the current color scheme.
     *
     * @function
     * @memberof ThemeSwitcher
     * @returns {string} The current color scheme ("auto", "light", or "dark").
     */&lt;/span&gt;
    &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nf"&gt;scheme&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_scheme&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * Apply the selected color scheme to the HTML root element.
     *
     * @function
     * @memberof ThemeSwitcher
     */&lt;/span&gt;
    &lt;span class="nf"&gt;applyScheme&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rootAttribute&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scheme&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * Store the selected color scheme in local storage.
     *
     * @function
     * @memberof ThemeSwitcher
     */&lt;/span&gt;
    &lt;span class="nf"&gt;schemeToLocalStorage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;localStorage&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;undefined&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;localStorageKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scheme&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;


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

&lt;/div&gt;
&lt;p&gt;Then, I used it in the &lt;code&gt;Navbar.svelte&lt;/code&gt; component within the &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; section, utilizing Svelte's &lt;code&gt;onMount&lt;/code&gt; feature.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ThemeSwitcher&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$lib/scripts/minimal-theme-switcher&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;onMount&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;svelte&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nf"&gt;onMount&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;ThemeSwitcher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;


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

&lt;/div&gt;
&lt;h2&gt;
  
  
  Font Awesome Icons
&lt;/h2&gt;

&lt;p&gt;Utilizing Font Awesome icons in my project was easy. By integrating the &lt;a href="https://fontawesome.com/docs/web/setup/use-kit" rel="noopener noreferrer"&gt;Font Awesome Kit&lt;/a&gt;, I have access to a vast icon library that covers various categories, from social media icons to common UI elements. Even though using the kit may not be the fastest way to render icons on the web, the flexibility of the kit allows me to select and combine icons that blend seamlessly with my website's design, adding aesthetic value and a better user experience.&lt;/p&gt;
&lt;h2&gt;
  
  
  Fetch Blogs Data from Dev.to API
&lt;/h2&gt;

&lt;p&gt;For populating my &lt;code&gt;/blogs&lt;/code&gt; page with my published blog articles, I utilized Dev.to's API as provided in their &lt;a href="https://developers.forem.com/api/v1#tag/articles/operation/getArticles" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;. I included a &lt;code&gt;username&lt;/code&gt; query parameter with my username as the value:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

https://dev.to/api/articles?username=bagashiz


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

&lt;/div&gt;
&lt;p&gt;This query allowed me to get my published blog articles from the API and integrate them into my &lt;code&gt;/blogs&lt;/code&gt; page on my portfolio website.&lt;/p&gt;

&lt;p&gt;To enhance user experience and website performance, I implemented a strategy where I returned the data as a &lt;a href="https://kit.svelte.dev/docs/load#streaming-with-promises" rel="noopener noreferrer"&gt;streamed promise&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$env/dynamic/private&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cm"&gt;/** @type {import('./$types').PageServerLoad} */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BLOG_URL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;streamed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="cm"&gt;/**
             * @type {Promise&amp;lt;Blog[]&amp;gt;} blogs - Array of dev.to blogs
             */&lt;/span&gt;
            &lt;span class="na"&gt;blogs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
                    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;blogs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;blogs&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                    &lt;span class="p"&gt;});&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;
&lt;p&gt;This approach allowed me to render the page with a placeholder before the actual blog data was fetched. &lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;BlogCard&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$lib/components/BlogCard.svelte&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="cm"&gt;/** @type {import('./$types').PageData} */&lt;/span&gt;
    &lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;section&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;&amp;lt;!-- ... --&amp;gt;&lt;/span&gt;

    {#await data.streamed.blogs}
        &lt;span class="nt"&gt;&amp;lt;article&lt;/span&gt; &lt;span class="na"&gt;aria-busy=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"skeleton"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"outline"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;strong&amp;gt;&lt;/span&gt;Fetching data...&lt;span class="nt"&gt;&amp;lt;/strong&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/article&amp;gt;&lt;/span&gt;
    {:then blogs}
        {#each blogs as blog}
            &lt;span class="nt"&gt;&amp;lt;BlogCard&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;&lt;span class="na"&gt;blog&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        {/each}
    {:catch}
        &lt;span class="nt"&gt;&amp;lt;article&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"skeleton"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"outline"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;strong&amp;gt;&lt;/span&gt;Uh oh! Failed to fetch data.&lt;span class="nt"&gt;&amp;lt;/strong&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/article&amp;gt;&lt;/span&gt;
    {/await}
&lt;span class="nt"&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;/* ... */&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;


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

&lt;/div&gt;
&lt;p&gt;This approach not only ensured a smooth and responsive user experience but also provided an nice way to handle the loading of external data.&lt;/p&gt;
&lt;h2&gt;
  
  
  Fetch Projects Data from GitHub GraphQL API
&lt;/h2&gt;

&lt;p&gt;To populate my &lt;code&gt;/projects&lt;/code&gt; page with information about my pinned GitHub repositories, I utilized &lt;a href="https://docs.github.com/en/graphql" rel="noopener noreferrer"&gt;GitHub GraphQL API&lt;/a&gt;. This API allows for more specific and efficient data retrieval compared to traditional REST APIs.&lt;/p&gt;

&lt;p&gt;However, fetching data from the GitHub GraphQL API involved a slightly different process. Instead of using simple &lt;code&gt;GET&lt;/code&gt; requests, I needed to use the &lt;code&gt;POST&lt;/code&gt; method. To specify the data I wanted to retrieve, I included a GraphQL query in the request body:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;login&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bagashiz"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;pinnedItems&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;types&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;REPOSITORY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;nodes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Repository&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="n"&gt;primaryLanguage&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="n"&gt;stargazers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="n"&gt;totalCount&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="n"&gt;forks&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="n"&gt;totalCount&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;


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

&lt;/div&gt;
&lt;p&gt;This query allowed me to request my pinned GitHub repositories, such as repository names, descriptions, stars and forks count, primary languages, and links.&lt;/p&gt;

&lt;p&gt;Additionally, I also needed to add a bearer token to the Authorization header of the request. This token was generated using a GitHub personal access token with the necessary permissions, including access to public repositories and the ability to read all user profile data.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$env/dynamic/private&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * @type {string} url - Github GraphQL API endpoint
 */&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.github.com/graphql&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * @type {string} query - GraphQL query to fetch pinned github repositories
 */&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`...`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cm"&gt;/** @type {import('./$types').PageServerLoad} */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;projects&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GITHUB_ACCESS_TOKEN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reqInfo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;streamed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="cm"&gt;/**
             * @type {Promise&amp;lt;Project[]&amp;gt;} projects - Array of GitHub projects
             */&lt;/span&gt;
            &lt;span class="na"&gt;projects&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reqInfo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
                        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;projects&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pinnedItems&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                            &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;projects&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                        &lt;span class="p"&gt;});&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;
&lt;h2&gt;
  
  
  Caching API Data with Redis
&lt;/h2&gt;

&lt;p&gt;To make data retrieval efficient and minimize the need to request data from the API on every page load, I implemented a caching strategy using the &lt;a href="https://github.com/redis/ioredis" rel="noopener noreferrer"&gt;ioredis&lt;/a&gt; package, which is a popular redis client for Node.js for interacting with a &lt;a href="https://redis.io" rel="noopener noreferrer"&gt;Redis&lt;/a&gt; database.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$env/dynamic/private&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Redis&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ioredis&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * @type {Redis} redis - Redis client instance
 */&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Redis&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;REDIS_HOST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;REDIS_PORT&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;6379&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Error initializing redis client: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;


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

&lt;/div&gt;
&lt;p&gt;For caching strategy, I implemented the &lt;a href="https://learn.microsoft.com/en-us/azure/architecture/patterns/cache-aside" rel="noopener noreferrer"&gt;Cache-Aside&lt;/a&gt; pattern. This process involves fetching data from an external API, storing it in the Redis cache, and using the cached data for the next request. I also configured the cache to expire after an hour, making sure to keep it efficient and up-to-date.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;blogs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;streamed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="cm"&gt;/**
         * @type {Promise&amp;lt;Blog[]&amp;gt;} blogs - Array of dev.to blogs
         */&lt;/span&gt;
        &lt;span class="na"&gt;blogs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;blogs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                    &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;blogs&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
                    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`[&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;] Cache miss, getting data from dev.to API`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

                    &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
                        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;blogs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;blogs&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
                            &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;blogs&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                        &lt;span class="p"&gt;});&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;


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

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

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;projects&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;streamed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="cm"&gt;/**
         * @type {Promise&amp;lt;Project[]&amp;gt;} projects - Array of GitHub projects
         */&lt;/span&gt;
        &lt;span class="na"&gt;projects&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;projects&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                    &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;projects&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
                    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`[&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;] Cache miss, getting data from GitHub API`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

                    &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reqInfo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
                        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;projects&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pinnedItems&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                            &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;projects&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
                            &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;projects&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                        &lt;span class="p"&gt;});&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;


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

&lt;/div&gt;
&lt;h2&gt;
  
  
  Trying Out New View Transitions API
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://developer.chrome.com/docs/web-platform/view-transitions" rel="noopener noreferrer"&gt;view transitions API&lt;/a&gt;  streamlines the process of animating between two page states, which is especially useful for page transitions. This excellent &lt;a href="https://svelte.dev/blog/view-transitions" rel="noopener noreferrer"&gt;blog post&lt;/a&gt; by &lt;a href="https://geoffrich.net" rel="noopener noreferrer"&gt;Geoff Rich&lt;/a&gt; offered valuable insights and guidelines on implementing this new feature to my project.&lt;/p&gt;

&lt;p&gt;I set up the new View Transitions API in my SvelteKit project using the &lt;code&gt;onNavigate()&lt;/code&gt; function inside the &lt;code&gt;+layout.svelte&lt;/code&gt; file. This function enabled the transitions between different page states seamlessly.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="c1"&gt;// view transition between routes&lt;/span&gt;
    &lt;span class="nf"&gt;onNavigate&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;navigation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// @ts-ignore&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;startViewTransition&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// @ts-ignore&lt;/span&gt;
            &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startViewTransition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
                &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;navigation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;complete&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;


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

&lt;/div&gt;
&lt;p&gt;I also implemented custom transitions using CSS. These custom transitions were defined inside the &lt;code&gt;app.css&lt;/code&gt; file.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;

&lt;span class="k"&gt;@keyframes&lt;/span&gt; &lt;span class="n"&gt;fade-in&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nt"&gt;from&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;@keyframes&lt;/span&gt; &lt;span class="n"&gt;fade-out&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nt"&gt;to&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;@keyframes&lt;/span&gt; &lt;span class="n"&gt;slide-from-right&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nt"&gt;from&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;translateX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;30px&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;@keyframes&lt;/span&gt; &lt;span class="n"&gt;slide-to-left&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nt"&gt;to&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;translateX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;-30px&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/**
 * slide animation if prefers-reduced-motion is not set,
 * else default cross-fade animation
 */&lt;/span&gt;
&lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefers-reduced-motion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;no-preference&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;:root::view-transition-old&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;root&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;90ms&lt;/span&gt; &lt;span class="n"&gt;cubic-bezier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nb"&gt;both&lt;/span&gt; &lt;span class="n"&gt;fade-out&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="m"&gt;300ms&lt;/span&gt; &lt;span class="n"&gt;cubic-bezier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nb"&gt;both&lt;/span&gt; &lt;span class="n"&gt;slide-to-left&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;:root::view-transition-new&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;root&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;210ms&lt;/span&gt; &lt;span class="n"&gt;cubic-bezier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="m"&gt;90ms&lt;/span&gt; &lt;span class="nb"&gt;both&lt;/span&gt; &lt;span class="n"&gt;fade-in&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="m"&gt;300ms&lt;/span&gt; &lt;span class="n"&gt;cubic-bezier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nb"&gt;both&lt;/span&gt; &lt;span class="n"&gt;slide-from-right&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;
&lt;h2&gt;
  
  
  Deployment
&lt;/h2&gt;

&lt;p&gt;With SvelteKit, deploying my portfolio website can't get any simpler. The first step is to switch from the default SvelteKit adapter to using the &lt;a href="https://kit.svelte.dev/docs/adapter-node" rel="noopener noreferrer"&gt;Node.js adapter&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Then I created a Dockerfile that specified all the necessary dependencies and configurations. I used a multi-stage build approach to reduce the size of the final image. &lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:18.18.0-alpine3.18&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package.json package-lock.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm run build

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:18.18.0-alpine3.18&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;prod&lt;/span&gt;

&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; node:node&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build --chown=node:node /app/build ./build&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build --chown=node:node /app/package.json ./&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;npm i &lt;span class="nt"&gt;--omit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dev

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node", "build"]&lt;/span&gt;


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

&lt;/div&gt;
&lt;p&gt;After that, I set up a GitHub workflow to build and publish the Docker image to the &lt;a href="https://github.com/bagashiz/portfolio/pkgs/container/portfolio" rel="noopener noreferrer"&gt;GitHub Container Registry&lt;/a&gt; automatically.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Create and publish a Docker image&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;main"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;REGISTRY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghcr.io&lt;/span&gt;
  &lt;span class="na"&gt;IMAGE_NAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.repository }}&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build-and-push-image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;
      &lt;span class="na"&gt;packages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout repository&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4.1.0&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Log in to the Container registry&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/login-action@v3.0.0&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;registry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ env.REGISTRY }}&lt;/span&gt;
          &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.actor }}&lt;/span&gt;
          &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Extract metadata (tags, labels) for Docker&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;meta&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/metadata-action@v5.0.0&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;images&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build and push Docker image&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/build-push-action@v5.0.0&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
          &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
          &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.meta.outputs.tags }}&lt;/span&gt;
          &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.meta.outputs.labels }}&lt;/span&gt;


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

&lt;/div&gt;
&lt;p&gt;Finally, I pulled that image and deployed it to my VPS. You can see it live now at &lt;a href="https://bagashiz.me" rel="noopener noreferrer"&gt;https://bagashiz.me&lt;/a&gt;!&lt;/p&gt;
&lt;h2&gt;
  
  
  Source Code
&lt;/h2&gt;

&lt;p&gt;If you're interested in exploring more about this project, you can check out the source code in my GitHub repository&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/bagashiz" rel="noopener noreferrer"&gt;
        bagashiz
      &lt;/a&gt; / &lt;a href="https://github.com/bagashiz/portfolio" rel="noopener noreferrer"&gt;
        portfolio
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Class-less personal portfolio &amp;amp; resume website built using Go, Templ, HTMX, and styled with Pico CSS.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Portfolio&lt;/h1&gt;
&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Description&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;My personal portfolio website for showcasing my resume, projects, and blog posts. The site is built using Go, Templ, HTMX, and Pico CSS. The site also uses &lt;a href="https://keydb.dev" rel="nofollow noopener noreferrer"&gt;KeyDB&lt;/a&gt; as a drop-in replacement for Redis.&lt;/p&gt;

&lt;p&gt;The featured GitHub projects are dynamically retrieved through the power of the &lt;a href="https://docs.github.com/en/graphql" rel="noopener noreferrer"&gt;GitHub GraphQL API&lt;/a&gt;. The blog posts are seamlessly pulled in using the &lt;a href="https://docs.dev.to/api" rel="nofollow"&gt;Dev.to API&lt;/a&gt;. Additionally, KeyDB is used to cache the GitHub and Dev.to API responses for 1 hour to reduce the number of API calls. Icons are provided by &lt;a href="https://fontawesome.com" rel="nofollow noopener noreferrer"&gt;Font Awesome&lt;/a&gt; through their kit from the CDN. I've also implemented the new &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API" rel="nofollow noopener noreferrer"&gt;View Transition API&lt;/a&gt; feature to enhance the user experience.&lt;/p&gt;

&lt;p&gt;There is an old stack version of the site in the &lt;code&gt;sveltekit&lt;/code&gt; branch. The old stack version of the site was built using SveteKit.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Demo&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;Check out the live demo at &lt;a href="https://bagashiz.xyz" rel="nofollow noopener noreferrer"&gt;bagashiz.xyz&lt;/a&gt;!&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Dependencies&lt;/h2&gt;

&lt;/div&gt;


&lt;ul&gt;

&lt;li&gt;

&lt;a href="https://golang.org" rel="nofollow noopener noreferrer"&gt;Go&lt;/a&gt; version 1.22 or…&lt;/li&gt;

&lt;/ul&gt;
&lt;/div&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/bagashiz/portfolio" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


</description>
      <category>webdev</category>
      <category>svelte</category>
      <category>sveltekit</category>
      <category>picocss</category>
    </item>
    <item>
      <title>Building RESTful API with Hexagonal Architecture in Go</title>
      <dc:creator>Bagas Hizbullah</dc:creator>
      <pubDate>Wed, 27 Sep 2023 15:45:16 +0000</pubDate>
      <link>https://dev.to/bagashiz/building-restful-api-with-hexagonal-architecture-in-go-1mij</link>
      <guid>https://dev.to/bagashiz/building-restful-api-with-hexagonal-architecture-in-go-1mij</guid>
      <description>&lt;p&gt;I've been learning how to build web applications using different frameworks and languages for a while now, such as &lt;a href="https://github.com/bagashiz/sea-cinema-tix" rel="noopener noreferrer"&gt;Laravel&lt;/a&gt; with its MVC architecture and &lt;a href="https://github.com/bagashiz/openmusic-api" rel="noopener noreferrer"&gt;Node.js&lt;/a&gt; following the 'Hapi.js Way'. As I'm trying to create a new portfolio project using Go, I found myself contemplating over the ideal project structure. I wanted something that not only aligns with &lt;a href="https://github.com/golang-standards/project-layout" rel="noopener noreferrer"&gt;the standard Go project layout&lt;/a&gt;, but also makes the code both easy to write and understand. That's when I stumbled upon the concept of Hexagonal Architecture, as showcased in &lt;a href="https://netflixtechblog.com/ready-for-changes-with-hexagonal-architecture-b315ec967749" rel="noopener noreferrer"&gt;Netflix's engineering blog&lt;/a&gt;. The idea of seamlessly swapping infrastructures with minimal code changes fascinated me, and I decided to implement it in my new project.&lt;/p&gt;

&lt;p&gt;Now, the remaining question was, &lt;em&gt;"what should I build?"&lt;/em&gt; Fortunately, as I was casually scrolling through YouTube videos, I stumbled upon this hidden gem video titled &lt;a href="https://www.youtube.com/watch?v=uAR1kjyeDtg" rel="noopener noreferrer"&gt;&lt;em&gt;'Ide Project untuk Upgrade Portfolio Backend Engineer'&lt;/em&gt;&lt;/a&gt; by &lt;a href="https://www.youtube.com/@asditaprasetya" rel="noopener noreferrer"&gt;Asdita Prasetya&lt;/a&gt;. He recommended developing a RESTful Point of Sale API service. It felt like the perfect project to put my newfound architectural knowledge into practice. So, getting my coffee and keyboard ready, I started on this exciting learning journey!&lt;/p&gt;

&lt;h2&gt;
  
  
  Hexagonal Architecture
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Create your application to work without either a UI or a database so you can run automated regression-tests against the application, work when the database becomes unavailable, and link applications together without any user involvement.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://alistair.cockburn.us/hexagonal-architecture" rel="noopener noreferrer"&gt;Hexagonal Architecture&lt;/a&gt;, also known as Ports &amp;amp; Adapters Architecture, is one of several ways to build decoupled software systems. It was popularized by &lt;a href="https://alistaircockburn.com/Bio" rel="noopener noreferrer"&gt;Alistair Cockburn&lt;/a&gt;, who is known as one of the initiators of the agile movement in software development. This way of organizing software is great for making applications that are easy to work on and can change without breaking.&lt;/p&gt;

&lt;p&gt;The main idea behind Hexagonal Architecture is to separate the application's core business logic from the external stuff, such as databases, user interfaces, and other external services. It promotes a clean and modular structure that makes it easier to test, maintain, and scale the application.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Core
&lt;/h3&gt;

&lt;p&gt;The core is where the heart of the application lives. It contains the essential business logic and rules of the application. This is where things like processing orders, managing user accounts, and performing all the tasks of the application is designed for. The core should be independent of any external technologies or frameworks, making it highly portable and reusable.&lt;/p&gt;

&lt;p&gt;There are two other essential concepts in Hexagonal Architecture: "ports" and "adapters". These concepts dictates how the "core" interacts with the external components.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ports
&lt;/h3&gt;

&lt;p&gt;Think of ports as contracts or interfaces. They define how the application communicates with external systems or services. For example, a port could specify the rules for connecting to a database, interacting with another web services, or handling user interfaces. Ports belongs to the core, because the core defines which actions are required to achieve the business logic goals.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adapters
&lt;/h3&gt;

&lt;p&gt;Adapters are the ones who implement the contracts or interfaces defined by ports. Adapters are responsible for making sure the application can interact to databases, web services, or other things. They handle the technical details.&lt;/p&gt;

&lt;h3&gt;
  
  
  Driver Actors
&lt;/h3&gt;

&lt;p&gt;Driver actors are the initiators of communication with the core. They reach out to the core to request specific services. Examples of driver actors can be HTTP request or command line interfaces (CLI).&lt;/p&gt;

&lt;h3&gt;
  
  
  Driven Actors
&lt;/h3&gt;

&lt;p&gt;Driven actors are the ones that triggered by the core. If the core needs something from external services, it sends a request to the adapter, instructing it to perform a particular action. For example, if the core needs to store data in a Postgres database, it triggers communication with the Postgres client to execute an INSERT query. In this scenario, the core initiates the communication.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Requirements
&lt;/h3&gt;

&lt;p&gt;Asdita had been kind enough to provide the &lt;a href="https://hellodit.mayar.link/catalog/desain-database-aplikasi-pos" rel="noopener noreferrer"&gt;database schema design&lt;/a&gt; and &lt;a href="https://documenter.getpostman.com/view/4080490/2s93CSnAJa" rel="noopener noreferrer"&gt;Postman API documentation&lt;/a&gt;. So all I had to do was just structure my application to adhere to hexagonal architecture principles and start coding.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tech Stacks
&lt;/h3&gt;

&lt;p&gt;For building the RESTful Point of Sale service API, I've considered and selected a combination of technologies that would work seamlessly together. For handling HTTP requests and responses, using the &lt;a href="https://github.com/gin-gonic/gin" rel="noopener noreferrer"&gt;Gin HTTP web framework&lt;/a&gt; would make sense because I think it seems complete and popular among Go community too. To ensure data integrity and persistence, I'm using &lt;a href="https://www.postgresql.org" rel="noopener noreferrer"&gt;PostgreSQL&lt;/a&gt; database with &lt;a href="https://github.com/jackc/pgx" rel="noopener noreferrer"&gt;pgx&lt;/a&gt; as the database driver, the reason I choose PostgreSQL because it is the most popular relational database to use in production and offers efficient Go integration. I'm also implementing caching using &lt;a href="https://redis.io" rel="noopener noreferrer"&gt;Redis&lt;/a&gt; with &lt;a href="https://github.com/redis/go-redis" rel="noopener noreferrer"&gt;go-redis&lt;/a&gt; client library, which provides powerful in-memory data storage capabilities.&lt;/p&gt;

&lt;h3&gt;
  
  
  Database Schema
&lt;/h3&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;users&lt;/code&gt;: stores user information, including names, emails, passwords, roles (admin or cashier), and timestamps for creation and updates.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;payments&lt;/code&gt;: stores various payment methods. It tracks payment types, names, logos, and timestamps for creation and updates.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;categories&lt;/code&gt;: stores product categories, tracking category names and timestamps for creation and updates.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;products&lt;/code&gt;: stores essential product data, including category IDs, SKUs, names, stock levels, prices, images, and timestamps for creation and updates.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;orders&lt;/code&gt;: stores order details, such as user IDs, payment IDs, customer names, total prices, payments made, returns, receipt codes, and timestamps for creation and updates.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;order_products&lt;/code&gt;: stores record of products included in each order. It notes order and product IDs, quantities, total prices, and timestamps for creation and updates.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Application Architecture
&lt;/h3&gt;

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

&lt;p&gt;The application follows Hexagonal Architecture, depicted as a hexagon. The &lt;code&gt;core&lt;/code&gt; at its center houses &lt;code&gt;domain&lt;/code&gt; and &lt;code&gt;service&lt;/code&gt; containing business logic.&lt;/p&gt;

&lt;p&gt;On the left side of core is a &lt;code&gt;driver port&lt;/code&gt;, an entry point for driver adapter, while on the right side there are 2 &lt;code&gt;driven ports&lt;/code&gt; that act as the gate for the core to interact with driven adapter.&lt;/p&gt;

&lt;p&gt;Around the core, &lt;code&gt;adapters&lt;/code&gt; bridge the core and external world. On the left, the &lt;code&gt;HTTP adapter&lt;/code&gt; handles incoming HTTP requests. On the right, 2 adapters exist: &lt;code&gt;DB adapter&lt;/code&gt; connects to PostgreSQL and &lt;code&gt;Cache adapter&lt;/code&gt; works with Redis.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;driver actors&lt;/code&gt; and &lt;code&gt;driven actors&lt;/code&gt; resides outside of the application. Nginx, on the left, serves as an HTTP server, the &lt;code&gt;driver actor&lt;/code&gt;. On the right, PostgreSQL and Redis represent &lt;code&gt;driven actors&lt;/code&gt; responding to core requests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Project Structure
&lt;/h3&gt;

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

.
├── bin
├── cmd
│   └── http
├── docs
└── internal
    ├── adapter
    │   ├── cache
    │   │   └── redis
    │   ├── handler
    │   │   └── http
    │   ├── repository
    │   │   └── postgres
    │   │       └── migrations
    │   └── token
    │       └── paseto
    └── core
        ├── domain
        ├── port
        ├── service
        └── util

21 directories



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

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;bin&lt;/code&gt;: directory to store compiled executable binary.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docs&lt;/code&gt;: directory to store project's documentation, such as swagger static files.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cmd&lt;/code&gt;: directory for main entry points or commands of the application. The &lt;code&gt;http&lt;/code&gt; sub-directory holds the main HTTP server entry point.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;internal&lt;/code&gt;: directory for containing application code that should not exposed to external packages.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;core&lt;/code&gt;: directory that contains the central business logic of the application. Inside it there are 4 sub-directories.

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;domain&lt;/code&gt;: directory that contains domain models/entities representing core business concepts.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;port&lt;/code&gt;: directory that contains defined interfaces or contracts that adapters must follow.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;service&lt;/code&gt;: directory that contains the business logic or services of the application.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;util&lt;/code&gt;: directory that contains utility functions that reused in the &lt;code&gt;service&lt;/code&gt; package.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;adapters&lt;/code&gt;: directory for containing external services that will interact with the core of application. There are 4 external services used in this application.

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;handler/http&lt;/code&gt;: directory that contains HTTP request and response handler. &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;repository/postgres&lt;/code&gt;: directory that contains database adapters for PostgreSQL.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cache/redis&lt;/code&gt;: directory that contains cache adapters for Redis.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;token/paseto&lt;/code&gt;: directory that contains token generation and validation adapters using Paseto.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Dev tools
&lt;/h3&gt;

&lt;p&gt;I've used several tools to ease things out during development. These tools help in different ways.&lt;/p&gt;
&lt;h4&gt;
  
  
  Podman
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://podman.io" rel="noopener noreferrer"&gt;Podman&lt;/a&gt; is a containerization tool similar to &lt;a href="https://www.docker.com" rel="noopener noreferrer"&gt;Docker&lt;/a&gt;, it manages containers for the application. It simplifies tasks such as building, running, and maintaining containers. It is ideal for creating isolated development environments such as PostgreSQL and Redis instance.&lt;/p&gt;
&lt;h4&gt;
  
  
  DBDocs
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://dbdocs.io" rel="noopener noreferrer"&gt;DBDocs&lt;/a&gt; serves as a database documentation generator. It automates the process of generating documentation for the database schema, aiding in comprehending and managing the database structure, especially when working with relational databases like PostgreSQL.&lt;/p&gt;
&lt;h4&gt;
  
  
  Taskfile
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://taskfile.dev" rel="noopener noreferrer"&gt;Taskfile&lt;/a&gt; is a tool for streamlining repetitive development tasks. It helps automate activities like building, testing, and deploying applications. Unlike &lt;a href="https://www.gnu.org/software/make/manual/make.html" rel="noopener noreferrer"&gt;Makefile&lt;/a&gt;, Taskfile uses YAML for configuration, making it more readable and user-friendly.&lt;/p&gt;
&lt;h4&gt;
  
  
  Golang-migrate
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://github.com/golang-migrate/migrate" rel="noopener noreferrer"&gt;Golang-migrate&lt;/a&gt; is a database migration tool designed for Go applications. It helps manage and apply changes to the database schema as the application grows, ensuring that the code and database structure stay in sync.&lt;/p&gt;
&lt;h4&gt;
  
  
  Air
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://github.com/cosmtrek/air" rel="noopener noreferrer"&gt;Air&lt;/a&gt; is an automatic reloading tool for Go applications. It keeps an eye on code changes and automatically restarts the application, making development more efficient.&lt;/p&gt;
&lt;h4&gt;
  
  
  Swaggo
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://github.com/swaggo/swag" rel="noopener noreferrer"&gt;Swaggo&lt;/a&gt; is a tool that creates Swagger documentation for Go APIs. It makes documenting API endpoints easier, helping developers understand and use the API.&lt;/p&gt;
&lt;h4&gt;
  
  
  Golangci-lint
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://github.com/golangci/golangci-lint" rel="noopener noreferrer"&gt;Golangci-lint&lt;/a&gt; is a tool for checking Go code quality, finding issues, bugs, and style problems. It helps keep the code clean and maintainable.&lt;/p&gt;
&lt;h4&gt;
  
  
  Github Actions
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://github.com/features/actions" rel="noopener noreferrer"&gt;GitHub Actions&lt;/a&gt; is a platform linked with GitHub repositories for automating tasks like building, testing, and deploying applications. It simplifies development and ensures code quality.&lt;/p&gt;
&lt;h3&gt;
  
  
  Deployment
&lt;/h3&gt;

&lt;p&gt;I have created a GitHub Action workflow to automate the containerization process of the application and put it on &lt;a href="https://github.com/bagashiz/go-pos/pkgs/container/go-pos" rel="noopener noreferrer"&gt;GitHub Container Registry&lt;/a&gt;. Then I manually deployed it on my personal VPS with &lt;a href="https://www.nginx.com" rel="noopener noreferrer"&gt;Nginx&lt;/a&gt; as reverse proxy. I might consider writing another blog post to explain my VPS setup.&lt;/p&gt;

&lt;p&gt;You can explore the application's Swagger documentation at this URL: &lt;a href="https://gopos.bagashiz.me/docs/index.html" rel="noopener noreferrer"&gt;&lt;code&gt;https://gopos.bagashiz.me/docs/index.html&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg4c5awnunlu0fgpf3opt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg4c5awnunlu0fgpf3opt.png" alt="Go-POS Swagger Live Documentation" width="800" height="443"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Source Code
&lt;/h2&gt;

&lt;p&gt;I don't want to make this post too long. So if you are interested, please check out the complete source code for this project on my &lt;a href="https://github.com/bagashiz/go-pos" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;. You can access and explore the code base, review the application's structure, and even contribute to its development.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/bagashiz" rel="noopener noreferrer"&gt;
        bagashiz
      &lt;/a&gt; / &lt;a href="https://github.com/bagashiz/go-pos" rel="noopener noreferrer"&gt;
        go-pos
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Simple RESTful Point of Sale (POS) Service API written in Go using Gin web framework, PostgreSQL database, and Redis cache. Proof of concept of implementing Hexagonal Architecture in Go.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Go POS&lt;/h1&gt;
&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Description&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;A simple RESTful Point of Sale (POS) web service written in Go programming language. This project is a part of my learning process in understanding &lt;a href="https://alistair.cockburn.us/hexagonal-architecture/" rel="nofollow noopener noreferrer"&gt;Hexagonal Architecture&lt;/a&gt; in Go.&lt;/p&gt;

&lt;p&gt;It uses &lt;a href="https://gin-gonic.com/" rel="nofollow noopener noreferrer"&gt;Gin&lt;/a&gt; as the HTTP framework and &lt;a href="https://www.postgresql.org/" rel="nofollow noopener noreferrer"&gt;PostgreSQL&lt;/a&gt; as the database with &lt;a href="https://github.com/jackc/pgx/" rel="noopener noreferrer"&gt;pgx&lt;/a&gt; as the driver and &lt;a href="https://github.com/Masterminds/squirrel/" rel="noopener noreferrer"&gt;Squirrel&lt;/a&gt; as the query builder. It also utilizes &lt;a href="https://redis.io/" rel="nofollow noopener noreferrer"&gt;Redis&lt;/a&gt; as the caching layer with &lt;a href="https://github.com/redis/go-redis/" rel="noopener noreferrer"&gt;go-redis&lt;/a&gt; as the client.&lt;/p&gt;

&lt;p&gt;This project idea was inspired by the &lt;a href="https://www.youtube.com/watch?v=uAR1kjyeDtg" rel="nofollow noopener noreferrer"&gt;Ide Project untuk Upgrade Portfolio Backend Engineer&lt;/a&gt; video on YouTube by &lt;a href="https://www.youtube.com/@asditaprasetya" rel="nofollow noopener noreferrer"&gt;Asdita Prasetya&lt;/a&gt;, which provided valuable guidance and inspiration for its development.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Getting Started&lt;/h2&gt;
&lt;/div&gt;


&lt;ol&gt;

&lt;li&gt;

&lt;p&gt;If you do not use devcontainer, ensure you have &lt;a href="https://go.dev/dl/" rel="nofollow noopener noreferrer"&gt;Go&lt;/a&gt; 1.23 or higher and &lt;a href="https://taskfile.dev/installation/" rel="nofollow noopener noreferrer"&gt;Task&lt;/a&gt; installed on your machine:&lt;/p&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;go version &lt;span class="pl-k"&gt;&amp;amp;&amp;amp;&lt;/span&gt; task --version&lt;/pre&gt;

&lt;/div&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;Create a copy of the &lt;code&gt;.env.example&lt;/code&gt; file and rename it to &lt;code&gt;.env&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;cp .env.example .env&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Update configuration values as needed.&lt;/p&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;Install all dependencies, run docker…&lt;/p&gt;


&lt;/li&gt;

&lt;/ol&gt;
&lt;/div&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/bagashiz/go-pos" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;h2&gt;
  
  
  Improvements
&lt;/h2&gt;

&lt;p&gt;I admit that there are a few areas of this project that still have room for improvement:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;As of now, unit tests haven't been implemented yet.&lt;/li&gt;
&lt;li&gt;The project currently uses &lt;a href="https://pkg.go.dev/log/slog" rel="noopener noreferrer"&gt;&lt;code&gt;slog&lt;/code&gt;&lt;/a&gt; package from standard library for logging. But switching to a more advanced logger like &lt;a href="https://github.com/uber-go/zap" rel="noopener noreferrer"&gt;&lt;code&gt;zap&lt;/code&gt;&lt;/a&gt; could offer more flexibility and features.&lt;/li&gt;
&lt;li&gt;Instead of directly accessing environment variables with &lt;code&gt;os.Getenv()&lt;/code&gt;, integrating a configuration handler like &lt;a href="https://github.com/spf13/viper" rel="noopener noreferrer"&gt;&lt;code&gt;viper&lt;/code&gt;&lt;/a&gt; might make it maintainable.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://alistair.cockburn.us/hexagonal-architecture" rel="noopener noreferrer"&gt;Hexagonal Architecture&lt;/a&gt; by Alistair Cockburn&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://netflixtechblog.com/ready-for-changes-with-hexagonal-architecture-b315ec967749" rel="noopener noreferrer"&gt;Ready for changes with Hexagonal Architecture&lt;/a&gt; by Netflix Technology Blog&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://medium.com/@matiasvarela/hexagonal-architecture-in-go-cfd4e436faa3" rel="noopener noreferrer"&gt;Hexagonal Architecture in Go&lt;/a&gt; by Matias Varela&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>go</category>
      <category>api</category>
      <category>backend</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Berkenalan dengan Fedora Silverblue</title>
      <dc:creator>Bagas Hizbullah</dc:creator>
      <pubDate>Sun, 15 Jan 2023 19:47:21 +0000</pubDate>
      <link>https://dev.to/bagashiz/berkenalan-dengan-fedora-silverblue-2i2m</link>
      <guid>https://dev.to/bagashiz/berkenalan-dengan-fedora-silverblue-2i2m</guid>
      <description>&lt;p&gt;Sebagai mahasiswa IT, saya tahu betul betapa pentingnya &lt;em&gt;tools&lt;/em&gt; dan &lt;em&gt;libraries&lt;/em&gt; dalam menyelesaikan tugas dan proyek kuliah saya. Demi tugas kuliah, saya harus menginstall berbagai macam &lt;em&gt;software&lt;/em&gt; seperti Python dengan pip sebagai package manager-nya, Java, PHP, NodeJS, berbagai macam &lt;em&gt;database&lt;/em&gt; seperti MySQL, Postgresql, Mariadb, dan bahkan &lt;em&gt;virtual machine&lt;/em&gt;! Namun, beberapa &lt;em&gt;tools&lt;/em&gt; itu hanya saya gunakan selama menempuh mata kuliah yang membutuhkan saja. Setelah saya menyelesaikan mata kuliah tersebut, saya tidak menggunakan &lt;em&gt;tools&lt;/em&gt; dan &lt;em&gt;libraries&lt;/em&gt; itu lagi. Hal ini dapat menyebabkan komputer saya menjadi berat dan lambat, atau istilahnya &lt;em&gt;bloated&lt;/em&gt;. Oleh karena itu, saya sangat tertarik dengan salah satu sistem operasi berbasis Linux yang tidak biasa, yaitu &lt;strong&gt;&lt;em&gt;Fedora Silverblue&lt;/em&gt;&lt;/strong&gt;!&lt;/p&gt;

&lt;h2&gt;
  
  
  Apa Itu Fedora Silverblue?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3SNxHr9h--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n9vhe5snh2r41ls41u7d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3SNxHr9h--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n9vhe5snh2r41ls41u7d.png" alt="Fedora Silverblue logo" width="224" height="273"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Fedora Silverblue adalah varian dari &lt;a href="https://getfedora.org/workstation/"&gt;Fedora Workstation&lt;/a&gt; yang menerapkan prinsip "&lt;strong&gt;&lt;em&gt;Immutable OS&lt;/em&gt;&lt;/strong&gt;" yang berfokus pada kecepatan, keamanan, &lt;em&gt;atomic updates&lt;/em&gt;, dan &lt;em&gt;immutability&lt;/em&gt;. Fedora Silverblue adalah salah satu sistem operasi desktop generasi baru yang bertujuan untuk memberikan pengalaman yang lebih stabil dan efisien untuk pengguna.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://docs.fedoraproject.org/en-US/fedora-silverblue/"&gt;Fedora Silverblue&lt;/a&gt; is an immutable desktop operating system. It aims to be extremely stable and reliable. It also aims to be an excellent platform for developers and for those using container-focused workflows.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Apa yang dimaksud dari &lt;em&gt;immutable desktop operating system&lt;/em&gt;? Artinya &lt;a href="https://opensource.com/life/16/10/introduction-linux-filesystems"&gt;root filesystem&lt;/a&gt; yang berisi file dan direktori penting untuk sistem operasi berjalan dengan lancar hanya bisa dibaca saja (&lt;em&gt;read-only&lt;/em&gt;). Ini berarti pengguna tidak bisa mengubah root filesystem secara langsung.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--esa4WHqa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ptm878jrxbwxy1yaw3lh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--esa4WHqa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ptm878jrxbwxy1yaw3lh.png" alt="Fedora Silverblue Root Filesystem" width="355" height="630"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Teknologi OSTree
&lt;/h2&gt;

&lt;p&gt;Mungkin Anda bertanya-tanya bagaimana cara meng-install &lt;em&gt;tools&lt;/em&gt; dan &lt;em&gt;libraries&lt;/em&gt; jika root filesystem hanya bisa dibaca saja. Pertama-tama, kita perlu memahami bagaimana cara kerja sistem Fedora Silverblue dibandingkan sistem operasi berbasis Linux tradisional lainnya, seperti Fedora Workstation, Ubuntu, Arch, dan sejenisnya.&lt;/p&gt;

&lt;p&gt;Teknologi inti (&lt;em&gt;core technology&lt;/em&gt;) yang digunakan oleh Fedora Silverblue adalah &lt;a href="https://ostreedev.github.io/ostree/"&gt;OSTree&lt;/a&gt;. OSTree digunakan untuk menyusun, men-deploy, dan meng-update Fedora Silverblue. Cara kerja OSTree mirip dengan &lt;em&gt;version control system&lt;/em&gt; seperti Git, tetapi diterapkan pada seluruh filesystem dibandingkan hanya pada beberapa file saja. Teknologi OSTree ini diterapkan pada tool bernama &lt;a href="https://coreos.github.io/rpm-ostree/"&gt;&lt;code&gt;rpm-ostree&lt;/code&gt;&lt;/a&gt; yang digunakan pada Fedora Silverblue untuk mengelola &lt;em&gt;package&lt;/em&gt; yang terinstall di filesystem. &lt;code&gt;rpm-ostree&lt;/code&gt; meng-install package sebagai "&lt;em&gt;layer&lt;/em&gt;" pada &lt;em&gt;image&lt;/em&gt; yang sudah dibuat oleh &lt;code&gt;ostree&lt;/code&gt;. Kedua teknologi ini menggantikan &lt;code&gt;dnf&lt;/code&gt; yang digunakan pada Fedora Workstation sebagai &lt;em&gt;package manager&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Setiap kali sebuah package di-install menggunakan perintah &lt;code&gt;rpm-ostree install [nama package]&lt;/code&gt; di terminal, sebuah &lt;em&gt;image&lt;/em&gt; baru dibuat dengan package tersebut ter-install di dalamnya. Alhasil, system perlu di-reboot untuk berpindah menggunakan image baru dengan package yang sudah ter-install tadi. Mekanisme ini memungkinkan package untuk di-install pada sistem operasi yang &lt;em&gt;immutable&lt;/em&gt; seperti Fedora Silverblue tanpa mengubah root filesystem secara langsung, jadi sistem tersebut sangat stabil dan &lt;em&gt;reliable&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qNx1cAUb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7bnpn63yctfsw37n1rrp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qNx1cAUb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7bnpn63yctfsw37n1rrp.png" alt="Daftar image yang dibuat oleh OSTree" width="800" height="437"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Mengelola Aplikasi atau &lt;em&gt;Software&lt;/em&gt; Berbasis GUI
&lt;/h2&gt;

&lt;p&gt;Untuk aplikasi atau &lt;em&gt;software&lt;/em&gt; berbasis GUI (&lt;em&gt;Graphical User Interface&lt;/em&gt;), sangat disarankan untuk meng-install menggunakan &lt;em&gt;package manager&lt;/em&gt; &lt;a href="https://flatpak.org/"&gt;Flatpak&lt;/a&gt;. Untuk meng-install sebuah aplikasi pengguna dapat menjalankan perintah &lt;code&gt;flatpak install [nama aplikasi]&lt;/code&gt; dan &lt;code&gt;flatpak update&lt;/code&gt; untuk melakukan pembaruan.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Wm0b5OzJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ss8vk7xo98wowwj0kcnz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Wm0b5OzJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ss8vk7xo98wowwj0kcnz.png" alt="Install aplikasi atau software melalui terminal" width="800" height="667"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Selain melalui perintah di terminal, Pengguna dapat meng-install aplikasi yang diinginkan dengan mudah melalui GNOME Software, yang sudah terintegrasi dengan Flatpak di Fedora Silverblue. Mekanisme ini mirip seperti App Store pada macOS dan Windows Store pada Windows. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--H7S5CwMk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3om12arurrxkv8d9wznr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--H7S5CwMk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3om12arurrxkv8d9wznr.png" alt="Install aplikasi atau software melalui Gnome Software" width="800" height="605"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Menjalankan Container dengan Toolbox
&lt;/h2&gt;

&lt;p&gt;Bagi &lt;em&gt;software developer&lt;/em&gt;, sangat disarankan untuk meng-install &lt;em&gt;tools&lt;/em&gt; dan &lt;em&gt;libraries&lt;/em&gt; yang dibutuhkan di dalam sebuah &lt;a href="https://www.redhat.com/en/topics/containers/"&gt;container&lt;/a&gt; agar tidak mengotori root filesystem sehingga menjadi &lt;em&gt;bloated&lt;/em&gt;. Dengan begitu, sistem akan tetap stabil dan memudahkan dalam melakukan &lt;em&gt;maintenance&lt;/em&gt; desktop. Oleh karena itu, Fedora Silverblue menyediakan fitur bernama &lt;code&gt;toolbox&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://containertoolbx.org/"&gt;&lt;code&gt;toolbox&lt;/code&gt;&lt;/a&gt; adalah sebuah tool untuk memudahkan pembuatan container untuk pengguna awam. Hal ini dilakukan dengan memanfaatkan &lt;em&gt;rootless container&lt;/em&gt; dari &lt;a href="https://podman.io/"&gt;Podman&lt;/a&gt; (Saya juga berencana membahas Podman pada blog selanjutnya.). &lt;code&gt;toolbox&lt;/code&gt; memungkinkan pengguna untuk membuat container dengan mudah dan cepat yang dapat digunakan sebagai &lt;em&gt;development environment&lt;/em&gt;, terpisah dari sistem operasi utama.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KDQW9_ng--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hhratj95qf4i7ask3zzd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KDQW9_ng--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hhratj95qf4i7ask3zzd.png" alt="Lingkungan toolbox terpisah dari sistem operasi utama (*host*)" width="800" height="437"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Manfaat dari Fedora Silverblue dan "&lt;em&gt;Immutable OS&lt;/em&gt;" Secara Umum
&lt;/h2&gt;

&lt;p&gt;Root filesystem yang hanya dapat dibaca (&lt;em&gt;read-only&lt;/em&gt;) dapat meningkatkan ketahanan terhadap kerusakan yang tidak sengaja oleh pengguna serta beberapa jenis &lt;em&gt;malware&lt;/em&gt;. Satu-satunya cara untuk meng-update atau mengubah root filesystem adalah dengan menggunakan &lt;code&gt;rpm-ostree&lt;/code&gt;. Manfaat lainnya adalah hampir mustahil bagi pengguna awam untuk membuat komputer mereka ke dalam kondisi tidak bisa &lt;em&gt;booting&lt;/em&gt; atau tidak berfungsi dengan baik setelah secara sengaja atau tidak sengaja menghapus beberapa komponen sistem.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/0506yDSgU7M"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

</description>
      <category>linux</category>
      <category>fedora</category>
      <category>silverblue</category>
      <category>indonesian</category>
    </item>
  </channel>
</rss>
