Why Neo4j Became My Default Database (Yes, Even for Simple Data)
By Chetan Dhandal
I spent three days writing a SQL query that should have taken three minutes. By the end of it, I had created a monster,a 12-JOIN behemoth that made my developer stare at the screen in silence for a full minute before asking, "Does this... actually work?"
It did work. Barely. And that was the problem.
I've built systems with every major database you can name. For years, PostgreSQL was my answer to everything. Simple CRUD app? Postgres. Complex analytics? Postgres with some creative JOINs. Even when things got messy, I'd just add another JOIN, another subquery, another index. I knew Postgres inside and out, and I was convinced that with enough SQL wizardry, I could solve any problem.
Then came the social recommendation feature that humbled me.
When Your Tool Becomes Your Prison
Here's what the product team asked for: "Show users products that their friends have bought, but they haven't."
Sounds simple, right? I thought so too.
I was working on an e-commerce platform with about 2 million users and growing. We had standard relational tables: users, products, orders, order_items, and a user_friends mapping table. Clean, normalized, textbook database design.
Three days later, I had this:
SELECT DISTINCT p.*
FROM products p
JOIN order_items oi_friend ON p.id = oi_friend.product_id
JOIN orders o_friend ON oi_friend.order_id = o_friend.id
JOIN users friend ON o_friend.user_id = friend.id
JOIN user_friends_map ufm ON friend.id = ufm.friend_id
JOIN users u ON ufm.user_id = u.id
WHERE u.id = 'my-user-id'
AND p.id NOT IN (
SELECT p_user.id
FROM products p_user
JOIN order_items oi_user ON p_user.id = oi_user.product_id
JOIN orders o_user ON oi_user.order_id = o_user.id
WHERE o_user.user_id = 'my-user-id'
);
And honestly? This is the cleaned-up version. The real query had more edge cases, more nested subqueries, and took 4-6 seconds to run. For a single user.
I remember sitting in the code review feeling defensive. "We just need to add some indexes," I said. "Maybe denormalize a bit. Cache it."
But deep down, I knew. The problem wasn't the query. It was me trying to force a fundamentally connected problem into a tabular structure that was never designed for it.
The Moment Everything Clicked
A friend mentioned Neo4j over coffee. I'd heard of it,"that graph database thing",but had dismissed it as niche. After all, I had Postgres. What more did I need?
Desperate, I spent a weekend learning Cypher (Neo4j's query language). And here's what that same query looked like:
MATCH (u:User {id: 'my-user-id'})-[:FRIENDS_WITH]->(friend)
MATCH (friend)-[:BOUGHT]->(product:Product)
WHERE NOT (u)-[:BOUGHT]->(product)
RETURN DISTINCT product
That's it. Three lines.
I stared at my screen. Then I ran it. 15 milliseconds.
I ran it again. 12 milliseconds.
I felt like I'd been using a hammer to drive in screws for years, and someone had just handed me a screwdriver.
How It Actually Works (And Why It's Fast)
Here's what Neo4j taught me: sometimes you're not solving the wrong problem,you're using the wrong model.
In a relational database, relationships are computed at query time through JOINs. When I asked Postgres to find a user's friends, it had to:
- Scan the user_friends_map table
- Match user IDs
- Join to the users table
- Then repeat this process for orders, order_items, and products
- Then do it all again for the exclusion part
Every. Single. Time.
Neo4j flips this around. Relationships are stored on disk, right alongside the data. When I create a friendship, Neo4j literally stores a pointer from one User node to another with a [:FRIENDS_WITH] relationship.
Finding friends isn't a table scan,it's following pointers. That's why it's O(1) or O(k) complexity, where k is the number of relationships, not the size of your entire table.
I didn't just get a performance improvement. I got a mental model that matched the problem I was solving.
Where I Use It Now (And Where I Don't)
After that experience, I went a bit overboard. I tried using Neo4j for everything. Spoiler: that was a mistake.
Here's where Neo4j has become my first choice:
Knowledge Graphs for RAG Systems
I'm building AI applications that need to understand complex business contexts. Vector search is great for similarity, but it can't answer "What suppliers does this customer use, and which of those suppliers are at financial risk based on their recent orders?"
That's a graph problem. I store entities (customers, suppliers, orders) as nodes and relationships as edges. When the LLM needs context, I query the graph to build rich, relationship-aware context. This has been game-changing for enterprise AI.
Recommendation Engines
After my initial experience, I built a "recommended for you" system that looked at purchase history, browsing behavior, and social connections. The Neo4j version shipped in two weeks. The SQL version we started with was still being optimized three months later.
Dependency Tracking in CI/CD
I modeled our microservices architecture as a graph: services as nodes, dependencies as edges. "If Service X goes down, what breaks?" became a 5-line Cypher query that runs in milliseconds. Our ops team now uses it for impact analysis before deployments.
Fraud Detection
"Does this new account share an IP address, device ID, or credit card with any flagged accounts?" In SQL, this required unions, self-joins, and took 8-12 seconds. In Neo4j, it's a simple pattern match that runs in under 50ms.
But here's where I used to avoid Neo4j:
- Simple tabular data (user profiles, settings)
- High-volume transactional logging
- Binary data storage
I said "used to" because my thinking has evolved recently, and it's changed how I approach database selection.
Why Neo4j Is Now My Default (Even for "Simple" Data)
Here's what changed my mind: Every organization I work with now wants a chatbot or AI assistant on top of their data.
It's not optional anymore. It's the first question in requirements meetings: "Can users ask questions about this in natural language?"
And here's the uncomfortable truth I discovered: building a natural language to SQL query engine is brutally difficult.
I spent two months on a project trying to convert user questions like "Show me customers who haven't ordered in 3 months but have high lifetime value" into reliable SQL queries. We tried prompt engineering, fine-tuning, RAG with schema documentation, few-shot examples,everything.
The problem? SQL queries are brittle. One wrong JOIN, one incorrect WHERE clause, and you get garbage results. Users don't know when the AI messed up. They just see wrong data and lose trust.
With Neo4j and Cypher, it's fundamentally different.
Cypher queries mirror how humans think about data: "Find customers, filter by those who haven't ordered recently, check their total order values." The graph structure means relationships are explicit, not inferred from foreign keys scattered across a schema.
When I feed an LLM a graph schema, it can generate reliable Cypher queries with far less hallucination. Why? Because:
- Relationships are named and explicit:
(Customer)-[:PLACED]->(Order)is clearer thanorders.customer_id = customers.id - Patterns match natural language: "Find customers who placed orders" directly maps to
(c:Customer)-[:PLACED]->(o:Order) - Less room for error: You can't accidentally cartesian join or forget a critical WHERE clause as easily
- Schema is discoverable: You can query the graph structure itself to understand what's possible
I recently rebuilt that customer insights bot on top of Neo4j. It took one week, and the accuracy went from ~60% to ~95%. Users now trust it because it consistently returns correct results.
So here's my updated stance: Even if your data is "simple" and tabular, if there's any chance you'll need to put an AI assistant on top of it, start with Neo4j.
The migration headache of moving from Postgres to Neo4j later,when everyone's already demanding chat interfaces,is far worse than just building it right the first time.
Obviously, I still don't use Neo4j for:
- High-write volume logging (time-series databases win)
- Binary/blob storage (object stores are cheaper)
- When I'm working with a team that refuses to learn Cypher and there's no AI/bot requirement
But for business data that humans will query,either directly or through AI? Neo4j has become my default first choice.
The Elephant in the Room: Cost
Let me be honest,when I first pitched Neo4j, the finance team nearly had a heart attack. AuraDB (Neo4j's managed service) and Enterprise Edition licenses aren't cheap, especially at scale.
So I made a different choice: self-hosting with Community Edition.
Here's the important part: Neo4j Community Edition is completely legal to use in commercial applications. It's open-source (GPLv3), and it's free.
But,and this is a big but,you need to know what you're giving up:
- No clustering: Single-node only
- No automated backups: You handle this yourself
- No role-based security: Basic auth only
- No multi-database support: One database per instance
- No hot failover: If it goes down, it's down
- No high availability: See above
- No enterprise monitoring tools: Roll your own
- No professional support: Stack Overflow is your friend
If you need any of those features, Enterprise Edition is required. Period.
For my use cases,recommendation systems, knowledge graphs for RAG, fraud detection,I didn't need clustering or HA. I built my own backup scripts (simple cron jobs), implemented monitoring with Prometheus and Grafana, and accepted that a database restart means brief downtime.
The tradeoff is simple: I exchange licensing fees for my time. I've deployed it on AWS EC2 instances, Azure VMs, and Kubernetes clusters. I handle configuration, monitoring, backups, and scaling myself.
For some teams, that's a dealbreaker. For me,as someone who enjoys the infrastructure side of architecture,it's been worth it. I get powerful graph database capabilities while keeping costs tied to infrastructure, not licenses.
Is this the right choice for everyone? No. If you need HA, clustering, or don't have the time to manage databases yourself, Community Edition won't cut it. But for startups, side projects, and even some production systems where you can tolerate brief downtime, self-hosting Community Edition is a viable path that more architects should consider.
What I'd Tell My Past Self
If I could go back to that moment when I was three days into writing a 12-JOIN query, here's what I'd say:
Stop. And think about where this data is headed.
Sure, your requirement today is just "show friend recommendations." But in six months, someone's going to ask for a chatbot. In a year, they'll want natural language queries. The CEO will see a demo of ChatGPT and suddenly everyone needs AI on top of your database.
I've seen this pattern repeat across every project I've worked on in the past two years. The AI question isn't "if",it's "when."
And when that moment comes, if you've built on SQL, you're in for a world of pain. You'll spend months trying to teach an LLM to generate reliable JOINs, debugging why it keeps producing nonsense queries, and rebuilding trust with users after it returns wrong data.
Or you could start with Neo4j now.
Not just for the graph problems. Not just for the performance. But because Cypher queries map to how humans,and LLMs,naturally think about data. Because when that inevitable AI requirement lands on your desk, you'll ship it in a week instead of fighting it for months.
Neo4j didn't replace my entire stack. I still use time-series databases for logs, object stores for files, and caching layers where they make sense. But for business data that humans need to understand and query? Neo4j has become my default first choice.
Not because it's trendy. Not because graphs are "cool." But because I've learned that the real cost isn't the database license or the learning curve,it's the cost of building on the wrong foundation and having to migrate later when AI requirements hit.
The best architecture decisions aren't about solving today's problem. They're about anticipating tomorrow's. And tomorrow, everyone wants to talk to their data.
Have you ever had a moment where switching tools completely changed how you thought about a problem? I'd love to hear your stories.
Enjoyed this?
Abhanga delivers one essay like this every Saturday.