1 | # Neo4j Driver for JavaScript
|
2 |
|
3 | A database driver for Neo4j 3.0.0+.
|
4 |
|
5 | Resources to get you started:
|
6 |
|
7 | - Detailed docs _Not available yet_
|
8 | - [Neo4j Manual](https://neo4j.com/docs/)
|
9 | - [Neo4j Refcard](https://neo4j.com/docs/cypher-refcard/current/)
|
10 |
|
11 | ## What's New
|
12 |
|
13 | - Upcoming version is now named as `4.0.0` instead of `2.0.0` to better align with server versions.
|
14 | - Reactive API (built on top of RxJS) is now available with 4.0 version server, which includes reactive protocol improvements.
|
15 | - Session instances can now be acquired against a specific database against a multi-database server, which is available with 4.0 version server.
|
16 | - A new `driver.verifyConnectivity()` method is introduced for connectivity verification purposes.
|
17 | - Driver default configuration for `encrypted` is now `false` (meaning that driver will only attempt clear text connections by default), and when encryption is explicitly enabled the default trust mode is TRUST_SYSTEM_CA_SIGNED_CERTIFICATES which relies upon underlying system's certificate trust settings.
|
18 |
|
19 | ## Breaking Changes
|
20 |
|
21 | - Driver API is moved from `neo4j.v1` to `neo4j` namespace.
|
22 | - `driver#session()` method now makes use of object destructuring rather than positional arguments (see [Acquiring a Session](#acquiring-a-session) for examples).
|
23 | - `session#close()` and `driver#close()` both now return `Promise`s and no more accept callback function arguments.
|
24 | - `driver.onError` and `driver.onCompleted` callbacks are completely removed. Errors should be monitored on related code paths (i.e. through `Promise#catch`, etc.).
|
25 | - `bolt+routing` scheme is now renamed to `neo4j`. `neo4j` scheme is designed to work work with all possible 4.0 server deployments, but `bolt` scheme is still available for explicit single instance connections.
|
26 |
|
27 | ## Including the Driver
|
28 |
|
29 | ### In Node.js application
|
30 |
|
31 | Stable channel:
|
32 |
|
33 | ```shell
|
34 | npm install neo4j-driver
|
35 | ```
|
36 |
|
37 | Pre-release channel:
|
38 |
|
39 | ```shell
|
40 | npm install neo4j-driver@next
|
41 | ```
|
42 |
|
43 | Please note that `@next` only points to pre-releases that are not suitable for production use.
|
44 | To get the latest stable release omit `@next` part altogether or use `@latest` instead.
|
45 |
|
46 | ```javascript
|
47 | var neo4j = require('neo4j-driver')
|
48 | ```
|
49 |
|
50 | Driver instance should be closed when Node.js application exits:
|
51 |
|
52 | ```javascript
|
53 | driver.close() // returns a Promise
|
54 | ```
|
55 |
|
56 | otherwise application shutdown might hang or it might exit with a non-zero exit code.
|
57 |
|
58 | ### In web browser
|
59 |
|
60 | We build a special browser version of the driver, which supports connecting to Neo4j over WebSockets.
|
61 | It can be included in an HTML page using one of the following tags:
|
62 |
|
63 | ```html
|
64 | <!-- Direct reference -->
|
65 | <script src="lib/browser/neo4j-web.min.js"></script>
|
66 |
|
67 | <!-- unpkg CDN non-minified -->
|
68 | <script src="https://unpkg.com/neo4j-driver"></script>
|
69 | <!-- unpkg CDN minified for production use, version X.Y.Z -->
|
70 | <script src="https://unpkg.com/neo4j-driver@X.Y.Z/lib/browser/neo4j-web.min.js"></script>
|
71 |
|
72 | <!-- jsDelivr CDN non-minified -->
|
73 | <script src="https://cdn.jsdelivr.net/npm/neo4j-driver"></script>
|
74 | <!-- jsDelivr CDN minified for production use, version X.Y.Z -->
|
75 | <script src="https://cdn.jsdelivr.net/npm/neo4j-driver@X.Y.Z/lib/browser/neo4j-web.min.js"></script>
|
76 | ```
|
77 |
|
78 | This will make a global `neo4j` object available, where you can access the driver API at `neo4j`\*:
|
79 |
|
80 | ```javascript
|
81 | var driver = neo4j.driver(
|
82 | 'neo4j://localhost',
|
83 | neo4j.auth.basic('neo4j', 'neo4j')
|
84 | )
|
85 | ```
|
86 |
|
87 | It is not required to explicitly close the driver on a web page. Web browser should gracefully close all open
|
88 | WebSockets when the page is unloaded. However, driver instance should be explicitly closed when it's lifetime
|
89 | is not the same as the lifetime of the web page:
|
90 |
|
91 | ```javascript
|
92 | driver.close() // returns a Promise
|
93 | ```
|
94 |
|
95 | ## Usage examples
|
96 |
|
97 | ### Constructing a Driver
|
98 |
|
99 | ```javascript
|
100 | // Create a driver instance, for the user neo4j with password neo4j.
|
101 | // It should be enough to have a single driver per database per application.
|
102 | var driver = neo4j.driver(
|
103 | 'neo4j://localhost',
|
104 | neo4j.auth.basic('neo4j', 'neo4j')
|
105 | )
|
106 |
|
107 | // Close the driver when application exits.
|
108 | // This closes all used network connections.
|
109 | await driver.close()
|
110 | ```
|
111 |
|
112 | ### Acquiring a Session
|
113 |
|
114 | #### Regular Session
|
115 |
|
116 | ```javascript
|
117 | // Create a session to run Cypher statements in.
|
118 | // Note: Always make sure to close sessions when you are done using them!
|
119 | var session = driver.session()
|
120 | ```
|
121 |
|
122 | ##### with a Default Access Mode of `READ`
|
123 |
|
124 | ```javascript
|
125 | var session = driver.session({ defaultAccessMode: neo4j.session.READ })
|
126 | ```
|
127 |
|
128 | ##### with Bookmarks
|
129 |
|
130 | ```javascript
|
131 | var session = driver.session({
|
132 | bookmarks: [bookmark1FromPreviousSession, bookmark2FromPreviousSession]
|
133 | })
|
134 | ```
|
135 |
|
136 | ##### against a Database
|
137 |
|
138 | ```javascript
|
139 | var session = driver.session({
|
140 | database: 'foo',
|
141 | defaultAccessMode: neo4j.session.WRITE
|
142 | })
|
143 | ```
|
144 |
|
145 | #### Reactive Session
|
146 |
|
147 | ```javascript
|
148 | // Create a reactive session to run Cypher statements in.
|
149 | // Note: Always make sure to close sessions when you are done using them!
|
150 | var rxSession = driver.rxSession()
|
151 | ```
|
152 |
|
153 | ##### with a Default Access Mode of `READ`
|
154 |
|
155 | ```javascript
|
156 | var rxSession = driver.rxSession({ defaultAccessMode: neo4j.session.READ })
|
157 | ```
|
158 |
|
159 | ##### with Bookmarks
|
160 |
|
161 | ```javascript
|
162 | var rxSession = driver.rxSession({
|
163 | bookmarks: [bookmark1FromPreviousSession, bookmark2FromPreviousSession]
|
164 | })
|
165 | ```
|
166 |
|
167 | ##### against a Database
|
168 |
|
169 | ```javascript
|
170 | var rxSession = driver.rxSession({
|
171 | database: 'foo',
|
172 | defaultAccessMode: neo4j.session.WRITE
|
173 | })
|
174 | ```
|
175 |
|
176 | ### Executing Queries
|
177 |
|
178 | #### Consuming Records with Streaming API
|
179 |
|
180 | ```javascript
|
181 | // Run a Cypher statement, reading the result in a streaming manner as records arrive:
|
182 | session
|
183 | .run('MERGE (alice:Person {name : $nameParam}) RETURN alice.name AS name', {
|
184 | nameParam: 'Alice'
|
185 | })
|
186 | .subscribe({
|
187 | onKeys: keys => {
|
188 | console.log(keys)
|
189 | },
|
190 | onNext: record => {
|
191 | console.log(record.get('name'))
|
192 | },
|
193 | onCompleted: () => {
|
194 | session.close() // returns a Promise
|
195 | },
|
196 | onError: error => {
|
197 | console.log(error)
|
198 | }
|
199 | })
|
200 | ```
|
201 |
|
202 | Subscriber API allows following combinations of `onKeys`, `onNext`, `onCompleted` and `onError` callback invocations:
|
203 |
|
204 | - zero or one `onKeys`,
|
205 | - zero or more `onNext` followed by `onCompleted` when operation was successful. `onError` will not be invoked in this case
|
206 | - zero or more `onNext` followed by `onError` when operation failed. Callback `onError` might be invoked after couple `onNext` invocations because records are streamed lazily by the database. `onCompleted` will not be invoked in this case.
|
207 |
|
208 | #### Consuming Records with Promise API
|
209 |
|
210 | ```javascript
|
211 | // the Promise way, where the complete result is collected before we act on it:
|
212 | session
|
213 | .run('MERGE (james:Person {name : $nameParam}) RETURN james.name AS name', {
|
214 | nameParam: 'James'
|
215 | })
|
216 | .then(result => {
|
217 | result.records.forEach(record => {
|
218 | console.log(record.get('name'))
|
219 | })
|
220 | })
|
221 | .catch(error => {
|
222 | console.log(error)
|
223 | })
|
224 | .then(() => session.close())
|
225 | ```
|
226 |
|
227 | #### Consuming Records with Reactive API
|
228 |
|
229 | ```javascript
|
230 | rxSession
|
231 | .run('MERGE (james:Person {name: $nameParam}) RETURN james.name AS name', {
|
232 | nameParam: 'Bob'
|
233 | })
|
234 | .records()
|
235 | .pipe(
|
236 | map(record => record.get('name')),
|
237 | concat(rxSession.close())
|
238 | )
|
239 | .subscribe({
|
240 | next: data => console.log(data),
|
241 | complete: () => console.log('completed'),
|
242 | error: err => console.log(err)
|
243 | })
|
244 | ```
|
245 |
|
246 | ### Transaction functions
|
247 |
|
248 | ```javascript
|
249 | // Transaction functions provide a convenient API with minimal boilerplate and
|
250 | // retries on network fluctuations and transient errors. Maximum retry time is
|
251 | // configured on the driver level and is 30 seconds by default:
|
252 | // Applies both to standard and reactive sessions.
|
253 | neo4j.driver('neo4j://localhost', neo4j.auth.basic('neo4j', 'neo4j'), {
|
254 | maxTransactionRetryTime: 30000
|
255 | })
|
256 | ```
|
257 |
|
258 | #### Reading with Async Session
|
259 |
|
260 | ```javascript
|
261 | // It is possible to execute read transactions that will benefit from automatic
|
262 | // retries on both single instance ('bolt' URI scheme) and Causal Cluster
|
263 | // ('neo4j' URI scheme) and will get automatic load balancing in cluster deployments
|
264 | var readTxResultPromise = session.readTransaction(txc => {
|
265 | // used transaction will be committed automatically, no need for explicit commit/rollback
|
266 |
|
267 | var result = txc.run('MATCH (person:Person) RETURN person.name AS name')
|
268 | // at this point it is possible to either return the result or process it and return the
|
269 | // result of processing it is also possible to run more statements in the same transaction
|
270 | return result
|
271 | })
|
272 |
|
273 | // returned Promise can be later consumed like this:
|
274 | readTxResultPromise
|
275 | .then(result => {
|
276 | console.log(result.records)
|
277 | })
|
278 | .catch(error => {
|
279 | console.log(error)
|
280 | })
|
281 | .then(() => session.close())
|
282 | ```
|
283 |
|
284 | #### Reading with Reactive Session
|
285 |
|
286 | ```javascript
|
287 | rxSession
|
288 | .readTransaction(txc =>
|
289 | txc
|
290 | .run('MATCH (person:Person) RETURN person.name AS name')
|
291 | .records()
|
292 | .pipe(map(record => record.get('name')))
|
293 | )
|
294 | .subscribe({
|
295 | next: data => console.log(data),
|
296 | complete: () => console.log('completed'),
|
297 | error: err => console.log(error)
|
298 | })
|
299 | ```
|
300 |
|
301 | #### Writing with Async Session
|
302 |
|
303 | ```javascript
|
304 | // It is possible to execute write transactions that will benefit from automatic retries
|
305 | // on both single instance ('bolt' URI scheme) and Causal Cluster ('neo4j' URI scheme)
|
306 | var writeTxResultPromise = session.writeTransaction(async txc => {
|
307 | // used transaction will be committed automatically, no need for explicit commit/rollback
|
308 |
|
309 | var result = await txc.run(
|
310 | "MERGE (alice:Person {name : 'Alice'}) RETURN alice.name AS name"
|
311 | )
|
312 | // at this point it is possible to either return the result or process it and return the
|
313 | // result of processing it is also possible to run more statements in the same transaction
|
314 | return result.records.map(record => record.get('name'))
|
315 | })
|
316 |
|
317 | // returned Promise can be later consumed like this:
|
318 | writeTxResultPromise
|
319 | .then(namesArray => {
|
320 | console.log(namesArray)
|
321 | })
|
322 | .catch(error => {
|
323 | console.log(error)
|
324 | })
|
325 | .then(() => session.close())
|
326 | ```
|
327 |
|
328 | #### Writing with Reactive Session
|
329 |
|
330 | ```javascript
|
331 | rxSession
|
332 | .writeTransaction(txc =>
|
333 | txc
|
334 | .run("MERGE (alice:Person {name: 'James'}) RETURN alice.name AS name")
|
335 | .records()
|
336 | .pipe(map(record => record.get('name')))
|
337 | )
|
338 | .subscribe({
|
339 | next: data => console.log(data),
|
340 | complete: () => console.log('completed'),
|
341 | error: error => console.log(error)
|
342 | })
|
343 | ```
|
344 |
|
345 | ### Explicit Transactions
|
346 |
|
347 | #### With Async Session
|
348 |
|
349 | ```javascript
|
350 | // run statement in a transaction
|
351 | const txc = session.beginTransaction()
|
352 | try {
|
353 | const result1 = await txc.run(
|
354 | 'MERGE (bob:Person {name: $nameParam}) RETURN bob.name AS name',
|
355 | {
|
356 | nameParam: 'Bob'
|
357 | }
|
358 | )
|
359 | result1.records.forEach(r => console.log(r.get('name')))
|
360 | console.log('First query completed')
|
361 |
|
362 | const result2 = await txc.run(
|
363 | 'MERGE (adam:Person {name: $nameParam}) RETURN adam.name AS name',
|
364 | {
|
365 | nameParam: 'Adam'
|
366 | }
|
367 | )
|
368 | result2.records.forEach(r => console.log(r.get('name')))
|
369 | console.log('Second query completed')
|
370 |
|
371 | await txc.commit()
|
372 | console.log('committed')
|
373 | } catch (error) {
|
374 | console.log(error)
|
375 | await txc.rollback()
|
376 | console.log('rolled back')
|
377 | } finally {
|
378 | await session.close()
|
379 | }
|
380 | ```
|
381 |
|
382 | #### With Reactive Session
|
383 |
|
384 | ```javascript
|
385 | rxSession
|
386 | .beginTransaction()
|
387 | .pipe(
|
388 | flatMap(txc =>
|
389 | concat(
|
390 | txc
|
391 | .run(
|
392 | 'MERGE (bob:Person {name: $nameParam}) RETURN bob.name AS name',
|
393 | {
|
394 | nameParam: 'Bob'
|
395 | }
|
396 | )
|
397 | .records()
|
398 | .pipe(map(r => r.get('name'))),
|
399 | of('First query completed'),
|
400 | txc
|
401 | .run(
|
402 | 'MERGE (adam:Person {name: $nameParam}) RETURN adam.name AS name',
|
403 | {
|
404 | nameParam: 'Adam'
|
405 | }
|
406 | )
|
407 | .records()
|
408 | .pipe(map(r => r.get('name'))),
|
409 | of('Second query completed'),
|
410 | txc.commit(),
|
411 | of('committed')
|
412 | ).pipe(catchError(err => txc.rollback().pipe(throwError(err))))
|
413 | )
|
414 | )
|
415 | .subscribe({
|
416 | next: data => console.log(data),
|
417 | complete: () => console.log('completed'),
|
418 | error: error => console.log(error)
|
419 | })
|
420 | ```
|
421 |
|
422 | ### Numbers and the Integer type
|
423 |
|
424 | The Neo4j type system includes 64-bit integer values.
|
425 | However, JavaScript can only safely represent integers between `-(2`<sup>`53`</sup>`- 1)` and `(2`<sup>`53`</sup>`- 1)`.
|
426 | In order to support the full Neo4j type system, the driver will not automatically convert to javascript integers.
|
427 | Any time the driver receives an integer value from Neo4j, it will be represented with an internal integer type by the driver.
|
428 |
|
429 | _**Any javascript number value passed as a parameter will be recognized as `Float` type.**_
|
430 |
|
431 | #### Writing integers
|
432 |
|
433 | Numbers written directly e.g. `session.run("CREATE (n:Node {age: $age})", {age: 22})` will be of type `Float` in Neo4j.
|
434 | To write the `age` as an integer the `neo4j.int` method should be used:
|
435 |
|
436 | ```javascript
|
437 | var neo4j = require('neo4j-driver')
|
438 |
|
439 | session.run('CREATE (n {age: $myIntParam})', { myIntParam: neo4j.int(22) })
|
440 | ```
|
441 |
|
442 | To write integers larger than can be represented as JavaScript numbers, use a string argument to `neo4j.int`:
|
443 |
|
444 | ```javascript
|
445 | session.run('CREATE (n {age: $myIntParam})', {
|
446 | myIntParam: neo4j.int('9223372036854775807')
|
447 | })
|
448 | ```
|
449 |
|
450 | #### Reading integers
|
451 |
|
452 | Since Integers can be larger than can be represented as JavaScript numbers, it is only safe to convert to JavaScript numbers if you know that they will not exceed `(2`<sup>`53`</sup>`- 1)` in size.
|
453 | In order to facilitate working with integers the driver include `neo4j.isInt`, `neo4j.integer.inSafeRange`, `neo4j.integer.toNumber`, and `neo4j.integer.toString`.
|
454 |
|
455 | ```javascript
|
456 | var aSmallInteger = neo4j.int(123)
|
457 | if (neo4j.integer.inSafeRange(aSmallInteger)) {
|
458 | var aNumber = aSmallInteger.toNumber()
|
459 | }
|
460 | ```
|
461 |
|
462 | If you will be handling integers larger than that, you should convert them to strings:
|
463 |
|
464 | ```javascript
|
465 | var aLargerInteger = neo4j.int('9223372036854775807')
|
466 | if (!neo4j.integer.inSafeRange(aLargerInteger)) {
|
467 | var integerAsString = aLargerInteger.toString()
|
468 | }
|
469 | ```
|
470 |
|
471 | #### Enabling native numbers
|
472 |
|
473 | Starting from 1.6 version of the driver it is possible to configure it to only return native numbers instead of custom `Integer` objects.
|
474 | The configuration option affects all integers returned by the driver. **Enabling this option can result in a loss of precision and incorrect numeric
|
475 | values being returned if the database contains integer numbers outside of the range** `[Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER]`.
|
476 | To enable potentially lossy integer values use the driver's configuration object:
|
477 |
|
478 | ```javascript
|
479 | var driver = neo4j.driver(
|
480 | 'neo4j://localhost',
|
481 | neo4j.auth.basic('neo4j', 'neo4j'),
|
482 | { disableLosslessIntegers: true }
|
483 | )
|
484 | ```
|
485 |
|
486 | ## Building
|
487 |
|
488 | npm install
|
489 | npm run build
|
490 |
|
491 | This produces browser-compatible standalone files under `lib/browser` and a Node.js module version under `lib/`.
|
492 | See files under `examples/` on how to use.
|
493 |
|
494 | ## Testing
|
495 |
|
496 | Tests **require** latest [Boltkit](https://github.com/neo4j-contrib/boltkit) and [Firefox](https://www.mozilla.org/firefox/) to be installed in the system.
|
497 |
|
498 | Boltkit is needed to start, stop and configure local test database. Boltkit can be installed with the following command:
|
499 |
|
500 | pip3 install --upgrade boltkit
|
501 |
|
502 | To run tests against "default" Neo4j version:
|
503 |
|
504 | ./runTests.sh
|
505 |
|
506 | To run tests against specified Neo4j version:
|
507 |
|
508 | ./runTests.sh '-e 4.0.0'
|
509 |
|
510 | Simple `npm test` can also be used if you already have a running version of a compatible Neo4j server.
|
511 |
|
512 | For development, you can have the build tool rerun the tests each time you change
|
513 | the source code:
|
514 |
|
515 | gulp watch-n-test
|
516 |
|
517 | ### Testing on windows
|
518 |
|
519 | To run the same test suite, run `.\runTest.ps1` instead in powershell with admin right.
|
520 | The admin right is required to start/stop Neo4j properly as a system service.
|
521 | While there is no need to grab admin right if you are running tests against an existing Neo4j server using `npm test`.
|