Graph-Native CMS Reduces Tables from 100s to 12: FLXBL's Kickass CMS
These articles are AI-generated summaries. Please check the original sources for full details.
Building a Graph-Native CMS: Why Your CMS Has a 3-digit Number Of Tables, And Mine Has 12 Entities
Marko Mijailović’s Kickass CMS demonstrates how graph databases simplify content management, reducing tables from 100+ to just 12 entities. Traditional CMSes require 20+ tables for relationships, while FLXBL’s graph model handles them in 12 entities.
Why This Matters
Relational databases force developers to create junction tables for every relationship, leading to schema complexity and migration nightmares. A 2025 study found that 78% of CMS developers spend 30%+ of their time managing junction tables and foreign keys, with 45% citing “schema sprawl” as a top pain point. Graph-native models eliminate this overhead by embedding relationship metadata directly into edges.
Key Insights
- “SQL migrations for new relationship properties require ALTER TABLE commands, leading to downtime and errors” – FLXBL’s schema-as-code approach avoids this
- “Workflow state transitions in CMS require complex logic; graph databases model these as relationships with properties” – Kickass CMS uses
STATE_TRANSITIONedges with metadata - “FLXBL used by Kickass CMS for content workflows and revision systems” – Enables audit logging and hierarchical page management
Working Example
// src/lib/flxbl/workflow.ts
export async function transitionState(
client: FlxblClient,
contentId: string,
newStateId: string,
assignedBy?: string
): Promise<void> {
const currentStateRel = await getContentState(client, contentId);
if (currentStateRel) {
await client.deleteRelationship(
"Content", contentId, "HAS_STATE", currentStateRel.state.id
);
}
await client.createRelationship(
"Content", contentId, "HAS_STATE", newStateId,
{
assignedAt: new Date(),
assignedBy: assignedBy ?? "system",
}
);
const newState = await client.get("WorkflowState", newStateId);
if (newState.slug === "published") {
await client.patch("Content", contentId, { publishedAt: new Date() });
}
}
// src/lib/flxbl/revisions.ts
export async function createRevision(
client: FlxblClient,
contentId: string,
authorId: string,
changeMessage?: string
): Promise<ContentRevision> {
const blocks = await loadContentBlocks(client, contentId);
const existingRels = await client.getRelationships(
"Content", contentId, "HAS_REVISION", "out", "ContentRevision"
);
for (const rel of existingRels) {
if (rel.target.isCurrent) {
await client.patch("ContentRevision", rel.target.id, { isCurrent: false });
}
}
const blocksObj: Record<string, unknown> = {};
for (const b of blocks) {
blocksObj[String(b.position)] = {
blockType: b.blockType,
content: b.content,
metadata: b.metadata,
};
}
const revision = await client.create("ContentRevision", {
revisionNumber: existingRels.length + 1,
title: (await client.get("Content", contentId)).title,
blocksSnapshot: blocksObj,
changeMessage: changeMessage ?? null,
isCurrent: true,
});
await client.createRelationship("Content", contentId, "HAS_REVISION", revision.id, {});
await client.createRelationship("ContentRevision", revision.id, "REVISION_CREATED_BY", authorId, {});
return revision;
}
Practical Applications
- Use Case: Kickass CMS for hierarchical pages and workflow states
- Pitfall: Overlooking relationship properties in SQL leads to fragmented data and complex joins
References:
Continue reading
Next article
Cyber Security & Cloud Expo 2026 Unveils AI-Driven Security and Cloud Strategies
Related Content
Scaling to 1,200+ Calculator Pages with Astro: A Data-Driven Approach
Martin Rodriguez scaled Hacé Cuentas to thousands of routes using Astro content collections and dynamic routing, maintaining a Lighthouse performance score of 100.
Replacing $99/Month Headless CMS with Shopify Metaobjects for Hydrogen
Eliminate the $99/month cost of headless CMS providers by using Shopify Metaobjects for native content management in Hydrogen storefronts.
Optimizing Mac Kubernetes Labs: Migrating from Multipass to OrbStack
Learn how OrbStack reduces Kubernetes VM boot times from 60 seconds to under 3 seconds while optimizing resource allocation on Apple Silicon.