A quick tour of Arangojs

I’ve been using ArangoDB for a while now, but for most of that time I’ve been using it from Ruby. I’ve dabbled with the Guacamole library and even took a crack at writing my own, but switching to Javascript has led me to get to know Arangojs.

Given that Arangojs is talking to ArangoDB via its HTTP API, basically everything you do is asynchronous. There are a few ways of dealing with async code in Javascript, and Arangojs has been written to support basically all of them.

Arangojs’s flexibility and my inexperience with the new Javascript syntax combined to give me bit of an awkward start, so with a little learning under my belt I thought I would write up some examples that would have saved me some time.

My most common use case is running an AQL query, so lets use that as an example. First up, I’ve been saving my config in a separate file:

// arango_config.js
//Using auth your url would look like:
// "http://uname:passwd@127.0.0.1:8529"
module.exports = {
  "production" : {
    "databaseName": process.env.PROD_DB_NAME,
    "url": process.env.PROD_DB_HOST,
  },
  "development" : {
    "databaseName": process.env.DEVELOPMENT_DB_NAME,
    "url": process.env.DEVELOPMENT_URL
  },
  "test" : {
    "databaseName": "test",
    "url": "http://127.0.0.1:8529",
  },
}

With that I can connect to one of my existing databases like so:

var config = require('../arangodb_config')[process.env.NODE_ENV]
var db = require('arangojs')(config)

This keeps my test database nicely separated from everything else and all my db credentials in the environment and out of my project code.

Assuming that our test db has a collection called “documents” containing a single document, we can use Arangojs to go get it:

db.query('FOR doc IN documents RETURN doc', function(err, cursor) {
  cursor.all(function(err, result) {
    console.log(result)
  })
})

Which returns:

[ { foo: 'bar',
    _id: 'documents/206191358605',
    _rev: '206192931469',
    _key: '206191358605' } ]

While this is perfectly valid Javascript, its pretty old-school at this point since ECMAScript 2015 is now standard in both Node.js and any browser worth having. This means we can get rid of the “function” keyword and replace it with the “fat arrow” syntax and get the same result:

db.query('FOR doc IN documents RETURN doc', (err, cursor) => {
  cursor.all((err, result) => {
    console.log(result)
  })
})

So far so good but the callback style (and the callback-hell it brings) is definitely an anti-pattern. The widely cited antidote to this is promises:

db.query('FOR doc IN documents RETURN doc')
  .then((cursor) => { return cursor.all() })
  .then((doc) => { console.log(doc) });

While this code is functionally equivalent, it operates by chaining promises together. While it’s an improvement over callback-hell, after writing a bunch of this type of code, I ended up feeling like I had replaced callback hell with promise hell.

what-fresh-hell-is-this

The path back to sanity lies in ECMAScript 2016 aka ES7 and the new async/await keywords. Inside a function marked as async, you have access to an await keyword which allows you to write code that looks synchronous but does not block the event loop.

Using the babel transpiler lets us use the new ES7 syntax right now by compiling it all down to ES5/6 equivalents. Installing with npm install -g babel and running your project with babel-node is all that you need to be able to write this:

async () => {
    let cursor = await db.query('FOR doc IN documents RETURN doc')
    let result = await cursor.all()
    console.log(result)
}()

Once again we get the same result but without all the extra cruft that we would normally have to write.

One thing that is notably absent in these examples is the use of bound variables in our queries to avoid SQL injection (technically parameter injection since this is NoSQL).

So what does that look like?

async () => {
    let bindvars = {foo: "bar"}
    let cursor = await db.query('FOR doc IN documents FILTER doc.foo == @foo RETURN doc', bindvars)
    let result = await cursor.all()
    console.log(result)
}()

But Arangojs lets you go further, giving you a nice aqlQuery function based on ES6 template strings:

async () => {
    let foo = "bar"
    let aql = aqlQuery`
      FOR doc IN documents
        FILTER doc.foo == ${foo}
          RETURN doc
    `
    let cursor = await db.query(aql)
    let result = await cursor.all()
    console.log(result)
}()

Its pretty astounding how far that simple example has come. It’s hard to believe that it’s even the same language.
With Javascript (the language and the community) clearly in transition, Arangojs (and likely every other JS library) is compelled to support both the callback style and promises. It’s a bit surprising to see how much leeway that gives me to write some pretty sub-optimal code.

With all the above in mind, suddenly Arangojs’s async heavy API no longer feels intimidating.

The documentation for Arangojs is simple (just a long readme file) but comprehensive and there is lots more it can do. Hopefully this little exploration will help people get started with Arangojs a little more smoothly than I did.

5 thoughts on “A quick tour of Arangojs”

  1. Really appreciated this Mike. Getting to know js and arangodb and found very few detailed examples similar to yours using arangojs. Been having problems with a tutorial by Ashish with arangojs and CRUD which I think was pre 3.0 and not the latest arangojs – (http://www.ashishblog.com/tag/arangodb/ ). Wonder if you ever played with it in your earlier aranagodb experimenting days? I’ve been trying to get it to run successfully for some weeks now and trying every different sort of variation I can think of (using Windows 10 and VSCode)!
    The query works OK in arangosh so I know the database is OK with some documents pre-loaded.
    My basic problem is with the first db query which I need to solve before I attempt the subsequent CRUD steps. For example, with plenty of console.log(ging), for debug:
    In DataServices.js, one version is:

    console.log(“~~~ DataSvcs Entered ~~~”);
    const Database = require(‘arangojs’);

    var db = new Database({url:’http://root:bonar@${host}:${port}’,
    databaseName: ‘nodeArangoWebAppDB’
    });
    console.log(‘DataSvcs – created db instance: ‘);

    var aqlQuery = require(‘arangojs’).aql

    getAllUsers : function() {
    db.query(aqlQuery`FOR doc IN User RETURN doc`)
    .then(console.log(“debug – 1st promise then – returning cursor all from aqlQuery”),
    (cursor) => { return cursor.all() })
    .catch(
    function (error) {
    console.log(“Error raised in getAllUsers”);
    console.log(error); // error object with message
    }
    )
    .then( console.log(“debug Final then in db.query – logging doc”),
    (doc) => { console.log(doc) });
    }
    }
    all versions always result in a Not Found error – presumably the database – or perhaps the collection User. some of VS Code Terminal console output:

    > nodeArangoWebApp@0.0.0 start D:\Projects\NodeExpressApps\nodeArangoWebApp
    > node ./bin/www

    index.js – get home page
    ~~~ DataSvcs Entered ~~~
    DataSvcs – created db instance:
    GET / 304 27.145 ms – –
    ~~~ users.js – call service.getAllUsers ~~~
    DataSvcs – entered getAllUsers Function
    Final then in db.query – logging doc
    GET /users 500 22.789 ms – 3260
    Error raised in getAllUsers
    { [NotFoundError: Not Found]
    response:
    IncomingMessage {
    _readableState:
    ReadableState { …….

    Wonder if you can throw any light on what is going on and what I need to change?
    John, Christchurch UK.

    1. Hi John,
      I took a peek at his tutorial. His DataServices.js is basically calling new Database.database. If that worked before, it certainly doesn’t with the version I’m using. I got it working like this:

      var {Database, aql} = require('arangojs')
      var db = new Database({url:'http://127.0.0.1:8529', databaseName: 'nodeArangoWebAppDB'})
      module.exports = {
        getAllUsers : function()
        {
            return db.query(aql`FOR x IN User RETURN x`)
            .then(function (cursor) { return  cursor.all()})
            .then(console.log)
        }
      }
      
      > ds = require('./DataServices')
      { getAllUsers: [Function: getAllUsers] }
      > ds.getAllUsers()
      Promise { <pending> }
      > []
      

      Keep in mind that ArangoDB 3 ships with authentication turned on by default (It’s turned off above). If you look in the config files ( arangosh.conf and arangod.conf ) you can toggle the authentication to see if the problem is there.

      When I run into stuff like that I use `tcpdump` to watch the network traffic: ‘sudo tcpdump -A -i lo src or dst port 8529’. I have no idea what a Windows equivalent would be (I think you could use Wireshark…) but being able to see the traffic is sometimes the only way to know what’s actually happening.

      I hope that helps!

      1. Many thanks for your time and effort in trying out that tutorial Mike. Am afraid that I have still been unable to get the getAllUsers to work! It’s coming back in the .catch stage of handling the promises with a Not found error. Have used Fiddler to look at the traffic but couldn’t see anything to throw any light on it.
        Understand that there is a 3.1 version of AranagoDB due out so if there is another version of arangojs to go with it, I guess I will just have to wait. Shame, because I like what I see might be possible per Ashish’s example; perhaps I should look into the Foxx approach but I don’t like to be beaten! Tried slack but couldn’t get much help there. Thanks again …

  2. Hi Mike, is there an advantage (other than concise syntax) in using template strings instead of bound variables? Such as better injection resistance or similar?

    1. The advantage of the template strings is this:

      aql`FOR thing IN collection FILTER thing.foo == ${foo} RETURN thing`
      { query: 'FOR thing IN collection FILTER thing.foo == @value0 RETURN thing',
        bindVars: { value0: 'bar' } }
      

      What you can see above is the return value that comes out of the aql tagged template literal.
      Before tagged template literals that syntax would be a SQL/command injection. ES6 turns that into a function call where aql is called with an array of strings and values which is uses to produce a query and bind variables. The difference is about readability/ergonomics, but I think that thought leads somewhere deeper. It’s clear that developers find it natural to write stuff this way which is why SQL injection won’t go away. IMO, SQL injection is a symptom of a failure of the language to provide a construct that captures how developers think. Tagged template literals address this.
      I suspect we may eventually look back on tagged template literals as the thing that finally stopped (or more probably reduced) SQL injections (at least in JS).

Leave a comment