<?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: Nicolai Kilian</title>
    <description>The latest articles on DEV Community by Nicolai Kilian (@nicolai_kilian_1a1311afc5).</description>
    <link>https://dev.to/nicolai_kilian_1a1311afc5</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%2F1590938%2Fb5f04b46-2883-4678-8934-bf3cc6013b8e.jpg</url>
      <title>DEV Community: Nicolai Kilian</title>
      <link>https://dev.to/nicolai_kilian_1a1311afc5</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nicolai_kilian_1a1311afc5"/>
    <language>en</language>
    <item>
      <title>I Built a TypeScript Object-Graph Database</title>
      <dc:creator>Nicolai Kilian</dc:creator>
      <pubDate>Thu, 14 May 2026 20:55:04 +0000</pubDate>
      <link>https://dev.to/nicolai_kilian_1a1311afc5/i-built-a-typescript-object-graph-database-because-i-got-tired-of-flattening-everything-k4o</link>
      <guid>https://dev.to/nicolai_kilian_1a1311afc5/i-built-a-typescript-object-graph-database-because-i-got-tired-of-flattening-everything-k4o</guid>
      <description>&lt;p&gt;Originally published on my blog:&lt;br&gt;
&lt;a href="https://nicolai.hashnode.dev/i-built-a-typescript-object-graph-database-because-i-got-tired-of-flattening-everything" rel="noopener noreferrer"&gt;https://nicolai.hashnode.dev/i-built-a-typescript-object-graph-database-because-i-got-tired-of-flattening-everything&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I like rich domain models.&lt;/p&gt;

&lt;p&gt;Objects that point to other objects. Shared references. Classes with behavior. Maps. Sets. Sometimes cycles, because real-world domains have a rude habit of being less tidy than our storage layers would prefer.&lt;/p&gt;

&lt;p&gt;In TypeScript, this can feel very natural. You build a root object, hang your domain off it, pass references around, and the shape of the program starts to match the shape of the problem.&lt;/p&gt;

&lt;p&gt;Then persistence enters the room and quietly starts rearranging the furniture.&lt;/p&gt;

&lt;p&gt;Tables. Join tables. Mapper code. DTOs. Rehydration logic. ORM behavior you mostly understand until Thursday afternoon. All the familiar stuff.&lt;/p&gt;

&lt;p&gt;This is not a complaint about Postgres. I like Postgres. I use Postgres. If anything, Postgres has earned the right to look at most new database ideas with mild disappointment.&lt;/p&gt;

&lt;p&gt;Still, I kept running into cases where the application state wanted to be an object graph, and the persistence model wanted something else entirely. The translation layer became a project inside the project.&lt;/p&gt;

&lt;p&gt;So I started building GraphVault.&lt;/p&gt;
&lt;h2&gt;
  
  
  The EclipseStore Spark
&lt;/h2&gt;

&lt;p&gt;The original spark came from &lt;a href="https://eclipsestore.io/" rel="noopener noreferrer"&gt;EclipseStore&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I liked the core idea immediately: persist the object graph itself. Let the application keep its natural shape. Treat the graph as the primary data structure instead of forcing it through a different model first.&lt;/p&gt;

&lt;p&gt;That idea stuck with me because even a familiar issue tracker becomes graph-shaped once you look past the main list of tickets. The interesting parts are the links: blockers, duplicates, related incidents, epics, customer context, workflow history, audit events, and the chain of "why did this change?" A table can store those facts, of course. But the domain you reason about is the network between them.&lt;/p&gt;

&lt;p&gt;I wanted to see what this would look like in TypeScript.&lt;/p&gt;
&lt;h2&gt;
  
  
  What GraphVault Does
&lt;/h2&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%2Ftsj0ckkzsvwxprrje9gc.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%2Ftsj0ckkzsvwxprrje9gc.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GraphVault is embedded object-graph persistence for TypeScript and NestJS.&lt;/p&gt;

&lt;p&gt;You start with a root object, mutate ordinary TypeScript objects, and commit explicitly when you want to persist the current graph.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;storage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;EmbeddedStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;storageDirectory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./data&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;root&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;documents&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="nx"&gt;storage&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="nx"&gt;documents&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="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;doc-1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello object graph&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;await&lt;/span&gt; &lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;storeRoot&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The graph can contain shared references, cycles, classes, &lt;code&gt;Map&lt;/code&gt;, &lt;code&gt;Set&lt;/code&gt;, &lt;code&gt;Date&lt;/code&gt;, &lt;code&gt;Buffer&lt;/code&gt;, &lt;code&gt;bigint&lt;/code&gt;, typed arrays, and other JavaScript values. Object identity is preserved, so two references to the same object still point to the same object after reload.&lt;/p&gt;

&lt;p&gt;That was the first milestone: store the graph and get it back intact.&lt;/p&gt;

&lt;p&gt;Then the list grew, as these things do.&lt;/p&gt;

&lt;p&gt;GraphVault now has transactions with rollback, optimistic and pessimistic locking, WAL recovery, fencing tokens for shared stores, persistent indexes, schema migrations, health checks, NestJS integration, and a graphical admin tool called GraphVault Studio.&lt;/p&gt;

&lt;p&gt;At some point the weekend experiment started asking for production shoes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Exists
&lt;/h2&gt;

&lt;p&gt;There are plenty of excellent databases already. I am not trying to talk anyone out of using them.&lt;/p&gt;

&lt;p&gt;GraphVault has a specific shape in mind: an application owns a rich object model and wants persistence close to that model.&lt;/p&gt;

&lt;p&gt;A few examples where this can make sense:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;issue and case-management systems with deeply connected entities&lt;/li&gt;
&lt;li&gt;rules engines where state and relationships matter&lt;/li&gt;
&lt;li&gt;simulations with object identity and references&lt;/li&gt;
&lt;li&gt;local-first or embedded apps&lt;/li&gt;
&lt;li&gt;internal admin-heavy tools&lt;/li&gt;
&lt;li&gt;services exposing bounded subgraphs through REST endpoints&lt;/li&gt;
&lt;li&gt;TypeScript/NestJS apps where the domain model is already the source of truth&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In those situations, the usual database mapping layer can become surprisingly noisy. You end up maintaining two models: the one your application wants and the one your database wants.&lt;/p&gt;

&lt;p&gt;GraphVault tries to reduce that gap.&lt;/p&gt;

&lt;h2&gt;
  
  
  Querying the Graph
&lt;/h2&gt;

&lt;p&gt;Persistence alone is not enough. Once the graph is stored, you need to inspect it, search it, aggregate over it, and sometimes make controlled updates.&lt;/p&gt;

&lt;p&gt;So GraphVault includes GVQL, the GraphVault Query Language.&lt;/p&gt;

&lt;p&gt;It gives you graph patterns, filters, joins across references, grouping, aggregates, execution plans, and preview-first batch updates. The goal is to make stored object graphs operable, not just serializable.&lt;/p&gt;

&lt;p&gt;That matters a lot for admin tooling. A persisted graph without good inspection tools quickly turns into "good luck, here is a giant blob." GraphVault Studio exists because I did not want the admin story to end at opening files and squinting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vibe-Coded with Codex
&lt;/h2&gt;

&lt;p&gt;GraphVault was built with Codex.&lt;/p&gt;

&lt;p&gt;I gave the direction, the feature ideas, the quality bar, and the feedback. Codex did the implementation work: architecture, code, refactorings, tests, documentation, benchmarks, CI fixes, release preparation, and plenty of unglamorous glue work in between.&lt;/p&gt;

&lt;p&gt;The process felt less like "using a code generator" and more like steering a very fast engineering team. I would describe what I wanted, test the result, complain when the API felt wrong, ask for stronger guarantees, push on edge cases, and raise the bar whenever the project started looking too much like a prototype.&lt;/p&gt;

&lt;p&gt;Codex kept turning those prompts into working software.&lt;/p&gt;

&lt;p&gt;This is exactly the kind of project that makes me think AI-assisted software development is not just about saving time. It changes what a single developer can attempt.&lt;/p&gt;

&lt;p&gt;Of course, the result still has to stand on its own. Code does not get extra credit because an AI helped write it. If anything, it deserves more scrutiny. That is why I care so much about tests, benchmarks, explicit boundaries, and feedback from people who know databases well.&lt;/p&gt;

&lt;p&gt;But I am not going to pretend this was built the old way. It was not. GraphVault is a real experiment in building serious software with Codex in the loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Makes It Interesting to Me
&lt;/h2&gt;

&lt;p&gt;The question behind GraphVault is simple:&lt;/p&gt;

&lt;p&gt;Can persistence stay closer to the shape of a TypeScript application?&lt;/p&gt;

&lt;p&gt;I do not think the answer is always yes. Often the right answer is still a mature database with decades of battle scars and tooling.&lt;/p&gt;

&lt;p&gt;But there are domains where the object graph is the most honest representation of the state. In those domains, object-graph persistence can feel very natural.&lt;/p&gt;

&lt;p&gt;The challenge is making it operationally credible. That is where the recent work has gone: transactions, WAL, locking, fencing tokens, indexes, migrations, health checks, benchmarks, package smoke tests, and clearer documentation of the boundaries.&lt;/p&gt;

&lt;h2&gt;
  
  
  Current Status
&lt;/h2&gt;

&lt;p&gt;GraphVault is young, but usable.&lt;/p&gt;

&lt;p&gt;It has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;npm package: &lt;code&gt;@sprengmeister/graphvault&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;TypeScript-first API&lt;/li&gt;
&lt;li&gt;NestJS module and transaction decorator&lt;/li&gt;
&lt;li&gt;persistent indexes&lt;/li&gt;
&lt;li&gt;GVQL query and batch-update language&lt;/li&gt;
&lt;li&gt;schema migrations with &lt;code&gt;up&lt;/code&gt; and &lt;code&gt;down&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;WAL recovery and transaction metadata&lt;/li&gt;
&lt;li&gt;optimistic and pessimistic locking&lt;/li&gt;
&lt;li&gt;health and safety reports&lt;/li&gt;
&lt;li&gt;GraphVault Studio as an admin UI&lt;/li&gt;
&lt;li&gt;tests, benchmarks, and package smoke tests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The boundaries are important. GraphVault is embedded, application-owned storage. It does not provide SQL wire compatibility, external user/role management, built-in replication, or distributed consensus.&lt;/p&gt;

&lt;p&gt;That said, if you are building a TypeScript or NestJS app whose natural state is a connected object graph, it might be worth a look.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/Sprengmeister-dev/graphvault-library" rel="noopener noreferrer"&gt;https://github.com/Sprengmeister-dev/graphvault-library&lt;/a&gt;&lt;br&gt;
Admin UI: &lt;a href="https://github.com/Sprengmeister-dev/graphvault-studio" rel="noopener noreferrer"&gt;https://github.com/Sprengmeister-dev/graphvault-studio&lt;/a&gt;&lt;br&gt;
npm: &lt;code&gt;@sprengmeister/graphvault&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;I would love feedback, especially from people who care about databases enough to be skeptical. &lt;/p&gt;

</description>
      <category>typescript</category>
      <category>node</category>
      <category>database</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
