<?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>Forem: Smaug#6739</title>
    <description>The latest articles on Forem by Smaug#6739 (@smaug6739).</description>
    <link>https://forem.com/smaug6739</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%2F2042020%2F274920d0-62a3-4961-8839-a933bbeb800a.jpg</url>
      <title>Forem: Smaug#6739</title>
      <link>https://forem.com/smaug6739</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/smaug6739"/>
    <language>en</language>
    <item>
      <title>Reactive Tree Management in Nuxt 4: How I Modeled Complex Hierarchies with Pinia</title>
      <dc:creator>Smaug#6739</dc:creator>
      <pubDate>Sat, 01 Nov 2025 12:17:22 +0000</pubDate>
      <link>https://forem.com/smaug6739/reactive-tree-management-in-nuxt-4-how-i-modeled-complex-hierarchies-with-pinia-2m8f</link>
      <guid>https://forem.com/smaug6739/reactive-tree-management-in-nuxt-4-how-i-modeled-complex-hierarchies-with-pinia-2m8f</guid>
      <description>&lt;p&gt;When I started building Alexandrie, I just wanted a fast, offline Markdown note-taking app I could rely on daily.&lt;br&gt;
But as the project grew  with nested categories, shared workspaces, and access permissions it evolved into something much more: a open-source knowledge management platform powered by Nuxt 4 and Go.&lt;/p&gt;

&lt;p&gt;This article walks through one of the toughest challenges I faced: how to model and manage hierarchical data efficiently, from the database to a reactive Pinia store.&lt;/p&gt;
&lt;h2&gt;
  
  
  1. Unified Data Model: “Nodes” Over Multiple Tables
&lt;/h2&gt;

&lt;p&gt;Early in development, I realized that managing &lt;strong&gt;categories, documents, and files&lt;/strong&gt; as separate entities was becoming painful.&lt;br&gt;&lt;br&gt;
Every new feature  especially &lt;strong&gt;sharing&lt;/strong&gt; and &lt;strong&gt;permissions&lt;/strong&gt; required deeper joins and complex recursive queries.&lt;/p&gt;

&lt;p&gt;Here’s what the early structure looked like conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Workspace
 ├── Category A
 │    ├── Document 1
 │    └── Document 2
 └── Category B
      ├── Subcategory
      │    └── Document 3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  1.1 The problem with separate tables
&lt;/h3&gt;

&lt;p&gt;Having multiple tables (&lt;code&gt;categories&lt;/code&gt;, &lt;code&gt;documents&lt;/code&gt;, &lt;code&gt;resources&lt;/code&gt;) worked fine until I introduced access control.&lt;br&gt;&lt;br&gt;
At that point, even simple questions like “what can this user see in this subtree?” required multiple recursive joins.&lt;br&gt;&lt;br&gt;
Performance and maintainability started to suffer.&lt;/p&gt;
&lt;h3&gt;
  
  
  1.2 The unified “nodes” approach
&lt;/h3&gt;

&lt;p&gt;The solution was to merge everything into a &lt;strong&gt;single table&lt;/strong&gt; a unified model where everything is a &lt;code&gt;node&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;nodes&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;BIGINT&lt;/span&gt; &lt;span class="n"&gt;PK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="nb"&gt;BIGINT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;parent_id&lt;/span&gt; &lt;span class="nb"&gt;BIGINT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;role&lt;/span&gt; &lt;span class="nb"&gt;SMALLINT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;workspace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;
  &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="nb"&gt;LONGTEXT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;content_compiled&lt;/span&gt; &lt;span class="nb"&gt;LONGTEXT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;order&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;updated_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this model, every element document, folder, file became a node.&lt;br&gt;
This made the hierarchy recursive but uniform, enabling simple queries like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“What can user X access in this subtree?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now, permission propagation and tree traversal both use a single recursive CTE or indexed path column, drastically improving maintainability and speed.&lt;/p&gt;
&lt;h2&gt;
  
  
  2. Scalable Permission System
&lt;/h2&gt;

&lt;p&gt;Building permissions on top of the nodes table required careful thought. I adopted a hybrid approach:&lt;/p&gt;

&lt;p&gt;A separate permissions table maps &lt;code&gt;user_id&lt;/code&gt;, &lt;code&gt;node_id&lt;/code&gt;, &lt;code&gt;permission_level&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;On access checks, the system checks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If the user owns the target node&lt;/li&gt;
&lt;li&gt;If a direct permission exists&lt;/li&gt;
&lt;li&gt;If any ancestor node grants sufficient permission&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This balances fine-grained control and inheritance users get access to everything under a node they’ve been granted permission for, without repeated recursive checks.&lt;/p&gt;
&lt;h2&gt;
  
  
  3. Backend Stack: Go + REST + Modular Design
&lt;/h2&gt;

&lt;p&gt;Language &amp;amp; framework: Go (Gin) lightweight and performant for API endpoints.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Language&lt;/strong&gt;: Go (Gin) for its simplicity, performance, and clean REST design.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database&lt;/strong&gt;: MySQL (or compatible) raw SQL for critical queries like subtree retrieval.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;File storage&lt;/strong&gt;: S3-compatible (MinIO, RustFS) abstracted via a pluggable service for self-hosted setups.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The API uses a flat structure (&lt;code&gt;/nodes&lt;/code&gt;, &lt;code&gt;/permissions&lt;/code&gt;, &lt;code&gt;/users&lt;/code&gt;) simple, predictable, and easy to version.&lt;/p&gt;

&lt;p&gt;I separated business logic from data access (DAO layer) to keep the backend maintainable and extensible.&lt;br&gt;
This paid off when refactoring: new storage backends or permission engines can now be added with minimal changes.&lt;/p&gt;
&lt;h2&gt;
  
  
  4. Frontend Architecture: Data management
&lt;/h2&gt;

&lt;p&gt;Surprisingly, the hardest part of building Alexandrie’s frontend wasn’t the UI it was the data layer.&lt;br&gt;
Representing thousands of interlinked notes in a reactive, permission-aware tree required careful design.&lt;/p&gt;

&lt;p&gt;In Alexandrie, &lt;strong&gt;everything is a Node&lt;/strong&gt;.&lt;br&gt;
A node can be a workspace, category, document, or resource and each can contain others.&lt;br&gt;
That means infinite nesting, partial hydration, and live updates when any part of the tree changes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Challenge&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In Alexandrie, everything is a Node.&lt;br&gt;
Each node can be a workspace, category, document, or resource, and every node can contain others effectively forming a tree structure that must remain reactive, searchable, and permission-aware across the app.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The main challenges:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Nested data: Users can nest documents/categories/resources infinitely deep.&lt;/li&gt;
&lt;li&gt;Partial hydration: Nodes are often fetched lazily or shared publicly, so the store must handle both partial and fully-hydrated nodes.&lt;/li&gt;
&lt;li&gt;Permission inheritance: Access rights propagate through parent nodes.&lt;/li&gt;
&lt;li&gt;Real-time reactivity: Any node update must immediately reflect across trees, search results, and UI components.&lt;/li&gt;
&lt;li&gt;Performance: Traversing large trees shouldn’t cause noticeable slowdowns.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  The Solution
&lt;/h3&gt;

&lt;p&gt;I built a dedicated Pinia store (useNodesStore) combined with a TreeStructure utility that keeps all nodes in a flat Collection (essentially a reactive Map), and reconstructs the hierarchical tree on demand.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;useNodesStore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defineStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;nodes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Collection&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Node&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;allTags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;
    &lt;span class="na"&gt;isFetching&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="na"&gt;getters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;getById&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;state&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;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;getChilds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;state&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;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parent_id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;getAllChildrens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* recursive logic */&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* lazy hydration of nodes */&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* merges partial and full states */&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* removes entire subtree */&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, a dedicated &lt;code&gt;TreeStructure&lt;/code&gt; class handles building the actual trees efficiently:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TreeStructure&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;itemMap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Item&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;private&lt;/span&gt; &lt;span class="nx"&gt;childrenMap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Item&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;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for &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;item&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;items&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="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;childrenMap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parent_id&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&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;childrenMap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parent_id&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;childrenMap&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;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parent_id&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&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;itemMap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;generateTree&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;Item&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;items&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parent_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;buildTree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;root&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;Set&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 gives:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;O(1) access to any node via itemMap&lt;/li&gt;
&lt;li&gt;Efficient subtree generation (only rebuild what’s needed)&lt;/li&gt;
&lt;li&gt;A clean way to filter, search, or recompute derived data (tags, permissions, etc.)&lt;/li&gt;
&lt;li&gt;Integration with Pinia’s reactivity: the entire graph updates live when a node changes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Lessons &amp;amp; Trade-offs
&lt;/h2&gt;

&lt;p&gt;Here are some high-level takeaways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Unify your data model early. Splitting multiple tables will bite you when adding features like permissions and sharing.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Favor simplicity in API contracts. Flat endpoints scale better than deeply nested resource structures.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Design for extensibility, not just immediate features. Adding plugin-like syntax blocks or alternate storage later becomes much easier.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Permissions and hierarchy are hard. Caching accessible node sets and flattening ancestors helps avoid recursive query bottlenecks. &lt;/p&gt;

&lt;h2&gt;
  
  
  What’s Next &amp;amp; How to Contribute
&lt;/h2&gt;

&lt;p&gt;Alexandrie is open-source and welcomes contributors. Areas where help is most welcome:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;UI/UX improvements&lt;/li&gt;
&lt;li&gt;Implement full offline support&lt;/li&gt;
&lt;li&gt;Add some cool new features&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’re interested in self-hosted knowledge tools or modern note apps, feel free to star or contribute!&lt;/p&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/Smaug6739/Alexandrie" rel="noopener noreferrer"&gt;https://github.com/Smaug6739/Alexandrie&lt;/a&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>webdev</category>
      <category>architecture</category>
      <category>nuxt</category>
    </item>
    <item>
      <title>I built a Markdown note-taking app for students and creators — and I’d love your feedback</title>
      <dc:creator>Smaug#6739</dc:creator>
      <pubDate>Mon, 28 Jul 2025 11:51:27 +0000</pubDate>
      <link>https://forem.com/smaug6739/i-built-a-markdown-note-taking-app-for-students-and-creators-and-id-love-your-feedback-o0o</link>
      <guid>https://forem.com/smaug6739/i-built-a-markdown-note-taking-app-for-students-and-creators-and-id-love-your-feedback-o0o</guid>
      <description>&lt;p&gt;Hi👋&lt;/p&gt;

&lt;p&gt;Over the past few years, I’ve been working on a personal side project that turned into something I now use every day: Alexandrie — a note-taking app built with students, developers, and content creators in mind.&lt;/p&gt;

&lt;p&gt;I’m publishing this not as a product pitch, but as an open invitation to explore, test, break, and maybe even contribute to the app — especially if you're interested in performance-focused web apps, offline-first design, and clean UI/UX.&lt;/p&gt;

&lt;h2&gt;
  
  
  ✨ What is Alexandrie?
&lt;/h2&gt;

&lt;p&gt;Alexandrie is a web-based Markdown note-taking app designed to be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✍️ Minimal and elegant — because tools should get out of your way&lt;/li&gt;
&lt;li&gt;⚡ Lightweight and fast — built for speed, even with hundreds of documents&lt;/li&gt;
&lt;li&gt;🌐 Offline-friendly — your notes stay available even without a connection&lt;/li&gt;
&lt;li&gt;🛠️ Custom productivity features — snippets, keyboard shortcuts, instant formatting&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;It’s designed first and foremost for students (like myself when I started it), but also fits the needs of developers and creators who want a frictionless writing and organization tool.&lt;/p&gt;

&lt;p&gt;🧠 The Philosophy Behind It&lt;br&gt;
I’ve tried tools like Notion, Obsidian, and many others. While they’re great, I often found them too heavy, too slow, or visually cluttered for daily academic or technical writing. And plain Markdown editors often lack structure and comfort.&lt;/p&gt;

&lt;p&gt;I wanted a tool that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is visually pleasant (especially for long writing sessions)&lt;/li&gt;
&lt;li&gt;Respects performance and runs well even on modest devices&lt;/li&gt;
&lt;li&gt;Works offline by default (ideal in classrooms, on trains, in bad Wi-Fi)&lt;/li&gt;
&lt;li&gt;Handles hundreds of notes without slowing down&lt;/li&gt;
&lt;li&gt;Supports shortcuts, snippets, and fast navigation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🧰 Tech Stack&lt;br&gt;
Alexandrie is built with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Frontend&lt;/strong&gt;: Vue.js + Nuxt 3 (SSR, PWA-ready)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend&lt;/strong&gt;: Go, REST API, simple auth system&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storage&lt;/strong&gt;: MinIO for file/media uploads (e.g. images in notes)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database&lt;/strong&gt;: MySQL, with raw SQL queries separated from business logic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offline mode&lt;/strong&gt;: Service Workers for seamless note access&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🧪 Things I’m still improving
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Better search and filtering&lt;/li&gt;
&lt;li&gt;Better UI and UX with animations etc&lt;/li&gt;
&lt;li&gt;Full PWA installation support&lt;/li&gt;
&lt;li&gt;User settings &amp;amp; themes&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  💡 Want to contribute ?
&lt;/h2&gt;

&lt;p&gt;If you’re interested in open source, Alexandrie is a great playground for:&lt;/p&gt;

&lt;p&gt;Vue/Nuxt 3 devs who like clean UIs and components&lt;/p&gt;

&lt;p&gt;Go backend developers who care about simplicity and performance&lt;/p&gt;

&lt;p&gt;People who love improving documentation or writing tutorials&lt;/p&gt;

&lt;p&gt;UX enthusiasts with ideas on improving offline-first apps&lt;/p&gt;

&lt;p&gt;You can also just drop feedback, file bugs, or suggest features — I read everything!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🔗 GitHub: &lt;a href="https://github.com/Smaug6739/Alexandrie" rel="noopener noreferrer"&gt;https://github.com/Smaug6739/Alexandrie&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Some app features images&lt;/strong&gt;&lt;/p&gt;

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

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

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>tooling</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
