$npx -y skills add neo4j-contrib/neo4j-skills --skill neo4j-driver-javascript-skillNeo4j JavaScript/TypeScript Driver v6 — driver lifecycle, executeQuery,
| 1 | ## When to Use |
| 2 | - Writing JS/TS code that connects to Neo4j (Node.js or browser) |
| 3 | - Setting up driver, sessions, transactions, or query execution |
| 4 | - Debugging Integer handling, result consumption, session leaks, async errors |
| 5 | - TypeScript type annotations for driver objects |
| 6 | |
| 7 | ## When NOT to Use |
| 8 | - **Writing/optimizing Cypher** → `neo4j-cypher-skill` |
| 9 | - **Upgrading driver version** → `neo4j-migration-skill` |
| 10 | - **RxJS session API** → [references/rxjs-session.md](references/rxjs-session.md) |
| 11 | |
| 12 | --- |
| 13 | |
| 14 | ## Install |
| 15 | |
| 16 | ```bash |
| 17 | npm install neo4j-driver # or: yarn add neo4j-driver |
| 18 | ``` |
| 19 | |
| 20 | --- |
| 21 | |
| 22 | ## Environment Variables |
| 23 | |
| 24 | Load connection config from environment — never hardcode credentials. |
| 25 | |
| 26 | ```bash |
| 27 | # .env file (add to .gitignore) |
| 28 | NEO4J_URI=neo4j+s://xxx.databases.neo4j.io |
| 29 | NEO4J_USERNAME=neo4j |
| 30 | NEO4J_PASSWORD=secret |
| 31 | NEO4J_DATABASE=neo4j |
| 32 | ``` |
| 33 | |
| 34 | ```javascript |
| 35 | // npm install dotenv (for Node.js < 20 or when .env auto-load is off) |
| 36 | import 'dotenv/config' // or: require('dotenv').config() |
| 37 | |
| 38 | const URI = process.env.NEO4J_URI |
| 39 | const USER = process.env.NEO4J_USERNAME |
| 40 | const PASSWORD = process.env.NEO4J_PASSWORD |
| 41 | const DATABASE = process.env.NEO4J_DATABASE ?? 'neo4j' |
| 42 | ``` |
| 43 | |
| 44 | Node 20+ natively loads `.env` with `--env-file .env`. Next.js / Vite auto-load `.env` — no dotenv import needed. |
| 45 | |
| 46 | --- |
| 47 | |
| 48 | ## Driver Lifecycle |
| 49 | |
| 50 | Create **one driver instance** at startup. Share everywhere. Never create per-request. |
| 51 | |
| 52 | ```javascript |
| 53 | // CommonJS |
| 54 | const neo4j = require('neo4j-driver') |
| 55 | // ESM / TypeScript |
| 56 | import neo4j from 'neo4j-driver' |
| 57 | |
| 58 | const driver = neo4j.driver( |
| 59 | process.env.NEO4J_URI, // 'neo4j+s://xxx.databases.neo4j.io' |
| 60 | neo4j.auth.basic(process.env.NEO4J_USER, process.env.NEO4J_PASSWORD) |
| 61 | ) |
| 62 | await driver.verifyConnectivity() // fail fast on startup if unreachable |
| 63 | // On shutdown: |
| 64 | await driver.close() |
| 65 | ``` |
| 66 | |
| 67 | **URI schemes:** |
| 68 | | Scheme | Transport | Use | |
| 69 | |---|---|---| |
| 70 | | `neo4j+s://` | TLS + cluster routing | Aura; production clusters | |
| 71 | | `neo4j://` | plaintext + cluster routing | local dev cluster | |
| 72 | | `bolt+s://` | TLS, single instance | single Neo4j instance with TLS | |
| 73 | | `bolt://` | plaintext, single instance | local single instance | |
| 74 | |
| 75 | **Auth options:** |
| 76 | ```javascript |
| 77 | neo4j.auth.basic(user, password) // username/password |
| 78 | neo4j.auth.bearer(token) // SSO / JWT |
| 79 | neo4j.auth.kerberos(base64Ticket) // Kerberos |
| 80 | neo4j.auth.none() // unauthenticated (dev only) |
| 81 | ``` |
| 82 | |
| 83 | **Singleton for web frameworks** — create once, import everywhere: |
| 84 | ```javascript |
| 85 | // db.js |
| 86 | let _driver = null |
| 87 | export function getDriver() { |
| 88 | if (!_driver) _driver = neo4j.driver(process.env.NEO4J_URI, |
| 89 | neo4j.auth.basic(process.env.NEO4J_USER, process.env.NEO4J_PASSWORD)) |
| 90 | return _driver |
| 91 | } |
| 92 | export async function closeDriver() { |
| 93 | if (_driver) { await _driver.close(); _driver = null } |
| 94 | } |
| 95 | ``` |
| 96 | |
| 97 | **Serverless** (Lambda/Vercel/Workers): keep `maxConnectionPoolSize: 5`; no guaranteed `SIGTERM`. |
| 98 | |
| 99 | --- |
| 100 | |
| 101 | ## Choose the Right API |
| 102 | |
| 103 | | API | Use when | Auto-retry | Result | |
| 104 | |---|---|---|---| |
| 105 | | `driver.executeQuery()` | Default for most queries | ✅ | eager (all records) | |
| 106 | | `session.executeRead/Write()` | Large results, streaming, multi-query tx | ✅ | lazy stream | |
| 107 | | `session.run()` | `LOAD CSV`, `CALL IN TRANSACTIONS`, scripts | ❌ | lazy stream | |
| 108 | |
| 109 | --- |
| 110 | |
| 111 | ## `executeQuery` — Default |
| 112 | |
| 113 | ```javascript |
| 114 | const { records, summary, keys } = await driver.executeQuery( |
| 115 | 'MATCH (p:Person {name: $name})-[:KNOWS]->(f) RETURN f.name AS name', |
| 116 | { name: 'Alice' }, |
| 117 | { database: 'neo4j', routing: neo4j.routing.READ } |
| 118 | ) |
| 119 | for (const record of records) { |
| 120 | console.log(record.get('name')) // use .get() — records are NOT plain objects |
| 121 | } |
| 122 | |
| 123 | // Write and count results |
| 124 | const { summary: s } = await driver.executeQuery( |
| 125 | 'CREATE (p:Person {name: $name, age: $age})', |
| 126 | { name: 'Bob', age: neo4j.int(30) }, |
| 127 | { database: 'neo4j' } |
| 128 | ) |
| 129 | console.log(s.counters.updates().nodesCreated) // ✅ must call .updates() |
| 130 | ``` |
| 131 | |
| 132 | Always specify `database` — omitting causes an extra round-trip. |
| 133 | |
| 134 | **❌ Never template-literal Cypher:** |
| 135 | ```javascript |
| 136 | // ❌ injection risk + disables plan caching |
| 137 | await driver.executeQuery(`MATCH (p:Person {name: '${name}'}) RETURN p`) |
| 138 | // ✅ parameterised |
| 139 | await driver.executeQuery('MATCH (p:Per |