home shape

ArangoDB in 10 Minutes: Node.js

This is a short tutorial to get started with ArangoDB using Node.js. In less than 10 minutes you can learn how to use ArangoDB from Node on Linux or OSX.

Note: This tutorial was written for ArangoDB 3 series with Node.js 6.2 and may not work with older versions.

right blob img min

ArangoDB in 10 Minutes: Node.js

This is a short tutorial to get started with ArangoDB using Node.js. In less than 10 minutes you can learn how to use ArangoDB from Node on Linux or OSX.

Note: This tutorial was written for ArangoDB 3 series with Node.js 6.2 and may not work with older versions.

Install the JavaScript driver for Node.js

First let’s install the ArangoDB JavaScript driver from NPM:

npm install arangojs@5

If you want to use the driver outside of the current directory, you can also install it globally using the --global  flag:

npm install --global arangojs@5

Note: Before we start: Install ArangoDB.

step0 npm image 1
background img

Install the JavaScript driver for Node.js

First let’s install the ArangoDB JavaScript driver from NPM:

npm install arangojs@5

If you want to use the driver outside of the current directory, you can also install it globally using the --global  flag:

npm install --global arangojs@5

Note: Before we start: Install ArangoDB.

step0 npm image 1

Quick start

You can follow this tutorial by using the Node shell from your command line:

node

To use the client you can import it using--require  flag:

Database = require('arangojs').Database;
step1 node import image 2
right blob img min

Quick start

You can follow this tutorial by using the Node shell from your command line:

node

To use the client you can import it using--require  flag:

Database = require('arangojs').Database;
step1 node import image 2
background img

Getting a handle

In order to do anything useful we need a handle to an existing ArangoDB database.
Let’s do this by creating a new instance of Database using a connection string:

db = new Database('http://127.0.0.1:8529');

This connection string actually represents the default values, so you can just omit it:

new Database();

If that’s still too verbose for you, you can invoke the driver directly:

db = require('arangojs')();

The outcome of any of the three calls should be identical.

step2 instantiate image 3

Getting a handle

In order to do anything useful we need a handle to an existing ArangoDB database.
Let’s do this by creating a new instance of Database using a connection string:

db = new Database('http://127.0.0.1:8529');

This connection string actually represents the default values, so you can just omit it:

new Database();

If that’s still too verbose for you, you can invoke the driver directly:

db = require('arangojs')();

The outcome of any of the three calls should be identical.

step2 instantiate image 3
right blob img min

Creating a database

We don’t want to mess with any existing data, so let’s start by creating a new database called “mydb”:

db.createDatabase('mydb').then(
  () => console.log('Database created'),
  err => console.error('Failed to create database:', err)
);

Because we’re trying to actually do something on the server, this action is asynchronous.
All asynchronous methods in the ArangoDB driver return promises but you can also pass a node-style callback instead:

db.createDatabase('mydb', function (err) {
  if (!err) console.log('Database created');
  else console.error('Failed to create database:', err);
});
 

Keep in mind that the new database you’ve created is only available once the callback is called or the promise is resolved.
Throughout this tutorial we’ll use the promise API because they’re available in recent versions of Node.js as well as most modern browsers.

step3 create image 4
background img

Creating a database

We don’t want to mess with any existing data, so let’s start by creating a new database called “mydb”:

db.createDatabase('mydb').then(
  () => console.log('Database created'),
  err => console.error('Failed to create database:', err)
);

Because we’re trying to actually do something on the server, this action is asynchronous.
All asynchronous methods in the ArangoDB driver return promises but you can also pass a node-style callback instead:

db.createDatabase('mydb', function (err) {
  if (!err) console.log('Database created');
  else console.error('Failed to create database:', err);
});
 

Keep in mind that the new database you’ve created is only available once the callback is called or the promise is resolved.
Throughout this tutorial we’ll use the promise API because they’re available in recent versions of Node.js as well as most modern browsers.

step3 create image 4
right blob img min

Switching to the new database

We’ve created a new database, but we haven’t yet told the driver it should start using it. Let’s change that:

db.useDatabase('mydb');

You’ll notice this method is executed immediately.
The handle “db” now references the “mydb” database instead of the (default) “_system” database it referenced before.

step4 switch db image 44

Switching to the new database

We’ve created a new database, but we haven’t yet told the driver it should start using it. Let’s change that:

db.useDatabase('mydb');

You’ll notice this method is executed immediately.
The handle “db” now references the “mydb” database instead of the (default) “_system” database it referenced before.

step4 switch db image 44
background img

Another handle

Collections are where you keep your actual data.
There are actually two types of collections but for now we only need one.

Like databases, you need a handle before you can do anything to it:

collection = db.collection('firstCollection');

Again notice that it executes immediately.
Unlike databases, the collection doesn’t have to already exist before we can create the handle.

step5 collection image 5

Another handle

Collections are where you keep your actual data.
There are actually two types of collections but for now we only need one.

Like databases, you need a handle before you can do anything to it:

collection = db.collection('firstCollection');

Again notice that it executes immediately.
Unlike databases, the collection doesn’t have to already exist before we can create the handle.

step5 collection image 5
right blob img min

Creating a collection

We have a handle but in order to put actual data in the collection we need to create it:

collection.create().then(
  () => console.log('Collection created'),
  err => console.error('Failed to create collection:', err)
);

Once the promise resolves the collection handle will point to an actual collection we can store data in.

step6 create collection 1 image 6
background img

Creating a collection

We have a handle but in order to put actual data in the collection we need to create it:

collection.create().then(
  () => console.log('Collection created'),
  err => console.error('Failed to create collection:', err)
);

Once the promise resolves the collection handle will point to an actual collection we can store data in.

step6 create collection 1 image 6

Creating a document

What good is a collection without any collectibles? Let’s start out by defining a piece of data we want to store:

doc = {
  _key: 'firstDocument',
  a: 'foo',
  b: 'bar',
  c: Date()
};
 

Collection entries (called documents in ArangoDB) are plain JavaScript objects and can contain anything you could store in a JSON string.
You may be wondering about the _key property: some property names that start with underscores are special in ArangoDB and the key is used to identify the document later.
If you don’t specify a key yourself, ArangoDB will generate one for you.

step7 document image 7
right blob img min

Creating a document

What good is a collection without any collectibles? Let’s start out by defining a piece of data we want to store:

doc = {
  _key: 'firstDocument',
  a: 'foo',
  b: 'bar',
  c: Date()
};
 

Collection entries (called documents in ArangoDB) are plain JavaScript objects and can contain anything you could store in a JSON string.
You may be wondering about the _key property: some property names that start with underscores are special in ArangoDB and the key is used to identify the document later.
If you don’t specify a key yourself, ArangoDB will generate one for you.

step7 document image 7
background img

Saving and updating the document

ArangoDB also adds a _rev property which changes every time the document is written to, and an _id which consists of the collection name and document key.
These “meta” properties are returned every time you create, update, replace or fetch a document directly.

Let’s see this in action by fist saving the document:

collection.save(doc).then(
  meta => console.log('Document saved:', meta._rev),
  err => console.error('Failed to save document:', err)
);

… and then updating it in place:

collection.update('firstDocument', {d: 'qux'}).then(
  meta => console.log('Document updated:', meta._rev),
  err => console.error('Failed to update document:', err)
);

Notice that the two _rev values are different.
You can see the full document (with the key, id and the updated _rev) by fetching it:

collection.document('firstDocument').then(
  doc => console.log('Document:', JSON.stringify(doc, null, 2)),
  err => console.error('Failed to fetch document:', err)
);
 
step8 save document image 8

Saving and updating the document

ArangoDB also adds a _rev property which changes every time the document is written to, and an _id which consists of the collection name and document key.
These “meta” properties are returned every time you create, update, replace or fetch a document directly.

Let’s see this in action by fist saving the document:

collection.save(doc).then(
  meta => console.log('Document saved:', meta._rev),
  err => console.error('Failed to save document:', err)
);

… and then updating it in place:

collection.update('firstDocument', {d: 'qux'}).then(
  meta => console.log('Document updated:', meta._rev),
  err => console.error('Failed to update document:', err)
);

Notice that the two _rev values are different.
You can see the full document (with the key, id and the updated _rev) by fetching it:

collection.document('firstDocument').then(
  doc => console.log('Document:', JSON.stringify(doc, null, 2)),
  err => console.error('Failed to fetch document:', err)
);
 
step8 save document image 8
right blob img min

Removing the document

We’ve played around enough with this document, so let’s get rid of it:

collection.remove('firstDocument').then(
  () => console.log('Document removed'),
  err => console.error('Failed to remove document', err)
);

Once the promise has resolved, the document has ceased to exist.
We can verify this by trying to fetch it again (which should result in an error):

collection.document('firstDocument').then(
  doc => console.log('Document:', JSON.stringify(doc, null, 2)),
  err => console.error('Failed to fetch document:', err.message));
 

If you see the error message "document not found", we were successful.

step9 remove document image 9
background img

Removing the document

We’ve played around enough with this document, so let’s get rid of it:

collection.remove('firstDocument').then(
  () => console.log('Document removed'),
  err => console.error('Failed to remove document', err)
);

Once the promise has resolved, the document has ceased to exist.
We can verify this by trying to fetch it again (which should result in an error):

collection.document('firstDocument').then(
  doc => console.log('Document:', JSON.stringify(doc, null, 2)),
  err => console.error('Failed to fetch document:', err.message));
 

If you see the error message "document not found", we were successful.

step9 remove document image 9

Bulk importing

Before we can move on, we need some more interesting example data:

docs = [];
for (i = 0; i < 100; i++) {
  docs.push({_key: `doc${i + 1}`, value: i});
}

Importing these one-by-one would get fairly tedious, so let’s use the import method instead:

collection.import(docs).then(
  result => console.log('Import complete:', result),
  err => console.error('Import failed:', err)
);
step10 import documents image 10
right blob img min
right blob img min
background img

Bulk importing

Before we can move on, we need some more interesting example data:

docs = [];
for (i = 0; i < 100; i++) {
  docs.push({_key: `doc${i + 1}`, value: i});
}

Importing these one-by-one would get fairly tedious, so let’s use the import method instead:

collection.import(docs).then(
  result => console.log('Import complete:', result),
  err => console.error('Import failed:', err)
);
step10 import documents image 10

Simple queries

The easiest way to see what’s currently inside a small collection is using the “all” query:

collection.all().then(
  cursor => cursor.map(doc => doc._key)
).then(
  keys => console.log('All keys:', keys.join(', ')),
  err => console.error('Failed to fetch all documents:', err)
);

Note: This tutorial was written for ArangoDB 3 series with Node.js 6.2 and may not work with older versions.

This helps avoiding unnecessarily cluttering up memory when working with very large result sets.

All interactions with the cursor object are asynchronous as the driver will automatically fetch additional data from the server as necessary.
Keep in mind that unlike arrays, cursors are depleted when you use them.
They’re single-use items, not permanent data structures.

step11 simple queries image 11

Simple queries

The easiest way to see what’s currently inside a small collection is using the “all” query:

collection.all().then(
  cursor => cursor.map(doc => doc._key)
).then(
  keys => console.log('All keys:', keys.join(', ')),
  err => console.error('Failed to fetch all documents:', err)
);

Note: This tutorial was written for ArangoDB 3 series with Node.js 6.2 and may not work with older versions.

This helps avoiding unnecessarily cluttering up memory when working with very large result sets.

All interactions with the cursor object are asynchronous as the driver will automatically fetch additional data from the server as necessary.
Keep in mind that unlike arrays, cursors are depleted when you use them.
They’re single-use items, not permanent data structures.

step11 simple queries image 11
right blob img min

AQL queries

Simple queries are useful but limited.
Instead of returning each entire document and fetching its key locally, let’s write a query to do it inside ArangoDB.
Additionally let’s sort them by value so we don’t have to deal with an unordered mess:

db.query('FOR d IN firstCollection SORT d.value ASC RETURN d._key').then(
  cursor => cursor.all()
).then(
  keys => console.log('All keys:', keys.join(', ')),
  err => console.error('Failed to execute query:', err)
);

Note that we still receive a cursor but no longer need to process the result itself.
We can simply convert the cursor to an array using its “all” method to fetch all results.

step12 aql image 12
background img

AQL queries

Simple queries are useful but limited.
Instead of returning each entire document and fetching its key locally, let’s write a query to do it inside ArangoDB.
Additionally let’s sort them by value so we don’t have to deal with an unordered mess:

db.query('FOR d IN firstCollection SORT d.value ASC RETURN d._key').then(
  cursor => cursor.all()
).then(
  keys => console.log('All keys:', keys.join(', ')),
  err => console.error('Failed to execute query:', err)
);

Note that we still receive a cursor but no longer need to process the result itself.
We can simply convert the cursor to an array using its “all” method to fetch all results.

step12 aql image 12

Template strings

When writing complex queries you don’t want to have to hardcode everything in a giant string.
The driver provides the same aqlQuery template handler you can also use within ArangoDB itself:

aqlQuery = require('arangojs').aqlQuery;

You can use it to write AQL templates.
Any variables referenced in AQL templates will be added to the query’s bind values automatically.
It even knows how to treat collection handles.

Let’s demonstrate this by writing an INSERT query that creates new documents using AQL:

db.query(aqlQuery`
  FOR doc IN ${collection}
  LET value = 100 + doc.value
  INSERT {
    _key: CONCAT("new", doc.value),
    value
  } INTO ${collection}
  RETURN NEW
`).then(
  cursor => cursor.map(doc => doc._key)
).then(
  keys => console.log('Inserted documents:', keys.join(', ')),
  err => console.error('Failed to insert:', err)
);

Notice that in AQL the special NEW variable refers to the newly created document.

step13 insert query image 13
right blob img min

Template strings

When writing complex queries you don’t want to have to hardcode everything in a giant string.
The driver provides the same aqlQuery template handler you can also use within ArangoDB itself:

aqlQuery = require('arangojs').aqlQuery;

You can use it to write AQL templates.
Any variables referenced in AQL templates will be added to the query’s bind values automatically.
It even knows how to treat collection handles.

Let’s demonstrate this by writing an INSERT query that creates new documents using AQL:

db.query(aqlQuery`
  FOR doc IN ${collection}
  LET value = 100 + doc.value
  INSERT {
    _key: CONCAT("new", doc.value),
    value
  } INTO ${collection}
  RETURN NEW
`).then(
  cursor => cursor.map(doc => doc._key)
).then(
  keys => console.log('Inserted documents:', keys.join(', ')),
  err => console.error('Failed to insert:', err)
);

Notice that in AQL the special NEW variable refers to the newly created document.

step13 insert query image 13
background img

Updating documents in AQL

We’ve seen how to create new documents using AQL, so for sake of completion let’s also look at how to update them:

db.query(aqlQuery`
  FOR doc IN ${collection}
  UPDATE doc WITH {
    value: doc.value * 2
  } IN ${collection}
  RETURN {old: OLD.value, new: NEW.value}
`).then(
  cursor => cursor.map(doc => `${doc.old} => ${doc.new}`)
).then(
  results => console.log('Update complete:', results.join(', ')),
  err => console.error('Update failed:', err)
);

Because UPDATE (and also REPLACE) modifies an existing document,
AQL additionally provides the special OLD variable to refer to the previous state of the document.

While REPLACE replaces an existing document with a new document entirely (only preserving its key)
the UPDATE operation merges a new object into the existing document.
Any existing properties that aren’t explicitly overwritten remain intact.

step14 update query image 14

Updating documents in AQL

We’ve seen how to create new documents using AQL, so for sake of completion let’s also look at how to update them:

db.query(aqlQuery`
  FOR doc IN ${collection}
  UPDATE doc WITH {
    value: doc.value * 2
  } IN ${collection}
  RETURN {old: OLD.value, new: NEW.value}
`).then(
  cursor => cursor.map(doc => `${doc.old} => ${doc.new}`)
).then(
  results => console.log('Update complete:', results.join(', ')),
  err => console.error('Update failed:', err)
);

Because UPDATE (and also REPLACE) modifies an existing document,
AQL additionally provides the special OLD variable to refer to the previous state of the document.

While REPLACE replaces an existing document with a new document entirely (only preserving its key)
the UPDATE operation merges a new object into the existing document.
Any existing properties that aren’t explicitly overwritten remain intact.

step14 update query image 14
right blob img min

Removing documents in AQL

Sometimes less data is better than more.
Let’s trim our collection down by deleting a subset of our data:

db.query(aqlQuery`
  FOR doc IN ${collection}
  FILTER doc.value % 10 != 0
  REMOVE doc IN ${collection}
  RETURN OLD._key
`).then(
  cursor => cursor.all()
).then(
  keys => console.log('Removed:', keys.join(', ')),
  err => console.error('Failed to remove:', err)
);

This time AQL doesn’t give you a NEW because the document will be gone.
But you still get to have a last look via OLD to let the document say its goodbyes.

step15 remove query image 15
background img

Removing documents in AQL

Sometimes less data is better than more.
Let’s trim our collection down by deleting a subset of our data:

db.query(aqlQuery`
  FOR doc IN ${collection}
  FILTER doc.value % 10 != 0
  REMOVE doc IN ${collection}
  RETURN OLD._key
`).then(
  cursor => cursor.all()
).then(
  keys => console.log('Removed:', keys.join(', ')),
  err => console.error('Failed to remove:', err)
);

This time AQL doesn’t give you a NEW because the document will be gone.
But you still get to have a last look via OLD to let the document say its goodbyes.

step15 remove query image 15

Removing all the documents

Enough fooling around. Let’s end with a clean slate.
The method for completely emptying a collection is called “truncate”:

collection.truncate().then(
  () => console.log('Truncated collection'),
  err => console.error('Failed to truncate:', err)
);

Note that it doesn’t give us anything useful back.
When you truncate a collection, you discard all of its contents.
There’s no way back:

collection.all().then(
  cursor => cursor.all()
).then(
  results => console.log('Collection contains', results.length, 'documents'),
  err => console.error('Failed to fetch:', err)
);

Keep in mind that you can also truncate databases.
Don’t worry about your collections though, truncating only deletes the documents.
Although it’s still probably not something you want to take lightly.

step16 truncate image 17
right blob img min

Removing all the documents

Enough fooling around. Let’s end with a clean slate.
The method for completely emptying a collection is called “truncate”:

collection.truncate().then(
  () => console.log('Truncated collection'),
  err => console.error('Failed to truncate:', err)
);

Note that it doesn’t give us anything useful back.
When you truncate a collection, you discard all of its contents.
There’s no way back:

collection.all().then(
  cursor => cursor.all()
).then(
  results => console.log('Collection contains', results.length, 'documents'),
  err => console.error('Failed to fetch:', err)
);

Keep in mind that you can also truncate databases.
Don’t worry about your collections though, truncating only deletes the documents.
Although it’s still probably not something you want to take lightly.

step16 truncate image 17

Learn more

By now you should have an idea of how you can interact with ArangoDB in Node.
But there is much more to know and so much more you can do:

Learn more