DEV Community

Cover image for Building "6 Degrees of Kevin Bacon" with a Graph-Native Backend: Why I Created FLXBL
Marko Mijailović for FLXBL

Posted on

Building "6 Degrees of Kevin Bacon" with a Graph-Native Backend: Why I Created FLXBL

TL;DR: After years of wrestling with recursive CTEs, junction tables, and the existential dread of N+1 queries, I built a graph-native Backend-as-a-Service called FLXBL. To prove it actually works (and isn't just vaporware from a sleep-deprived developer), I built a mock movie discovery platform that finds the shortest path between any two actors. Here's what I learned—and why you might want to ditch your relational database for relationship-heavy applications.

FLXBL


The Problem That Started It All

Have you ever played the Six Degrees of Kevin Bacon game? The premise is beautifully simple: any actor in Hollywood can be connected to Kevin Bacon through their film appearances in six steps or fewer. Tom Hanks was in Apollo 13 with Kevin Bacon—that's one degree. Meryl Streep was in The River Wild with Kevin Bacon—also one degree.

Simple game. Not-so-simple data problem.

The SQL Nightmare

Here's what I realized when I first tried to build this: finding a path between two actors requires either recursive queries or graph traversal—and relational databases are notoriously bad at the former while not supporting the latter at all.

Let me illustrate with some SQL that will give you nightmares:

WITH RECURSIVE actor_path AS (
  -- Base case: start from Actor A
  SELECT 
    p.id as actor_id,
    m.id as movie_id,
    ARRAY[p.id] as path,
    1 as depth
  FROM persons p
  JOIN acted_in ai ON p.id = ai.person_id
  JOIN movies m ON ai.movie_id = m.id
  WHERE p.id = $start_actor_id

  UNION ALL

  -- Recursive case: find co-stars (and pray for your database's soul)
  SELECT 
    p2.id,
    m2.id,
    ap.path || p2.id,
    ap.depth + 1
  FROM actor_path ap
  JOIN acted_in ai1 ON ap.movie_id = ai1.movie_id
  JOIN persons p2 ON ai1.person_id = p2.id
  JOIN acted_in ai2 ON p2.id = ai2.person_id
  JOIN movies m2 ON ai2.movie_id = m2.id
  WHERE NOT (p2.id = ANY(ap.path))  -- Cycle detection
    AND ap.depth < 6
)
SELECT * FROM actor_path WHERE actor_id = $end_actor_id LIMIT 1;
Enter fullscreen mode Exit fullscreen mode

This query has more joins than a yoga class. And it gets exponentially slower as the search depth increases—each hop multiplies the result set by the average number of co-stars (~50 per actor). By hop 3, you're looking at 125,000 potential paths. By hop 6? Your DBA is updating their resume.

The Realization

I kept running into this problem across different projects: social networks (friends-of-friends), recommendation engines (users-who-liked-also-liked), organizational hierarchies (who-reports-to-whom), supply chains (component-dependencies). The pattern was always the same: entities connected by relationships, where the relationships matter as much as the entities themselves.

And every time, I found myself:

  1. Creating junction tables with foreign keys
  2. Writing recursive CTEs that made my eyes bleed
  3. Implementing manual cycle detection
  4. Watching query times go from milliseconds to "let's grab coffee"

That's when I thought: What if the database understood relationships natively?

That's why I built FLXBL.


What is FLXBL?

FLXBL (pronounced "flexible") is a Graph-Native Backend-as-a-Service. You define your schema with entities and relationships using a visual editor, and FLXBL automatically generates production-ready REST and GraphQL APIs.

The magic? Under the hood, your data lives in a graph database where relationships are first-class citizens—not foreign keys bolted onto tables.

The Two-Plane Architecture (AKA "The Clever Bit")

FLXBL uses a two-plane architecture that gives you the best of both worlds:

┌─────────────────────────────────────────────────────────────┐
│                    FLXBL Platform                           │
├──────────────────────────────┬──────────────────────────────┤
│     Control Plane            │      Data Plane              │
│     (PostgreSQL)             │      (Neo4j)                 │
│                              │                              │
│  • Schema definitions        │  • Your actual data          │
│  • User authentication       │  • Relationships as edges    │
│  • API key management        │  • Graph traversal queries   │
│  • Webhook configurations    │  • Blazing fast path-finding │
│  • Migration tracking        │                              │
└──────────────────────────────┴──────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

PostgreSQL handles all the "boring but important" stuff—users, permissions, schemas, API keys. Neo4j handles your actual application data, storing it as a graph where relationships have equal standing with entities.

Why does this matter? Because when you query "find all actors who worked with Tom Hanks," you're not performing JOINs across junction tables—you're literally traversing edges in a graph. The database was designed for this operation.

How FLXBL Differs from Firebase/Supabase

Feature Firebase Supabase FLXBL
Data Model Document (NoSQL) Relational (PostgreSQL) Graph (Neo4j)
Relationships Denormalized/manual Foreign keys + JOINs First-class edges
Relationship Properties Stored in documents Junction tables Native edge properties
Multi-hop Queries Manual, N+1 prone Recursive CTEs (pain) Native traversal
Visual Schema Editor No Limited Drag-and-drop with live preview
Auto-generated APIs REST only REST + GraphQL (via PostGraphile) REST + GraphQL (native parity)
AI Integration No No MCP tools for AI-assisted editors, standalone AI apps

The biggest differentiator: FLXBL treats relationships as first-class citizens. If your application is relationship-heavy, you're working with the data model instead of fighting against it.


The "6 Degrees of Kevin Bacon" Project

To demonstrate what FLXBL can do (and frankly, to have some fun), I built a complete movie discovery platform: 6 Degrees of Kevin Bacon.

Kate Winslet getting that Bacon

The app lets you select any two actors and finds the shortest path between them through their shared filmography. Behind the scenes, it's running a bi-directional breadth-first search across a graph of ~800 actors and movies—and it finds connections.

Full source code: flxbl-dev/6-degrees-of-bacon


Modeling the Schema: Thinking in Graphs

When you use FLXBL, you think in terms of entities (the nodes) and relationships (the edges connecting them). Coming from a relational mindset, this is both liberating and slightly disorienting—like learning to ride a bike but the bike is made of concepts.

The Entities

I started with six core entities:

Entity Purpose Key Fields
Movie Films in the database title, releaseYear, posterUrl, tmdbRating
Person Actors, directors, writers name, photoUrl, biography
Genre Categories like Action, Drama name
User Platform users email, username
Review User ratings and reviews rating, content
Collection Movie franchises, curated lists name, description

The Relationships (Where the Magic Happens)

Here's where FLXBL diverges from relational thinking. In PostgreSQL, you'd model "Actor appeared in Movie" with a junction table:

CREATE TABLE acted_in (
  person_id UUID REFERENCES persons(id),
  movie_id UUID REFERENCES movies(id),
  character VARCHAR(255),
  billing_order INTEGER,
  is_lead_role BOOLEAN,
  PRIMARY KEY (person_id, movie_id)
);
Enter fullscreen mode Exit fullscreen mode

Three extra columns, a primary key constraint, and you still need JOINs to query it. In FLXBL, it's a relationship with properties:

Person --[ACTED_IN]--> Movie
  └── character: "Forrest Gump"
  └── billingOrder: 1
  └── isLeadRole: true
Enter fullscreen mode Exit fullscreen mode

The relationship itself carries data. When I query for Tom Hanks's filmography, I get both the movies AND the characters he played—in a single traversal. No JOINs. No N+1 queries. Just... data.

Here's the complete relationship model for the project:

Relationship From → To Properties Purpose
ACTED_IN Person → Movie character, billingOrder, isLeadRole Track who played whom
DIRECTED Person → Movie Directorial credits
WROTE Person → Movie Writing credits
HAS_GENRE Movie → Genre Movie categorization
WROTE_REVIEW User → Review Review authorship
HAS_REVIEW Movie → Review Reviews for movies
FOLLOWS User → User followedAt Social graph
WATCHLIST User → Movie addedAt, priority User's watch queue
WATCHED User → Movie watchedAt, rewatchCount Watch history

Notice FOLLOWS? It connects Users to other Users. Try modeling that elegantly in SQL. Go ahead, I'll wait.

(Spoiler: You'll end up with a self-referential junction table and a recursive CTE that questions your career choices.)


The Code: Graph Queries That Don't Make You Cry

Let me show you the actual difference with code from the project.

Fetching an Actor's Movies (GraphQL)

With FLXBL's auto-generated GraphQL API, I can fetch an actor's movies and their character names in one query:

query GetPersonMovies($personId: ID!) {
  person(id: $personId) {
    name
    photoUrl
    moviesConnection {
      edges {
        character        # ← Relationship property!
        billingOrder
        isLeadRole
        node {
          id
          title
          releaseYear
          posterUrl
        }
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

The moviesConnection returns edges with both the relationship properties (character, billingOrder) and the connected movie nodes. One request. Complete data. Zero existential dread.

The Query DSL: SQL for Graph People

FLXBL also provides a powerful Query DSL for REST that translates to optimized Cypher queries. Here's a real query from the project:

query GetActorFilmography($name: String!) {
  people(where: { name: { equals: $name } }, limit: 1) {
    id
    name
    photoUrl
    bio
    birthPlace

    # Traverse ACTED_IN relationship with edge properties
    moviesConnection {
      edges {
        # Relationship properties
        character
        billingOrder
        isLeadRole

        # Target movie with nested genre traversal
        node {
          id
          title
          releaseYear
          averageRating
          boxOffice
          posterUrl

          # Another traversal: Movie  Genres
          genres {
            name
            iconEmoji
          }
        }
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

This query says: "In a single request, fetch an actor's complete filmography with the characters they played, whether it was a lead role, and each movie's genres - traversing 3 levels of the graph."

In SQL, this would be... let's just say "significantly more verbose."

The Path-Finding Algorithm: Bi-Directional BFS

The signature feature—finding connections between actors—uses bi-directional breadth-first search. Instead of searching from Actor A all the way to Actor B (which explodes exponentially), I search from both ends and meet in the middle:

Tom Hanks ──────────────> <────────────── Meryl Streep
         Forward BFS        Backward BFS
                      ↓
               Meeting Point! 🎉
Enter fullscreen mode Exit fullscreen mode

This reduces complexity from O(b^d) to O(2 × b^(d/2)), where b is the branching factor (~50 co-stars per person) and d is the degrees of separation.

Translation for non-algorithm nerds: Instead of checking millions of paths, we check thousands. Your users get results in seconds instead of never.

Here's a simplified version of the algorithm:

export async function findConnectionPath(
  startPersonId: string,
  endPersonId: string,
  maxDepth: number = 6
): Promise<ConnectionResult | null> {
  // Initialize two frontiers - one from each actor
  const forwardVisited = new Map<string, BFSNode>();
  const backwardVisited = new Map<string, BFSNode>();

  let forwardQueue = [startPersonId];
  let backwardQueue = [endPersonId];

  while (totalDepth < maxDepth) {
    // Expand the smaller frontier (more efficient)
    const expandForward = forwardQueue.length <= backwardQueue.length;

    if (expandForward) {
      // Get co-stars via FLXBL's relationship endpoint
      const result = await expandFrontier(
        forwardQueue,
        forwardVisited,
        backwardVisited  // Check if frontiers have met!
      );

      if (result) {
        // 🎉 Frontiers met - reconstruct path
        return reconstructBidirectionalPath(result.meetingPersonId, ...);
      }
    }
    // ... expand backward frontier similarly
  }

  return { found: false, degrees: -1, path: [] };
}
Enter fullscreen mode Exit fullscreen mode

The getCoStarsOptimized function uses a hybrid approach—GraphQL for fetching movies (gets relationship properties in one call), and parallelized REST calls for fetching movie casts:

export async function getCoStarsOptimized(personId: string): Promise<CostarWithMovie[]> {
  // GraphQL: Get all movies this person acted in (with their characters)
  const movies = await getPersonMoviesGraphQL(personId);

  // REST: Fetch cast for each movie in parallel (FLXBL handles the load)
  const castResults = await Promise.all(
    movies.map(async (movie) => ({
      movie,
      cast: await getMovieCast(movie.id),
    }))
  );

  // Combine into co-star list with movie context
  // No N+1 queries because FLXBL batches relationship traversals
  return results;
}
Enter fullscreen mode Exit fullscreen mode

The Developer Experience: From Zero to API

Let me walk you through what it actually feels like to use FLXBL.

Step 1: Design Your Schema Visually

No YAML files. No migration scripts. Just drag, drop, and connect, fill in the names, the types, the descriptions (if you want to be nice to people that are going to implement what you're doodling).

Step 2: Get Your APIs Instantly

The moment you publish your schema, FLXBL generates:

REST Endpoints (for every entity):

GET    /api/v1/dynamic/Person           # List all
POST   /api/v1/dynamic/Person           # Create
GET    /api/v1/dynamic/Person/:id       # Get one
PUT    /api/v1/dynamic/Person/:id       # Update
DELETE /api/v1/dynamic/Person/:id       # Delete
POST   /api/v1/dynamic/Person/query     # Query DSL
Enter fullscreen mode Exit fullscreen mode

Relationship Endpoints:

POST   /api/v1/dynamic/Person/:id/relationships/ACTED_IN
GET    /api/v1/dynamic/Person/:id/relationships/ACTED_IN?direction=out
DELETE /api/v1/dynamic/Person/:id/relationships/ACTED_IN/:targetId
Enter fullscreen mode Exit fullscreen mode

GraphQL Endpoint:

POST   /api/v1/dynamic-gql/{tenantId}
GET    /api/v1/dynamic-gql/{tenantId}/schema.graphql  # Download SDL
Enter fullscreen mode Exit fullscreen mode

Step 3: Generate Type-Safe Clients

Use the MCP tools (more on this below) or the API to generate TypeScript interfaces, Zod schemas, or Python Pydantic models:

// Auto-generated from your FLXBL schema
interface Person {
  id: string;
  name: string;
  photoUrl?: string;
  biography?: string;
  createdAt: string;
  updatedAt: string;
}

interface ActedInRelationship {
  character?: string;
  billingOrder?: number;
  isLeadRole?: boolean;
}
Enter fullscreen mode Exit fullscreen mode

No more any types. No more runtime surprises. Just... actual type safety.


MCP Integration: AI-Assisted Backend Development

Here's something that made even me double-take: FLXBL has native integration with Model Context Protocol (MCP), which means AI assistants in Cursor, Windsurf, or VS Code can directly interact with your FLXBL schema.

What Can the AI Actually Do?

Tool What It Does
validate_schema Check your schema for errors before publishing
publish_schema_version Create a new schema version from natural language
check_migration_status Track migration progress
generate_client_types Generate TypeScript, Zod, Python, or API contracts
list_schema_templates Browse pre-built templates for common use cases

Real Example Workflow

Here's an actual conversation I had with Claude in Cursor:

Me: "Add a Reviews entity to my Movie schema. Users should be able to rate movies 1-5 and leave comments."

Claude: reads current schema via flxbl://schema/active

Claude: "I'll add a Review entity with rating (NUMBER, 1-5), content (TEXT), and create relationships: WROTE_REVIEW (User → Review) and HAS_REVIEW (Movie → Review). Should I include a timestamp for when the review was written?"

Me: "Yes, add createdAt as a DATETIME."

Claude: uses publish_schema_version tool

Claude: "Done! Schema version 1.3.0 published. The migration is in progress—I'll check the status."

uses check_migration_status

Claude: "Migration complete. Your new endpoints are ready:

  • POST /api/v1/dynamic/Review
  • GET /api/v1/dynamic/Movie/:id/relationships/HAS_REVIEW
  • ..."

No context switching. No documentation diving. Just... chill. (And production-ready APIs.)

IDE Configuration

{
  "mcpServers": {
    "flxbl": {
      "command": "npx",
      "args": ["@flxbl-dev/mcp"],
      "env": {
        "FLXBL_INSTANCE_URL": "https://api.flxbl.dev",
        "FLXBL_API_KEY": "flxbl_your_key_here"
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Security: Because Your Data Matters

A few things I baked in from day one:

Multi-Tenant Isolation

Every query includes automatic tenant filtering. Your data is your data—there's no universe where a misconfigured query leaks data between tenants.

API Key Security

flxbl_<8-char-prefix>_<48-char-secret>
Enter fullscreen mode Exit fullscreen mode
  • Prefix for fast lookup without exposing the secret
  • 192 bits of entropy
  • Bcrypt-hashed in storage
  • Never logged, never exposed

Webhook Verification

All webhooks include HMAC-SHA256 signatures so you can verify they actually came from FLXBL:

const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, secret) {
  const hmac = crypto.createHmac('sha256', secret);
  hmac.update(JSON.stringify(payload));
  return signature === hmac.digest('hex');
}
Enter fullscreen mode Exit fullscreen mode

Try It Yourself

The complete "6 Degrees of Kevin Bacon" project is open source and ready to clone:

The repo includes a TMDB seeding script that populates your FLXBL instance with ~50 movies and ~200 actors centered around Kevin Bacon—enough data to demonstrate meaningful path-finding and have some fun discovering unexpected connections.


When Should You Use FLXBL?

FLXBL is ideal for:

  • Social applications — followers, friends, activity feeds
  • E-commerce — product relationships, "frequently bought together"
  • Content platforms — authors, posts, comments, tags
  • Knowledge graphs — interconnected concepts, research papers
  • Organizational tools — reporting hierarchies, team dependencies
  • Recommendation engines — user-item-feature relationships
  • Anything with "six degrees" problems — and there are more than you think

FLXBL might be overkill for:

  • Simple CRUD apps with no relationships
  • Document-centric applications
  • Time-series data
  • Applications that need raw SQL access

What's Next?

FLXBL is in public beta, and I'm actively building based on developer feedback. Some things on the roadmap:

  • More schema templates — CRM, CMS, project management out of the box
  • Enhanced webhook integrations — Zapier, Slack, and more
  • Query DSL improvements — aggregations, nested traversals
  • Real-time subscriptions — GraphQL subscriptions for live updates
  • Self-hosted option — for those who need it on-prem

If you're building something relationship-heavy and tired of fighting your database, give FLXBL a try. I'd genuinely love to hear what you build—and what pain points you hit. That's how this thing gets better.


The Bottom Line

Every time I see a recursive CTE or a five-table JOIN for a simple "friends of friends" query, a small part of me dies. Relational databases are incredible tools—but they're not the right tool for every job.

If your data is fundamentally about connections, relationships, and paths between things, a graph-native approach isn't just marginally better—it's a different paradigm. Queries that were "theoretically possible but practically insane" become trivial. Data models that required mental gymnastics become intuitive.

FLXBL is my attempt to bring that paradigm to developers without requiring them to become Neo4j experts, Cypher query authors, or DevOps wizards. Define your schema. Get your API. Build something cool.

What's the most relationship-heavy application you've built? How did you model it? Drop a comment below—I'm genuinely curious about the creative solutions people have found (or the pain they've endured).


Marko Mijailović is the creator of FLXBL. You can find him on LinkedIn or reach out through email, or join FLXBL Discord

Top comments (0)