Docs / Migrate
Migrating from MongoDB
Documents become nodes; references and embedded structures become relationships. The work is mostly deciding what stays a property and what becomes its own node, then a short driver script loads it, turning the connections your app tracked by hand into first-class, queryable edges.
Decide your model first
For a typical blog schema (orgs, users, posts):
- Collection → label. A document in
usersbecomes a(:User); top-level scalar fields become properties. - Reference → relationship.
user.orgIdbecomes(:User)-[:MEMBER_OF]->(:Org). - Embedded array of objects → nodes + edges. A post's
comments[]becomes(:Post)-[:HAS_COMMENT]->(:Comment): now queryable and shareable, not buried in a document. - Array of scalars → property or nodes. Keep
tags[]as a list property, or promote to(:Tag)nodes if you query by tag (recommended). - Simple embedded object → properties. An
addresssub-document with no identity stays as flattened properties.
Before you start
- A Tetra database + your connection string and API key (free DEMO MODE works).
mongoexport(ships with the MongoDB tools) and Node withneo4j-driverinstalled.
Steps
Export each collection
mongoexportwrites newline-delimited JSON, one file per collection.mongoexport --collection=orgs --out=orgs.json mongoexport --collection=users --out=users.json mongoexport --collection=posts --out=posts.json
Load with a driver script
A short script gives you full control over nested data:
MERGEkeeps shared entities (a user referenced by many posts) de-duplicated, andUNWINDturns embedded arrays into their own nodes and edges. Load referenced collections first.import neo4j from 'neo4j-driver'; import { readFileSync } from 'node:fs'; const driver = neo4j.driver( 'bolt://tetra-ca.com?db=YOUR_DB', neo4j.auth.basic('apikey', '<YOUR_API_KEY>')); const session = driver.session(); // mongoexport writes newline-delimited JSON; ObjectIds arrive as { "$oid": "…" }. const load = (f) => readFileSync(f, 'utf8').trim().split('\n').map(JSON.parse); const oid = (v) => (v && v.$oid) || v; // orgs → (:Org), users → (:User)-[:MEMBER_OF]->(:Org) for (const o of load('orgs.json')) await session.run('MERGE (:Org {id: $id, name: $name})', { id: oid(o._id), name: o.name }); for (const u of load('users.json')) await session.run( `MERGE (user:User {id: $id}) SET user.email = $email WITH user MATCH (org:Org {id: $orgId}) MERGE (user)-[:MEMBER_OF]->(org)`, { id: oid(u._id), email: u.email, orgId: oid(u.orgId) }); // posts → author ref + embedded comments[] + tags[] become nodes & edges for (const p of load('posts.json')) await session.run( `MERGE (post:Post {id: $id}) SET post.title = $title MERGE (author:User {id: $authorId}) MERGE (post)-[:AUTHORED_BY]->(author) WITH post UNWIND $comments AS c MERGE (cm:Comment {id: c.id}) SET cm.body = c.body MERGE (post)-[:HAS_COMMENT]->(cm) MERGE (cu:User {id: c.userId}) MERGE (cm)-[:BY]->(cu) WITH DISTINCT post UNWIND $tags AS t MERGE (tag:Tag {name: t}) MERGE (post)-[:TAGGED]->(tag)`, { id: oid(p._id), title: p.title, authorId: oid(p.authorId), comments: (p.comments || []).map((c) => ({ ...c, id: oid(c._id) })), tags: p.tags || [] }); await session.close(); await driver.close();Verify
Reconcile node counts against your collection counts, then spot-check a few relationships.
// Compare to db.<collection>.countDocuments() on the Mongo side: MATCH (n) RETURN labels(n)[0] AS label, count(*) AS n ORDER BY label; MATCH ()-[r]->() RETURN type(r) AS rel, count(*) AS n ORDER BY rel;
Tips & gotchas
- ObjectIds:
mongoexportwraps them as{"$oid": "…"}: extract the string and use it as the nodeid(theoid()helper above). - Use
MERGE, notCREATE, on ids so re-running the script is idempotent and shared references collapse to one node. - Dates: Mongo
ISODateexports as{"$date": "…"}: wrap withdatetime()on load. - Big collections: batch the loop (e.g. 5–10k docs per transaction) rather than one giant write.
- The payoff: joins MongoDB couldn't do natively (multi-hop, "friends of friends", recommendation paths) are now single Cypher queries.
Next steps
Connect a driver or your AI assistant from the dashboard, or see Migrating from Neo4j · PostgreSQL.