ArangoDB Manual Pages


Foxx

Foxx: Build APIs and simple web applications in ArangoDB

Foxx is an easy way to create APIs and simple web applications from within ArangoDB. It is inspired by Sinatra, the classy Ruby web framework. If Foxx is Sinatra, ArangoDB's Actions are the corresponding Rack. They provide all the HTTP goodness.

If you just want to install an existing application, please use the Foxx Manager. If you want to create your own application, please continue reading.

Overview

An application built with Foxx is written in JavaScript and deployed to ArangoDB directly. ArangoDB serves this application, you do not need a separate application server.

Think of an Foxx app as a typical web app similar to any other web app using other technologies. A Foxx app provides one or more URLs, which can either be accessed directly from the browser or from a backend application written e.g. in Ruby or C#. Other features include:

  • Routing: Define arbitrary routes namespaced via the Controllers
  • Accesses data: Direct access to all data in ArangoDB using simple queries, AQL, traversals and more
  • Manipulates data: Create new or manipulate existing entries
  • Deliver static files like HTML pages, CSS or images directly

The typical request to a Foxx application will work as follows (only conceptually, a lot of the steps are cached in reality):

  1. The request is routed to a Foxx application depending on the mount point
  2. The according controller of this application is determined (via something called the manifest file)
  3. The request is then routed to a specific handler in this controller

The handler will now parse the request. This includes determining all parameters from the body (which is typically JSON encoded) to the path parameters of the URL. It is then up to you to handle this request and generate a response. In this process you will probably access the database. This is done via the Repository: This is an entity that is responsible for a collection and specifically:

  1. Creating new entries in this collection
  2. Modify or delete existing entries in this collection
  3. Search for entries in this collection

To represent an entry in this collection it will use a Model, which is a wrapper around the raw data from the database. Here you can implement helper functions or simple access methods.

Your first Foxx app in 5 minutes - a step-by-step tutorial

Let's build an application that sends a plain-text response "Hello YourName!" for all requests to /dev/my_app/hello/YourName.

First, create a directory apps somewhere in your filesystem. This will be the Foxx application base directory for your database instance. Let's assume from now on that the absolute path for this directory is /home/user/apps. When you have created the directory, create a sub-directory databases in it.

Foxx applications are database-specific, and the databases sub-directory is used to separate the Foxx applications of different databases running in the same ArangoDB instance.

Let's assume for now that you are working in the default database (_system), that is used when no database name is specified otherwise. To use Foxx applications with the _system database, create a sub-directory _system inside the databases subdirectory. All Foxx applications for the _system database will go into this directory. Note: to add a Foxx application for a different databases than _system, use the database's name as the directory name instead of _system.

Finally, we can add a sub-directory my_app in the _system directory and should end up with the following directory layout (starting at /home/user in our example):

apps/
  databases/
    _system/
      my_app/

In the my_app directory, create a file named app.js and save the following content in it:

(function() {
    "use strict";

    var Foxx = require("org/arangodb/foxx"),
        controller = new Foxx.Controller(applicationContext)

    controller.get("/hello/:name", function(req, res) {
        res.set("Content-Type", "text/plain");
        res.body = "Hello " + req.params("name");
    }); 

}());

Beside the app.js we need a manifest file. In order to achieve that, we create a file called manifest.json in our my_app directory with the following content:

{
  "name": "my_app",
  "version": "0.0.1",
  "author": "me and myself",
  "controllers": {
    "/": "app.js"
  }
}

You must specify a name and a version number for your application, otherwise it won't be loaded into ArangoDB.

You should now have the following files and directories with your application (starting at /home/user in our example):

apps/
  databases/
    _system/
      my_app/
        manifest.json
        app.js

This is your application, and you're ready to use it.

Testing the application

Start ArangoDB as follows:

$ arangod --javascript.dev-app-path /home/user/apps /tmp/fancy_db

If you chose a different directory name, you need to replace /home/user/apps with the actual directory name of course. Replace /tmp/fancy_db with the directory your database data is located in.

The command will start the ArangoDB server in a development mode using the directory /home/user/apps as the workspace and /tmp/fancy_db as your database directory. In development mode the server automatically reloads all application files on every request, so changes to the underlying files are visible instantly. Note: if you use the development mode for the first time or choose a different directory for dev-app-path, it may be necessary to start ArangoDB with the --upgrade option once. This will initialise the specified application directory.

Note: the development mode is convenient when developing applications but the permanent reloading has an impact on performance. Therefore permanent reloading is only performed in development mode and not in production mode. Whenever you think your application is ready for production, you can install it using the Foxx manager and avoid the overhead of reloading.

Now point your browser to http://localhost:8529/dev/my_app/hello/YourName and you should see "Hello YourName".

Note: the above and all following examples assume that you are using the default database (_system).If you use a different database than _system, URLs must be changed to include the database name, too. For example, if your database name is mydb, the above URL changes to http://localhost:8529/_db/mydb/dev/my_app/hello/YourName. For more information on how to access specific databases, please refer to Address of a Database .

After this short overview, let's get into the details. There are several example apps available on Github. You can install them via Foxx manager (covered in the chapter on Foxx manager) or simply clone them from https://github.com/arangodb/.

Start with "hello-foxx" (https://github.com/arangodb/hello-foxx) as it contains several basic usage examples. "aye-aye" and "fugu" are more advanced apps showing how to use Backbone, Underscore and Jquery together with Foxx. "foxx-authentication" shows how to register users, log in and check permissions.

Handling Requests

In development mode all available applications from the application directory /home/user/apps/databases/<database name>/ are visible under http://localhost:8529/dev/<directory name> where <database name> is the name of the current database and <directory name> is the directory name of your application.

In our example, <directory name> was my_app and as we didn't specify a database, <database name> defaulted to _system.

When applications are installed in production mode, you can change the /dev prefix to whatever you like, see Foxx Manager.

If you do not redefine it, all requests that go to the root of your application (i.e. /) will be redirected to index.html.

This means that if your application does not provide a file index.html, calling the application root URL may result in a 404 error. In our example, the application root URL is http://localhost:8529/dev/my_app/hello/. Call it, and you should something like this in return:

{
  "error": true,
  "code": 404,
  "errorNum": 404,
  "errorMessage": "unknown path 'dev/my_app/index.html'"
}

To fix that, you can give your app a different default document, e.g. "hello/unknown". The adjusted manifest now looks like this:

{
  "name": "my_app",
  "version": "0.0.1",
  "author": "me and myself",
  "controllers": {
    "/": "app.js"
  },
  "defaultDocument": "hello/unknown"
}

Note: browsers tend to cache results of redirections. To see the new default document in effect, first clear your browser's cache and point your browser to http://localhost:8529/dev/my_app/.

Accessing collections from Foxx

Foxx assumes by default that an application has itws own collections. Accessing collections directly by name could cause problems, for instance if you had two completely independent Foxx applications that both access their own collection 'users'.

To prevent such issues, Foxx provides functions that return an application-specific collection name. For example, applicationContext.collectionName('users') will return the collection name prefixed with the application name, e.g. "myapp_users". This allows to have a 'users' collection which is specific for each application.

Additionally, a Foxx controller has a function "collection" that returns a reference to a collection prefixed like above, in the same way as db.<collection-name> would do. In the example, controller.collection('users') would return the collection object for the "myapp_users" collection, and you could use it like any other collection with the db object, e.g.

controller.collection('users').toArray() 
controller.collection('users').save(...) 
controller.collection('users').remove(...) 
controller.collection('users').replace(...) 

Of course you still use any collection directly with the db object even from Foxx. To access an collection called "movies" this could be one solution:

app.get("/all", function(req, res) { 
    var db = require("org/arangodb").db; 
    res.json({ movies: db.movies.toArray() }); 
}); 

Of course this completely bypasses prefixing and repositories, but works well especially for quick tests or shared collections that are NOT application-specific.

Then there are Foxx repositories. These are objects that you can create to hide the internals of the database access from the application so that the application will just use the repository but not the database.

A repository is an object that wrap access to a collection (or multiple collections if you want), whereas controller.collection returns the collection itself. That's the main difference.

To return a list of users from a controller using a repository, you could use it like this:

var foxx = require("org/arangodb/foxx"); 
var db = require("org/arangodb").db; 
var usersRepo = new foxx.Repository(db._collection("users")); 
app.get("/all", function(req, res) { 
   res.json({ users: usersRepo.collection.toArray() }); 
}); 

Of course you can create your own methods in the repository to add extra functionality.

Details on FoxxController

new FoxxController(applicationContext, options)

This creates a new Controller. The first argument is the controller context available in the variable applicationContext. The second one is an options array with the following attributes:

  • urlPrefix: All routes you define within will be prefixed with it.

Examples

app = new Controller(applicationContext, {
urlPrefix: "/meadow"
});

HTTP Methods

Get

FoxxController::get(path, callback)

This handles requests from the HTTP verb get.

When defining a route you can also define a so called 'parameterized' path like /goose/:barn. In this case you can later get the value the user provided for barn via the params function (see the Request object).

Examples

app.get('/goose/barn', function (req, res) {
// Take this request and deal with it!
});

Head

FoxxController::head(path, callback)

This handles requests from the HTTP verb head. You have to give a function as callback. It will get a request and response object as its arguments

Post

FoxxController::post(path, callback)

This handles requests from the HTTP verb post. See above for the arguments you can give.

Examples

app.post('/goose/barn', function (req, res) {
// Take this request and deal with it!
});

Put

FoxxController::put(path, callback)

This handles requests from the HTTP verb put. See above for the arguments you can give.

Examples

app.put('/goose/barn', function (req, res) {
// Take this request and deal with it!
});

Patch

FoxxController::patch(path, callback)

This handles requests from the HTTP verb patch. See above for the arguments you can give.

Examples

app.patch('/goose/barn', function (req, res) {
// Take this request and deal with it!
});

Delete

FoxxController::delete(path, callback)

This handles requests from the HTTP verb delete. See above for the arguments you can give.

Warning
Do not forget that delete is a reserved word in JavaScript and therefore needs to be called as app['delete']. There is also an alias del for this very reason.

Examples

app['delete']('/goose/barn', function (req, res) {
// Take this request and deal with it!
});
app.del('/goose/barn', function (req, res) {
// Take this request and deal with it!
});

Documenting and constraining a specific route

If you now want to document your route, you can use JSDoc style comments (a multiline comment block with the first line starting with /** instead of /*) above your routes to do that:

/** Get all foxxes
  * 
  * If you want to get all foxxes, please use this
  * method to do that.
  */
app.get("/foxxes", function () {
  // ...
});

The first line will be treated as a summary (For optical reasons in the produced documentation, the summary is restricted to 60 characters). All following lines will be treated as additional notes shown in the detailed view of the route documentation. With the provided information, Foxx will generate a nice documentation for you. Furthermore you can describe your API by chaining the following methods onto your path definition:

Path Param

If you defined a route "/foxx/:id", you can constrain which format a path parameter (/foxx/12) can have by giving it a type. We currently support the following types:

  • int
  • string

You can also provide a description of this parameter.

Examples

app.get("/foxx/:id", function {
// Do something
}).pathParam("id", {
description: "Id of the Foxx",
type: "int"
});

Query Param

FoxxController::queryParam(id, options)

Describe a query parameter:

If you defined a route "/foxx", you can constrain which format a query parameter (/foxx?a=12) can have by giving it a type. We currently support the following types:

  • int
  • string

You can also provide a description of this parameter, if it is required and if you can provide the parameter multiple times.

Examples

app.get("/foxx", function {
// Do something
}).queryParam("id", {
description: "Id of the Foxx",
type: "int",
required: true,
allowMultiple: false
});

Body Param

FoxxController::bodyParam(paramName, description, Model)

Expect the body of the request to be a JSON with the attributes you annotated in your model. It will appear alongside the provided description in your Documentation. This will initialize a Model with the data and provide it to you via the params as paramName. For information about how to annotate your models, see the Model section.

Error Response

FoxxController::errorResponse(errorClass, code, description)

Define a reaction to a thrown error for this route: If your handler throws an error of the defined errorClass, it will be caught and the response will have the given status code and a JSON with error set to your description as the body.

If you want more control over the returned JSON, you can give an optional fourth parameter in form of a function. It gets the error as an argument, the return value will transformed into JSON and then be used as the body. The status code will be used as described above. The description will be used for the documentation.

It also adds documentation for this error response to the generated documentation.

Examples

/* define our own error type, FoxxyError */
var FoxxyError = function (message) {
this.message = "the following FoxxyError occurred: ' + message;
};
FoxxyError.prototype = new Error();
app.get("/foxx", function {
/* throws a FoxxyError */
throw new FoxxyError();
}).errorResponse(FoxxyError, 303, "This went completely wrong. Sorry!");
app.get("/foxx", function {
throw new FoxxyError("oops!");
}).errorResponse(FoxxyError, 303, "This went completely wrong. Sorry!", function (e) {
return {
code: 123,
desc: e.message
};
});

onlyIf

FoxxController::onlyIf(check)

Provide it with a function that throws an exception if the normal processing should not be executed. Provide an errorResponse to define the behavior in this case. This can be used for authentication or authorization for example.

Examples

app.get("/foxx", function {
// Do something
}).onlyIf(aFunction).errorResponse(ErrorClass, 303, "This went completely wrong. Sorry!");

onlyIfAuthenticated

FoxxController::onlyIf(code, reason)

Please activate authentification for this app if you want to use this function. If the user is logged in, it will do nothing. Otherwise it will respond with the status code and the reason you provided (the route handler won't be called). This will also add the according documentation for this route.

Examples

app.get("/foxx", function {
// Do something
}).onlyIfAuthenticated(401, "You need to be authenticated");

Documenting and constraining all routes of a controller

In addition to documenting a specific route, you can also do the same for all routes of a controller. For this purpose use the allRoutes object of the according controller. The following methods are available:

Error Response

RequestContextBuffer::errorResponse(errorClass, code, description)

Defines an errorResponse for all routes of this controller. For details on errorResponse see the according method on routes.

Examples

app.allroutes.errorResponse(FoxxyError, 303, "This went completely wrong. Sorry!");
app.get("/foxx", function {
// Do something
});

onlyIf

RequestContextBuffer::onlyIf(code, reason)

Defines an onlyIf for all routes of this controller. For details on onlyIf see the according method on routes.

Examples

app.allroutes.onlyIf(myPersonalCheck);
app.get("/foxx", function {
// Do something
});

onlyIfAuthenticated

RequestContextBuffer::errorResponse(errorClass, code, description)

Defines an onlyIfAuthenticated for all routes of this controller. For details on onlyIfAuthenticated see the according method on routes.

Examples

app.allroutes.onlyIfAuthenticated(401, "You need to be authenticated");
app.get("/foxx", function {
// Do something
});

Before and After Hooks

You can use the following two functions to do something before or respectively after the normal routing process is happening. You could use that for logging or to manipulate the request or response (translate it to a certain format for example).

Before

FoxxController::before(path, callback)

The before function takes a path on which it should watch and a function that it should execute before the routing takes place. If you do omit the path, the function will be executed before each request, no matter the path. Your function gets a Request and a Response object.

Examples

app.before('/high/way', function(req, res) {
//Do some crazy request logging
});

After

FoxxController::after(path, callback)

This works pretty similar to the before function. But it acts after the execution of the handlers (Big surprise, I suppose).

Examples

app.after('/high/way', function(req, res) {
//Do some crazy response logging
});

The Request and Response Objects

When you have created your FoxxController you can now define routes on it. You provide each with a function that will handle the request. It gets two arguments (four, to be honest. But the other two are not relevant for now):

  • The request object
  • The response object

These objects are provided by the underlying ArangoDB actions and enhanced by the BaseMiddleware provided by Foxx.

The Request Object

The request object inherits several attributes from the underlying Actions:

  • compatibility: an integer specifying the compatibility version sent by the client (in request header x-arango-version). If the client does not send this header, ArangoDB will set this to the minimum compatible version number. The value is 10000 * major + 100 * minor (e.g. 10400 for ArangoDB version 1.4).
  • user: the name of the current ArangoDB user. This will be populated only if authentication is turned on, and will be null otherwise.
  • database: the name of the current database (e.g. _system)
  • protocol: http or https
  • server: a JSON object with sub-attributes address (containing server host name or IP address) and port (server port).
  • path: request URI path, with potential database name stripped off.
  • url: request URI path + query string, with potential database name stripped off
  • headers: a JSON object with the request headers as key/value pairs
  • cookies: a JSON object with the request cookies as key/value pairs
  • requestType: the request method (e.g. "GET", "POST", "PUT", ...)
  • requestBody: the complete body of the request as a string
  • parameters: a JSON object with all parameters set in the URL as key/value pairs
  • urlParameters: a JSON object with all named parameters defined for the route as key/value pairs.

In addition to these attributes, a Foxx request objects provides the following convenience methods:

Body

request.body()

Get the JSON parsed body of the request. If you need the raw version, please refer to the rawBody function.

Raw Body

request.rawBody()

The raw request body, not parsed. Just a String.

Params

request.params(key)

Get the parameters of the request. This process is two-fold:

  • If you have defined an URL like /test/:id and the user requested /test/1, the call params("id") will return 1.
  • If you have defined an URL like /test and the user gives a query component, the query parameters will also be returned. So for example if the user requested /test?a=2, the call params("a") will return 2.

The Response Object

Every response object has the body attribute from the underlying Actions to set the raw body by hand.

You provide your response body as a string here.

Status

response.status(code)

Set the status code of your response, for example:

Examples

response.status(404);

Set

response.set(key, value)

Set a header attribute, for example:

Examples

response.set("Content-Length", 123);
response.set("Content-Type", "text/plain");
// or alternatively:
response.set({
"Content-Length": "123",
"Content-Type": "text/plain"
});

JSON

response.json(object)

Set the content type to JSON and the body to the JSON encoded object you provided.

Examples

response.json({'born': 'December 12, 1915'});

Details on FoxxModel

The model doesn't know anything about the database. It is just a representation of the data as an JavaScript object. You can add and overwrite the methods of the prototype in your model prototype via the object you give to extend. In your model file, export the model as model.

var Foxx = require("org/arangodb/foxx");

var TodoModel = Foxx.Model.extend({
});

exports.model = TodoModel;

A Foxx Model can be initialized with an object of attributes and their values.

There's also the possibility of annotation: The second hash you give to the extend method are properties of the prototype. You can define an attributes property there:

var Foxx = require("org/arangodb/foxx");

var PersonModel = Foxx.Model.extend({
  // Your instance properties
}, {
  // Your prototype properties
  attributes: {
    name: { type: "string", required: true },
    age: { type: "integer" },
    active: { type: "boolean", defaultValue: true }
});

exports.model = TodoModel;

This has two effects: On the one hand it provides documentation. If you annotated your model, you can use it in the bodyParam method for documentation. On the other hand it will influence the behavior of the constructor: If you provide an object to the constructor, it will only take those attributes that are listed in the attributes object. This is especially useful if you want to to initialize the Model from user input. On the other hand it will set the default value for all attributes that have not been set by hand. An example:

var person = new PersonModel({
  name: "Pete",
  admin: true
});

person.attributes // => { name: "Pete", active: true }

Extend

FoxxModel::extend(instanceProperties, classProperties)

Extend the Model prototype to add or overwrite methods. The first object contains the properties to be defined on the instance, the second object those to be defined on the prototype.

Initialize

new FoxxModel(data)

If you initialize a model, you can give it initial data as an object.

Examples

instance = new Model({
a: 1
});

Get

FoxxModel::get(name)

Get the value of an attribute

Examples

instance = new Model({
a: 1
});
instance.get("a");

Set

FoxxModel::set(name, value)

Set the value of an attribute or multiple attributes at once

Examples

instance = new Model({
a: 1
});
instance.set("a", 2);
instance.set({
b: 2
});

Has

FoxxModel::has(name)

Returns true if the attribute is set to a non-null or non-undefined value.

Examples

instance = new Model({
a: 1
});
instance.has("a"); //=> true
instance.has("b"); //=> false

Attributes

forDB

FoxxModel::forDB()

Return a copy of the model which can be saved into ArangoDB

forClient

FoxxModel::forClient()

Return a copy of the model which you can send to the client.

Details on FoxxRepository

A repository is a gateway to the database. It gets data from the database, updates it or saves new data. It uses the given model when it returns a model and expects instances of the model for methods like save. In your repository file, export the repository as repository.

Foxx = require("org/arangodb/foxx");

TodosRepository = Foxx.Repository.extend({
});

exports.repository = TodosRepository;

Initialize

new FoxxRepository(collection, opts)

Create a new instance of Repository

A Foxx Repository is always initialized with a collection object. You can get your collection object by asking your Foxx.Controller for it: the collection method takes the name of the collection (and will prepend the prefix of your application). It also takes two optional arguments:

  1. Model: The prototype of a model. If you do not provide it, it will default to Foxx.Model
  2. Prefix: You can provide the prefix of the application if you need it in your Repository (for some AQL queries probably). Get it from the Foxx.Controller via the collectionPrefix attribute.

Examples

instance = new Repository(app.collection("my_collection"));
// or:
instance = new Repository(app.collection("my_collection"), {
model: MyModelPrototype,
prefix: app.collectionPrefix,
});

Collection

See the documentation of collection.

Prefix

See the documentation of collection.

ModelPrototype

See the documentation of collection.

Save

Expects a model. Will set the ID and Rev on the model. Returns the model (for convenience).

Remove By Id

Expects an ID of an existing document.

Remove By Example

Find all documents that fit this example and remove them.

Replace

Find the model in the database by its _id and replace it with this version. Expects a model. Sets the Revision of the model. Returns the model (for convenience).

ReplaceById

Find the model in the database by the given ID and replace it with the given. model. Expects a model. Sets the ID and Revision of the model. Returns the model (for convenience).

By Id

Returns the model for the given ID.

By Example

Returns an array of models for the given ID.

First Example

Returns a model that matches the given example.

The Manifest File

In the manifest.json you define the components of your application. The content is a JSON object with the following attributes (not all attributes are required though):

  • assets: Deliver pre-processed files
  • author: The author name
  • contributors: An array containing objects, each represents a contributor (with name and optional email)
  • controllers: Map routes to FoxxControllers
  • defaultDocument: The default document when the applicated root (/) is called (defaults to index.html)
  • description: A short description of the application (Meta information)
  • engines: Should be an object with arangodb set to the ArangoDB version your Foxx app is compatible with.
  • files: Deliver files
  • isSystem: Mark an application as a system application
  • keywords: An array of keywords to help people find your Foxx app
  • lib: Base path for all required modules
  • license: Short form of the license (MIT, GPL...)
  • name: Name of the application (Meta information)
  • repository: An object with information about where you can find the repository: type and url
  • setup: Path to a setup script
  • teardown: Path to a teardown script
  • thumbnail: Path to a thumbnail that represents the application (Meta information)
  • version: Current version of the application (Meta information)

If you install an application using the Foxx manager or are using the development mode, your manifest will be checked for completeness and common errors. You should have a look at the server log files after changing a manifest file to get informed about potential errors in the manifest.

A more complete example for a Manifest file:

{
  "name": "my_website",
  "version": "1.2.1",
  "description": "My Website with a blog and a shop",
  "thumnail": "images/website-logo.png",

  "controllers": {
    "/blog": "apps/blog.js",
    "/shop": "apps/shop.js"
  },

  "lib": "lib",

  "files": {
    "/images": "images"
  },

  "assets": {
    "application.js": {
      "files": [
        "vendor/jquery.js",
        "assets/javascripts/*"
      ]
    }
  },

  "setup": "scripts/setup.js",
  "teardown": "scripts/teardown.js"
}

The setup and teardown scripts

You can provide a path to a JavaScript file that prepares ArangoDB for your application (or respectively removes it entirely). These scripts have access to appCollection and appCollectionName. Use the setup script to create all collections your application needs and fill them with initial data if you want to. Use the teardown script to remove all collections you have created.

Note: the setup script is called on each request in the development mode. If your application needs to set up specific collections, you should always check in the setup script whether they are already there.

The teardown script is called when an application is uninstalled. It is good practice to drop any collections in the teardown script that the application used exclusively, but this is not enforced. Maybe there are reasons to keep application data even after removing an application. It's up to you to decide what to do.

controllers is an object that matches routes to files

  • The key is the route you want to mount at
  • The value is the path to the JavaScript file containing the FoxxController you want to mount

You can add multiple controllers in one manifest this way.

The files

Deliver all files in a certain folder without modifying them. You can deliver text files as well as binaries:

"files": {
  "/images": "images"
}

The assets

The value for the asset key is an object consisting of paths that are matched to the files they are composed of. Let's take the following example:

"assets": { "application.js": { "files": [ "vendor/jquery.js", "assets/javascripts/*" ] } }

If a request is made to /application.js (in development mode), the file array provided will be processed one element at a time. The elements are paths to files (with the option to use wildcards). The files will be concatenated and delivered as a single file.

The content-type (or mime type) of the HTTP response when requesting application.js is automatically determined by looking at the filename extension in the asset name (application.js in the above example). If the asset does not have a filename extension, the content-type is determined by looking at the filename extension of the first file in the files list. If no file extension can be determined, the asset will be delived with a content-type of text/plain.

It is possible to explicitly override the content-type for an asset by setting the optional contentType attribute of an asset as follows:

"assets": { "myincludes": { "files": [ "vendor/jquery.js", "assets/javascripts/*" ], "contentType": "text/javascript" } }

Development Mode

If you start ArangoDB with the option --javascript.dev-app-path followed by the path to a directory containing a manifest file and the path to the database, you are starting ArangoDB in development mode with the application loaded. This means that on every request:

  1. All routes are dropped
  2. All module caches are flushed
  3. Your manifest file is read
  4. All files in your lib folder are loaded
  5. An app in DIRNAME is mounted at /dev/DIRNAME
  6. The request will be processed

This means that you do not have to restart ArangoDB if you change anything in your app. It is of course not meant for production, because the reloading makes the app relatively slow.

Production Mode

To run a Foxx app in production first copy your app code to the directory given in the config variable --javascript.app-path. After that use Foxx manager to mount the app. You can also use Foxx manager to find out your current app-path.

Controlling Access to Foxx Applications

Access to Foxx applications is controlled by the regular authentication mechanisms present in ArangoDB. The server can be run with or without HTTP authentication.

If authentication is turned on, then every access to the server is authenticated via HTTP authentication. This includes Foxx applications. The global authentication can be toggled via the configuration option server.disable-authentication.

If global HTTP authentication is turned on, requests to Foxx applications will require HTTP authentication too, and only valid users present in the _users system collection are allowed to use the applications.

Since ArangoDB 1.4, there is an extra option to restrict the authentication to just system API calls, such as /_api/... and /_admin/.... This option can be turned on using the server.authenticate-system-only configuration option. If it is turned on, then only system API requests need authentication whereas all requests to Foxx applications and routes will not require authentication.

This is recommended if you want to disable HTTP authentication for Foxx applications but still want the general database APIs to be protected with HTTP authentication.

If you need more fine grained control over the access to your Foxx application, we built an authentication system you can use. Currently we only support cookie-based authentication, but we will add the possibility to use Auth Tokens and external OAuth providers in the near future. Of course you can roll your own authentication mechanism if you want to, and you can do it in an application-specific way if required.

To use the per-application authentication, you should first turn off the global HTTP authentication (or at least restrict it to system API calls as mentioned above). Otherwise clients will need HTTP authentication and need additional authentication by your Foxx application.

To have global HTTP authentication turned on for system APIs but turned off for Foxx, your server startup parameters should look like this:

--server.disable-authentication false --server.authenticate-system-only true

Note: during development, you may even turn off HTTP authentication completely:

--server.disable-authentication true --server.authenticate-system-only true

Please keep in mind that turning HTTP authentication off completely will allow unauthenticated access by anyone to all API functions, so do not use this is production.

Now it's time to configure the application-specific authentication. We built a small demo application to demonstrate how this works.

To use the application-specific authentication in your own app, first activate it in your controller:

FoxxController::activateAuthentication(opts)

To activate authentication for this authentication, first call this function. Provide the following arguments:

  • type: Currently we only support cookie, but this will change in the future.
  • cookieLifetime: An integer. Lifetime of cookies in seconds.
  • cookieName: A string used as the name of the cookie.
  • sessionLifetime: An integer. Lifetime of sessions in seconds.

Examples

app.activateAuthentication({
type: "cookie",
cookieLifetime: 360000,
cookieName: "my_cookie",
sessionLifetime: 400,
});

Adding a login route

FoxxController::login(path, opts)

Add a route for the login. You can provide further customizations via the the options:

usernameField and passwordField can be used to adjust the expected attributes in the post request. They default to username and password. onSuccess is a function that you can define to do something if the login was successful. This includes sending a response to the user. This defaults to a function that returns a JSON with user set to the identifier of the user and key set to the session key. onError is a function that you can define to do something if the login did not work. This includes sending a response to the user. This defaults to a function that sets the response to 401 and returns a JSON with error set to "Username or Password was wrong". Both onSuccess and onError should take request and result as arguments.

Examples

app.login('/login', {
onSuccess: function (req, res) {
res.json({"success": true});
}
});

Adding a logout route

FoxxController::logout(path, opts)

This works pretty similar to the logout function and adds a path to your app for the logout functionality. You can customize it with a custom onSuccess and onError function: onSuccess is a function that you can define to do something if the logout was successful. This includes sending a response to the user. This defaults to a function that returns a JSON with message set to "logged out". onError is a function that you can define to do something if the logout did not work. This includes sending a response to the user. This defaults to a function that sets the response to 401 and returns a JSON with error set to "No session was found". Both onSuccess and onError should take request and result as arguments.

Examples

app.logout('/logout', {
onSuccess: function (req, res) {
res.json({"message": "Bye, Bye"});
}
});

Adding a register route

FoxxController::register(path, opts)

This works pretty similar to the logout function and adds a path to your app for the register functionality. You can customize it with a custom onSuccess and onError function: onSuccess is a function that you can define to do something if the registration was successful. This includes sending a response to the user. This defaults to a function that returns a JSON with user set to the created user document. onError is a function that you can define to do something if the registration did not work. This includes sending a response to the user. This defaults to a function that sets the response to 401 and returns a JSON with error set to "Registration failed". Both onSuccess and onError should take request and result as arguments. You can also set the fields containing the username and password via usernameField (defaults to username) and passwordField (defaults to password). If you want to accept additional attributes for the user document, use the option acceptedAttributes and set it to an array containing strings with the names of the additional attributes you want to accept. All other attributes in the request will be ignored. If you want default attributes for the accepted attributes or set additional fields (for example admin) use the option defaultAttributes which should be a hash mapping attribute names to default values.

Examples

app.register('/logout', {
acceptedAttributes: ['name'],
defaultAttributes: {
admin: false
}
});

Adding a change password route

FoxxController::changePassword(route, opts)

Add a route for the logged in user to change the password. You can provide further customizations via the the options:

passwordField can be used to adjust the expected attribute in the post request. It defaults to password. onSuccess is a function that you can define to do something if the change was successful. This includes sending a response to the user. This defaults to a function that returns a JSON with notice set to "Changed password!". onError is a function that you can define to do something if the login did not work. This includes sending a response to the user. This defaults to a function that sets the response to 401 and returns a JSON with error set to "No session was found". Both onSuccess and onError should take request and result as arguments.

Examples

app.changePassword('/changePassword', {
onSuccess: function (req, res) {
res.json({"success": true});
}
});

Restricting routes

To restrict routes, see the documentation for Documenting and Restraining the routes.

Optional Functionality: FormatMiddleware

To use this plugin, please require it first:

FormatMiddleware = require("org/arangodb/foxx/template_middleware").FormatMiddleware;

This Middleware gives you Rails-like format handling via the extension of the URL or the accept header. Say you request an URL like /people.json:

The FormatMiddleware will set the format of the request to JSON and then delete the .json from the request. You can therefore write handlers that do not take an extension into consideration and instead handle the format via a simple string. To determine the format of the request it checks the URL and then the accept header. If one of them gives a format or both give the same, the format is set. If the formats are not the same, an error is raised.

Use it by calling:

FormatMiddleware = require('foxx').FormatMiddleware;
app.before(FormatMiddleware.new(['json']));

In both forms you can give a default format as a second parameter, if no format could be determined. If you give no defaultFormat this case will be handled as an error.

Optional Functionality: TemplateMiddleware

To use this plugin, please require it first:

TemplateMiddleware = require("org/arangodb/foxx/template_middleware").TemplateMiddleware;

The TemplateMiddleware can be used to give a Foxx.Controller the capability of using templates. Currently you can only use Underscore Templates. It expects documents in the following form in this collection:

{
  path: "high/way",
  content: "hello <%= username %>",
  contentType: "text/plain",
  templateLanguage: "underscore"
}

The content is the string that will be rendered by the template processor. The contentType is the type of content that results from this call. And with the templateLanguage you can choose your template processor. There is only one choice now: underscore. Which would set the body of the response to hello Controller with the template defined above. It will also set the contentType to text/plain in this case. In addition to the attributes you provided, you also have access to all your view helpers.

Initialize

Initialize with the name of a collection or a collection and optionally a set of helper functions. Then use before to attach the initialized middleware to your Foxx.Controller

Examples

templateMiddleware = new TemplateMiddleware("templates", {
uppercase: function (x) { return x.toUpperCase(); }
});
// or without helpers:
//templateMiddleware = new TemplateMiddleware("templates");
app.before(templateMiddleware);

Render

response.render(templatePath, data)

When the TemplateMiddleware is included, you will have access to the render function on the response object. If you call render, Controller will look into the this collection and search by the path attribute. It will then render the template with the given data.

Examples

response.render("high/way", {username: 'Application'})

Iteratively Developing an Application

While developing a Foxx application, it is recommended to use the development mode. The development mode makes ArangoDB reload all components of all Foxx applications on every request. While this has a negative impact on performance, it allows to view and debug changes in the application instantly. It is not recommended to use the development mode in production.

During development it is often necessary to log some debug output. ArangoDB provides a few mechanisms for this:

  • using console.log: ArangoDB provides the console module, which you can use from within your Foxx application like this:

    require("console").log(value);
    

    The console module will log all output to ArangoDB's logfile. If you are not redirecting to log output to stdout, it is recommended that you follow ArangoDB's logfile using a tail -f command or something similar. Please refer to Module "console" for details.

  • using internal.print: The print method of the internal module writes data to the standard output of the ArangoDB server process. If you have start ArangoDB manually and do not run it as an (unattended) daemon, this is a convenient method:
    require("internal").print(value);
    
  • tapping requests / responses: Foxx allows to tap incoming requests and outgoing responses using the before and after hooks. To print all incoming requests to the stdout of the ArangoDB server process, you could use some code like this in your controller:

    controller.before("/*", function (req, res) {
      require("internal").print(req);
    });
    

    Of course you can also use console.log or any other means of logging output.

Deploying a Foxx application

When a Foxx application is ready to be used in production, it is time to leave the development mode and deploy the app in a production environment.

The first step is to copy the application's script directory to the target ArangoDB server. If your development and production environment are the same, there is nothing to do. If production runs on a different server, you should copy the development application directory to some temporary place on the production server.

When the application code is present on the production server, you can use the fetch and mount commands from the Foxx Manager to register the application in the production ArangoDB instance and make it available.

Here are the individual steps to carry out:

  • development:

    • cd into the directory that application code is in. Then create a tar.gz file with the application code (replace app with the actual name):
      cd /path/to/development/apps/directory
      tar cvfz app.tar.gz app
      
    • copy the tar.gz file to the production server:
      scp app.tar.gz production:/tmp/
      
  • production:

    • create a temporary directory, e.g. /tmp/apps and extract the tar archive into this directory:
      mkdir /tmp/apps
      cd /tmp/apps
      tar xvfz /tmp/app.tar.gz
      
    • start the ArangoDB shell and run the following commands in it:
      fm.fetch("directory", "/tmp/apps/app");
      fm.mount("app", "/app");
      

More information on how to deploy applications from different sources can be found in the Foxx Manager manual.