Using ArangoDB within a NodeJS App

By Dassi Orleando, Alibaba Cloud Tech Share Author. Tech Share is Alibaba Cloud’s incentive program to encourage the sharing of technical knowledge and best practices within the cloud community.

Overview

ArangoDB is a multi-model NoSQL database management system allowing to build high performance applications using a convenient SQL-like query language or JavaScript extensions.

Previously, we’ve seen how to install ArangoDB into an Alibaba Cloud Elastic Compute Service (ECS) Ubuntu server. We’ve also experimented the command-line and graphical tools to perform some queries on databases, collections and users. Let’s consider we’ve that base to better follow the current write-up which will show the use of ArangoDB as database management system into a NodeJS project.

Prerequisites

  1. Basic understanding of NodeJS understanding
  2. Have installed ArangoDB on an Alibaba Cloud ECS server

Setup a NodeJS Project

Let’s assume we’ve done the required study or research and felt down to the fact we need to use ArangoDB to build our NodeJS App. The communication with ArangoDB server is possible via a REST interface provided many interfaces

Independently of your project architecture, there are some required npm packages we’ll be using to implement a basic API:

  • ExpressJS: a NodeJS web framework used to implement REST services
  • Arangojs: the official ArangoDB Javascript driver

From our project directory in the command line, let’s hit the following command to install both of them while letting them persist in the package.json file:

npm install express arangojs --save

Note: for the next sections to work properly we need to run our ArangoDB server and get the correct database user’s credentials to use within our API, by default there is always a root user with an empty password if not customized before.

Here is how the dashboard looks like when we’re connected as a database root user:

Image for post

Create Database & Collections

ArangoDB’s multi-model feature allows to manage many types of database with the same engine, for the sake of this article we will create and use a Document database for a basic CRUD API.

From the dashboard let’s create a database called alibabacloudblog as illustrated in the screenshot bellow (owned by the current user: root):

Image for post

Let’s switch to be connected to the database we have just created (still from the admin panel) so that we’ll manage our database to do things like create collections (represents table in relational DBMS).

We then create the article collection, it’s a self-explaining word representing an article as in a blog with title and description as main fields.

As example, here’s the screen to create the article Document collection:

Image for post

Database Configuration

Config file: config/db.js

Next, into the config folder we create a database configuration file called db.js within our project with the following content:

module.exports = {
'url': 'http://127.0.0.1:8529', // The default URL for a local server
'database': 'alibabacloudblog', // The database name
// Database user credentials to use
'username': 'root',
'password': 'root'
};

Database connection

Here’s how we setup the database connection:

var DB = new arangojs.Database({
url: dbConfig.url
});

Let’s select the database to work with, it’s the one we’ve created up here and defined in the configuration file:

DB.useDatabase(dbConfig.database);

We specify the database user with a single line of code, it consists of the user’s username and password as following:

DB.useBasicAuth(dbConfig.username, dbConfig.password);

It’s possible to connect by using a bearer token with the function useBearerAuth talking the token as single parameter, read more about it here.

Note: be sure to use an actual database user you’ve into your server installation as for this example we’re using the root user with root as its password both need to be updated in db.js file.

Article Service

Next, we create the Article service. It’s actually the place we’ll be writing every codes necessary to manage Article while interacting with the correct database collection.

/**
* ArticleService
* @author dassiorleando
*/
var dbConfig = require('../config/db'),
arangojs = require('arangojs'),
DB = new arangojs.Database({ // Database connection
url: dbConfig.url
});
// Database selection
DB.useDatabase(dbConfig.database);
// Speficy the database user
DB.useBasicAuth(dbConfig.username, dbConfig.password);
// Collection to manage: Article
var Article = DB.collection('article');
// Save a new article with title and description as required fields
exports.create = function (article) {
if (!article.title || !article.description) return;
return Article.save(article);
};
// Update an existing article, the incoming object should have the _key field
exports.update = function (article) {
if (!article._key) return;
return Article.update(article._key, article);
};
// Remove an existing article by its _key
exports.remove = function (key) {
if (!key) return;
return Article.removeByKeys([key]);
};
// Find all articles saved so fare
exports.findAll = function () {
return Article.all();
};
// Find an article by its key
exports.findByKey = function (key) {
if (!key) return;
return Article.firstExample({_key: key});
};

This service is used as interface for Article database operations and eventual custom business logics, these functions will then be used into the resources/article.js file acting as the Article controller (Rest).

Note that all documents created in any collections will automatically get the following server-generated attributes:

  • _id: A unique id, consisting of collection name and a server-side sequence value
  • _key: The server sequence value
  • _rev: The document’s revision id

Whenever you run queries on the documents in collections, don’t be surprised if these additional attributes are returned as well.

At any moment of your App development you can decide to add more fields to a collection’s document, as the field are dynamic there is no need to perform any other special queries on other documents unless you’ve something else to accomplish.

You can count the number of documents of a collections using the following query as described in the guide:

FOR doc IN collection
COLLECT WITH COUNT INTO length
RETURN length

A more populated approach to handle the whole data would have been by making a JOIN between two collections, one the Article who serve to define the blog’s article model and another the user defining the article’s authors. Let’s read more about join here.

Instead of using the predefined collections functions we have up here in the ArangoJS SDK, one might try writing plain AQL queries and end up with the same result, here’s a simple query getting a user by its Name and ID (using bind parameters):

FOR u IN users
FILTER u.id == @id && u.name == @name
RETURN u

AQL

Here are some keywords available to use in order to perform AQL queries:

  • FOR: array iteration
  • RETURN: results projection
  • FILTER: results filtering
  • SORT: result sorting
  • LIMIT: result slicing
  • LET: variable assignment
  • COLLECT: result grouping
  • INSERT: insertion of new documents
  • UPDATE: (partial) update of existing documents
  • REPLACE: replacement of existing documents
  • REMOVE: removal of existing documents
  • UPSERT: insertion or update of existing documents

Let’s update a single user by its key:

INSERT {
_key: "userKeyGoesHere",
firstName: "NewFirstName",
name: "NewName",
location: "Cameroon"
} IN users

The same query performed with the UPDATE keyword is as follow:

UPDATE "userKeyGoesHere" WITH {
firstName: "NewFirstName",
name: "NewName",
location: "Beijing"
} IN users

The most important thing when performing an update is to provide the exact document _key or _id fields as they are unique, unless we’ll be updating another document.

Removing a document in users collection if we know its key:

REMOVE " userKeyGoesHere " IN users

In this case as we’re using the Javascript driver, here is the syntax using to perform these queries:

DB.query({
query: "query goes here",
bindVars: bindVarsObjectsIfNeeded // It's an object with the binding fields
})
.then(function(cursor) {
return cursor.next().then(function(result) {
// ...
});
})
.catch(function(err) {
// ...
});

If interested in using it for custom business logic into your API, here is the AQL getting started which is clearly showing details on how to use AQL. Queries guide with more examples are available on the reference manual.

Create an Article

Here’s the controller action to create an Article:

router.post('/', function(req, res) {
var article = {
title: req.body.title,
description: req.body.description
};
// Explicit save
ArticleService
.create(article)
.then(function(doc) {
console.log('Saved documents ' + doc._key);
return res.status(200).json(doc);
})
.catch(function(error) {
console.error('Error saving document', error);
return res.status(500).json(error);
});
});

Update an Article

In the same way, the update rest action is as follow:

router.put('/', function(req, res) {
var article = {
_key: req.body._key,
title: req.body.title,
description: req.body.description
};
// Explicit update
ArticleService
.update(article)
.then(function(doc) {
console.log('Updated document ' + doc._key);
return res.status(200).json(doc);
})
.catch(function(error) {
console.error('Error updating document', error);
return res.status(500).json(error);
});
});

Find an Article by Key

Let’s define the find by key controller action:

router.get('/:articleKey', function(req, res) {
var articleKey = req.params.articleKey;
ArticleService
.findByKey(articleKey)
.then(function(doc) {
console.log(`Get a document by key "${req.params.articleKey}".`, doc._key);
return res.status(200).json(doc);
})
.catch(function(error) {
console.error('Error getting single document', error);
return res.status(500).json(error);
});
});

Find all Articles

Loading all saved Articles is a simple task as it’s done only by calling the all() collection’s method as we did into ArticleService, here’s the corresponding controller call:

router.get('/', function(req, res) {
ArticleService
.findAll()
.then(function(response) {
console.log(`Load all saved documents.`, response._result);
return res.status(200).json(response._result);
})
.catch(function(error) {
console.error('Error getting documents', error);
return res.status(500).json(error);
});
});

Delete Article

Deleting an Article is being done as follow:

router.delete('/:articleKey', function(req, res) {
var articleKey = req.params.articleKey;
ArticleService
.remove(articleKey)
.then(function(doc) {
if (doc.removed) console.log('Removed document' + doc);
return res.status(200).json(doc);
})
.catch(function(error) {
console.error('Error removing document', error);
return res.status(500).json(error);
});
});

Bulk Delete

We can use removeByKeys function to remove many Articles in a bulk way: Article.removeByKeys(keys) here keys is an array of Articles _key.

Run the API

A basic command is available to run our API on our ECS or locally on any computer as simply as typing node server.js, if not customized by default the port 3000 will serve the App.

We can quickly use a Rest API client to test the whole thing or via the dashboard, here is the Article collection content:

Image for post

Conclusion

In this long article, we’ve seen how to build a basic NodeJS API using ArangoDB as the database management system with its ArangoJS driver.

The full source code for this article can be found on my GitHub page.

Reference

https://www.alibabacloud.com/blog/using-arangodb-within-a-nodejs-app_594908?spm=a2c41.13018712.0.0

Written by

Follow me to keep abreast with the latest technology news, industry insights, and developer trends.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store