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.
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.
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.
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;
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;
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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) );
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) );
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.
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.
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) );
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) );
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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:
- Dive into AQL to learn more about the query language
- Dig into the driver API if you want to use ArangoDB from Node (or even from the browser!)
- Learn about the ArangoDB Shell to explore ArangoDB interactively
- Find out how to extend ArangoDB using JavaScript with ArangoDB Foxx
- Explore the ArangoDB Cookbook for practical solutions to common problems
- Confused by the jargon? There’s a glossary that can help you
- Did you know ArangoDB 2.8 brings graph queries to AQL?
- Have questions? Ask on StackOverflow and find answers
- Made a cool thing? Tell us on the mailing list
- Want to get involved? ArangoDB is Open Source with many ways to contribute
- Still confused? Get in touch on Slack Community channel or on Twitter @arangodb
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:- Dive into AQL to learn more about the query language
- Dig into the driver API if you want to use ArangoDB from Node (or even from the browser!)
- Learn about the ArangoDB Shell to explore ArangoDB interactively
- Find out how to extend ArangoDB using JavaScript with ArangoDB Foxx
- Explore the ArangoDB Cookbook for practical solutions to common problems
- Confused by the jargon? There’s a glossary that can help you
- Did you know ArangoDB 2.8 brings graph queries to AQL?
- Have questions? Ask on StackOverflow and find answers
- Made a cool thing? Tell us on the mailing list
- Want to get involved? ArangoDB is Open Source with many ways to contribute
- Still confused? Get in touch on Slack Community channel or on Twitter @arangodb