home shape

Building Hypermedia APIs – FoxxGenerator

This is the third and final part of Lucas blog series about building hypermedia APIs. In the previous part, we identified the needed transitions and collected some information about each of them. Begin with blog post one to get familiar with concepts on Hypermedia and JSON.

We can now describe the identified transitions using FoxxGenerator. To make the most common case simple, it defaults to the type follow. Therefore defining our four follow transitions is easy using FoxxGenerator:

generator.defineTransition('books');
generator.defineTransition('users');
generator.defineTransition('item');
generator.defineTransition('likes');

Note that at this point we are just defining the transitions, we are not adding them to the statemachine we are describing with the help of FoxxGenerator. In the case of creating a book, we need to add additional information. First of, it is a connect transition. Secondly we also need to define the parameters that this transition needs:

generator.defineTransition('createBook', {
  type: 'connect',

  parameters: {
    title: Joi.string()
  }
});

And finally we need to add two transitions to enable people to like books and get from the books to the people that liked it. We need one transition to

follow from the books to the people. Then we need a connect transition that uses the other transition to save the likes. Both of them need to be to: many:

generator.defineTransition('likedBy', {
  to: 'many'
});

generator.defineTransition('addLikes', {
  type: 'connect',
  as: 'likedBy',
  to: 'many'
});

Now we defined each of the transitions. In addition we can add documentation blocks to each of them that will be used in both the self-documenting API as well as the interactive documentation. For example:

/** Get to the detail view on the item
 *
 * Show the detail view of this specific item.
 */
generator.defineTransition('item');

Now that we have defined the transitions, we can start defining our statechart – one state at a time. Let’s begin with the start state:

generator.addStartState({
  transitions: [
    { to: 'books', via: 'books' },
    { to: 'users', via: 'users' }
  ]
});

The start state doesn’t have a name and it doesn’t give any additional information. It only allows you to get to other states. Therefore all we need is an array of transitions. Each transition needs information to which other state it is going (via its name) and via which previously defined transition.

In the case of our two repositories for books and users, we need an additional piece of information: We need to know what entity states are contained in it. In comparison to the start state, it also has a name. Let’s take the books state as an example:

generator.addState('books', {
  type: 'repository',
  contains: 'book',

  transitions: [
    { to: 'book', via: 'item' },
    { to: 'book', via: 'createBook' }
  ]
});

For our entities, we need to first note that they are parameterized. Furthermore we need to define which repository they are contained in, and what attributes they have. On the example of our book:

generator.addState('book', {
  type: 'entity',
  parameterized: true,
  containedin: 'books',

  attributes: {
    title: Joi.string().required()
  },

  transitions: [
    { to: 'likes', via: 'likes' },
    { to: 'user', via: 'likedby' },
    { to: 'user', via: 'addlikes' }
  ]
});

Finally we have one state with specific behavior. At this point we need to use JavaScript to define, how this state should work. We furthermore define the book state to be its superstate so we can access the specific book. We will use AQL to get all neighbors of the book (which are the users that liked the book):

generator.addState('likes', {
  type: 'service',
  superstate: 'book',
  verb: 'get',

  action: function (req, res, opts) {
    var db = require('org/arangodb').db,
      book = opts.superstate.entity,
      query,
      result;

    query = "FOR user " +
      "IN GRAPH_NEIGHBORS('books', { '_id': '" + book.get('_id') + "' }) " +
      "RETURN user.vertex";

    result = db._query(query).toArray();

    res.json({
      'properties': {
        'bookKey': book.get('_key'),
        'likes': result
      },
      'links': []
    });
  }
});

We can of course also extract the logic of finding the books into its own function:

var usersForBook = function (book) {
  var db = require('org/arangodb').db,
    query;

  query = "FOR user " +
    "IN GRAPH_NEIGHBORS('books', { '_id': '" + book.get('_id') + "' }) " +
    "RETURN user.vertex";

  return db._query(query).toArray();
};

generator.addState('likes', {
  type: 'service',
  superstate: 'book',
  verb: 'get',

  action: function (req, res, opts) {
    var book = opts.superstate.entity;

    res.json({
      'properties': {
        'bookKey': book.get('_key'),
        'likes': usersForBook(book)
      },
      'links': []
    });
  }
});

In addition to the description of transitions and states, we only need the following code:

(function () {
  'use strict';
  var FoxxGenerator = require('foxx_generator').Generator,
    Joi = require('joi'),
    generator;

  // books is the name of our app and our graph
  // the generated API will follow the Siren standard
  generator = new FoxxGenerator('books', {
    mediaType: 'application/vnd.siren+json',
    applicationContext: applicationContext,
  });

  // Add the transitions here

  // Add the states here

  generator.generate();
}());

And that’s all we need. This will generate the full API for us including an interactive documentation in the admin interface of ArangoDB to explore it. The API will work like this:

We did not mention an URL or think about status codes. FoxxGenerator takes the semantic description of our statechart and can do all those things for you. If you want to see the entire code, I created a Gist for you here. You can see how close the image is to your drawn image. In my thesis I found out that the drawn statechart is an excellent tool to communicate with an expert from the domain – with FoxxGenerator it is very little work to take this drawing and create an API from it.

FoxxGenerator is part of the technical preview (give it a try) and will be released with the upcoming ArangoDB 2.4.

Frank Celler

Frank Celler

Frank is both entrepreneur and backend developer, developing mostly memory databases for two decades. He is the CTO and co-founder of ArangoDB. Try to challenge Frank asking him questions on C, C++ and MRuby. Besides Frank organizes Cologne’s NoSQL group & is an active member of NoSQL community.

Leave a Comment





Get the latest tutorials, blog posts and news: