ArangoDB v2.8 reached End of Life (EOL) and is no longer supported.
This documentation is outdated. Please see the most recent version here: Try latest
My first Foxx App
Problem
I want to create a simple API with Foxx, but I never created one before. Specifically I want to create simple API to organize my todo items.
Note: For this recipe you need Arango 2.3. or a version below. For Arango since 2.4 look at the new My first Foxx App.
Solution
Create the folder structure for Foxx apps and start ArangoDB
Create a folder foxx_apps
somewhere where you have write access. Create a folder databases
inside of it, and one called _system
inside of that. Inside of this folder, create a folder for your app. Let’s call it todos
. From now on we will work in that folder. We now create a file called manifest.json
where we will add some meta information (if you want to get more information about the manifest file, check out
in the documentation):
{
"name": "todos",
"version": "0.1.0",
"description": "My first Foxx app to save todos in ArangoDB",
"author": "YOUR NAME",
"license": "Apache 2 License"
}
Now you can start ArangoDB with the following command:
arangod --javascript.dev-app-path /PATH/TO/foxx_apps /PATH/TO/DB
In that case, /PATH/TO/foxx_apps
is the path to the foxx_apps
folder you created above and /PATH/TO/DB
is the path to the folder where your database should be. If you can see the admin interface on http://localhost:8529 you’re now ready to go. Click on ‘Applications’ in the navigation bar – this is the place where all your Foxx apps are. You will see an app called todos (dev)
– the dev
means that your app is running in development mode which features automatic reloading. We are now ready to go.
Don’t have ArangoDB installed yet? Check out the instructions on the ArangoDB download page.
Create the required collections
In ArangoDB you need to create a collection in order to save documents. We will need a collection for our todos. We need to do that before the app is running. In Foxx we use a setup script to prepare ArangoDB for running our app, which includes creating collections. Create a folder scripts
and a file called setup.js
inside of it:
var db = require('org/arangodb').db,
todos = applicationContext.collectionName('todos');
if (db._collection(todos) === null) {
db._create(todos);
}
We use applicationContext.collectionName
to get a name for a collection that is specific for our apps. This allows you to install the same app twice and also prevents different apps writing into the same collection by accident. We will only create the collection, if it has not been created yet. If you want to learn more about db
, please check out
our documentation about handling collections. In our manifest file we now need to add this setup script by adding the following line:
"setup": "scripts/setup.js"
The setup script will be executed when the app is installed. In the development mode however, it will be called everytime we request the app. If we click on the tiny i
on our app, we will get to interactive documentation for our app (which is empty right now, because we don’t have any routes yet). This will trigger our app and therefore execute our setup script. To check if that worked, go to collections
. You will see a collection called dev_ideas_ideas
. Setup is done!
Create our first route
In Foxx, we use controllers to add routes to our application. Let’s create a folder controllers
and inside of that our first controller in a file todos.js
:
var Foxx = require('org/arangodb/foxx'),
controller;
controller = new Foxx.Controller(applicationContext);
controller.get('/', function (req, res) {
res.json({
hello: 'world'
});
});
This will add a get
route on the route of the controller which returns a JSON response { "hello": "world" }
. We now need to mount that controller to some endpoint, this is again done in our manifest file:
"controllers": {
"/todos": "controllers/todos.js"
}
Our controller is now reachable on the todos
endpoint. Want to see it in action? Let’s go to the interactive documentation for our app again (click on Applications, then on the i
next to the app todos (dev)
). Click on todos
and then you will see a get
route – clicking on that will reveal the documentation for this specific route. Click Try it out
and you will see the response we defined. But this description seems a little empty – let’s add some description of what this route should do to our controller:
var Foxx = require('org/arangodb/foxx'),
controller;
controller = new Foxx.Controller(applicationContext);
/** Get a list of all todos
*
* Get a list of all todos that are stored in the database.
*/
controller.get('/', function (req, res) {
res.json({
hello: 'world'
});
});
If you go to the interactive documentation again, you will now see the description you just added. Great! If you want more information about controllers, see the documentation.
Add a model that describes our todo items
We now need to define how we want a single todo item looks like. Foxx uses this information for both the documentation as well as for validating inputs. In a file called todo.js
in the folder models
you put the following
Foxx Model prototype:
var Foxx = require('org/arangodb/foxx'),
Joi = require('joi'),
Todo;
Todo = Foxx.Model.extend({
schema: {
completed: Joi.boolean().default(true),
order: Joi.number().integer().required(),
title: Joi.string().required()
}
});
exports.Model = Todo;
Now we can use the model in our post route to create new items. We want to store the model in the database – in order to do that we create a Repository. A repository has the responsibility to store models in a collection as well as giving a nice interface for searching for those models as well as updating or deleting them. To create a repository you have to provide the collection it should use as well as the model used for the documents in the collection. We create a standard repository in the following snippet. Our post action receives a Todo as its body. We provide a name for it as well as a short description. In the body of the function we then get the parameter todo
which is an instance of our Todo model. We then save this model into our database. This will also add some meta data to the model (the ID, key and revision from ArangoDB). We then send a response back to the client.
var Foxx = require('org/arangodb/foxx'),
Todo = require('models/todo').Model,
todosCollection = applicationContext.collection("todos"),
todos,
controller;
todos = new Foxx.Repository(todosCollection, {
model: Todo
});
controller = new Foxx.Controller(applicationContext);
/** Get a list of all todos
*
* Get a list of all todos that are stored in the database.
*/
controller.get('/', function (req, res) {
res.json({
hello: 'world'
});
});
/** Add a new todo to the list
*
* Create a new todo and add it to the list of todos.
*/
controller.post('/', function (req, res) {
var todo = req.params('todo');
todos.save(todo);
res.json(todo.forClient());
}).bodyParam('todo', 'The Todo you want to create', Todo);
We can now create a todo via the interactive documentation. If you click on the post route, you will see a text box where you can add the raw body that you want to send to the database. Enter the following for example:
{
"order": 1,
"title": "Clean my house"
}
This will create a new todo and add it to the collection. If you look into the collection, you will see that the completed attribute is set to false
, because that’s what we set as a default in our model.
Get a list of all todos
Our initial implementation of the route to get all todos was a fake, let us fix that. We will use underscore.js to iterate over all stored todos to call the forClient
method on them. First require underscore as _
(_ = require('underscore')
), then change the index action to be the following:
controller.get('/', function (req, res) {
res.json(_.map(todos.all(), function (todo) {
return todo.forClient();
}));
});
Try it out in the interactive documentation, you will get a list of all the todos.
Extending the representation of our model
In both our list as well as the return value we don’t get the key of the document. In order to delete a single item we will however need this information. Let’s change the forClient
method of our model in order to get this information. The method could look like this:
forClient: function () {
return {
completed: this.get('completed'),
order: this.get('order'),
title: this.get('title'),
key: this.get('_key')
};
}
We now get the key in both routes.
Deleting a todo item
We could for example define the following route to delete a todo:
controller.del('/:key', function (req, res) {
var key = req.params('key');
todos.removeById(key);
res.json({ success: true });
}).pathParam('key', Joi.string().description('The key of the Todo'));
pathParam
works similar to bodyParam
, but for path parameters. Declare a path parameter in the route by prefixing it with a colon. We use joi again to describe our parameter.
controller.del('/:key', function (req, res) {
var key = req.params('key');
todos.removeById(key);
res.json({ success: true });
}).pathParam('key', Joi.string().description('The key of the Todo'));
When you try this with a todo item you created, it will work. Send the same request again: It will blow up with an error: The document could not be found. This is of course correct, but you may want to have a nicer error response. In Foxx, we just add an error response description to the route and Foxx will handle this case for us. When ArangoDB (require it with ArangoError = require('org/arangodb').ArangoError
) can’t find a document, it will throw an ‘ArangoDB error’. We will instead return a 404 status code with a descriptive error message:
controller.del('/:key', function (req, res) {
var key = req.params('key');
todos.removeById(key);
res.json({ success: true });
}).pathParam('key', Joi.string().description('The key of the Todo'))
.errorResponse(ArangoError, 404, 'The todo item could not be found');
Comment
- If you want to have custom methods on your repository, you can extend it in the same way you extended the Foxx.Model. Learn more about it here
- We will add a new recipe for authentication in the future. In the mean time check out the foxx-sessions-example app
- We will also talk about workers and how to do work outside the request-response-cycle
Author: Lucas Dohmen
Tags: #foxx