API Design with API Blueprint and Aglio

Designing an API can be quite difficult depending on the size and the relations between your models. In Rails it's easy to create an API, open controller, add format.json ... and your done. This is not really a good way but it will work perfectly.

In the past I've always created API's for internal use or consumed external API's. Last week I started with designing/thinking out an API that we should use internally but later on should be consumed by external third parties.

Structure and Design

simpsons-so-many-choices

It turns out that thinking about an API endpoint is quite hard because you have so many ways of structuring your resources and in turn your URI's. Heroku wrote a small guide on best practices in API design, absolute a must to read. A small example to illustrate my struggle:

class Zoo < ActiveRecord::Base  
  has_many :animals
end

class Animal < ActiveRecord::Base  
  belongs_to :zoo
  belongs_to :cage
end  

Option 1: Nested Resources

Quite easy, we nest animals under zoo's and create following API endpoints that are based on RESTfull routes.

GET    /zoos/{zoo_id}/animals  
POST   /zoos/{zoo_id}/animals  
PUT    /animals/{id}  
GET    /animals/{id}  
DELETE /animals/{id}  
  • Advantage: It's clear from the URI that you are creating a new animal for a specific zoo. Updating/deleting an animal only requires to know the animals unique ID (or UUID).
  • Disadvantage: Maybe I also want to query animals based on their cage, using this pattern I would have a complete different URI (ex: /cages/{id}/animals). Not really user friendly for external parties. In most languages a new URI means more work.

Option 2: First class Resources

We treat our resources as first class citizens in our API. This means that our endpoints will look like the following:

GET    /animals        { zoo_id: 1 }  
POST   /animals        { zoo_id: 1 }  
PUT    /animals/{id}  
GET    /animals/{id}  
DELETE /animals/{id}  
  • Advantage: Option 1 disadvantage can now be easily solved by requiring a different body (ex: { cage_id: 1 }). This is more user friendly for external parties since the URI stays the same but it can easily provide different result sets based on provided body.
  • Disadvantage: Sending a body with a GET request is allowed in HTTP standards but it should have no meaning. It's also not clearly from the URI alone that we want animals from a particular zoo.

The GET method means retrieve whatever information (in the form of an entity) is identified by the Request-URI.

I'm in favour to use Option 2 since it allows to easily extend the API without changing to nested urls. I could use a query param instead (ex: /animals?zoo_id=1) to follow HTTP standards. I haven't decided anything but experimented with both options. But again Option 1 is so much clearer then Option 2. But when limiting the level of nesting Option 1 seems to be the better way, darn...

Experimenting with API Design

Exploring API design options in code can be quite slow and requires a lot of work. I searched for a way where I could see my different endpoints, what they would respond and what they required to be used. Enter: API Blueprint

Apollo Blueprint

API Blueprint is perfect for designing your Web API and its comprehensive documentation but also for quick prototyping and collaboration. It is easy to learn and even easier to read – after all it is just a form of plain text.

That clearly says it, one big advantage is that it uses a custom style of markdown. This means I can easily read/write it without to much hassle. The learning curve is very flat, in a single day I had a quite extensive API documentation.

API Blueprint also has a lot of integrations for setting up mock servers or converting the API documentation to something more human readable (read HTML). There is a paying option available but I went for the local version available on Github.

Aglio Gulp Setup

So I have picked my tools now I want to have some automation. Since Aglio converts my markdown to HTML, I need a WYSIWYG-live-reloading-tab in my chrome. I created the following Gulp file. I'm using the gulp-aglio package so I can use aglio with gulp and also added drakov so I can start a mocking server.

var gulp = require('gulp'),  
    aglio = require('gulp-aglio'),
    connect = require('gulp-connect'),
    drakov = require('drakov'),
    plumber = require('gulp-plumber'),
    watch = require('gulp-watch');

gulp.task('docs', function () {  
  gulp.src('api/*.apib')
    .pipe(plumber())
    .pipe(aglio({themeTemplate: 'triple', themeStyle: 'default', themeVariables: 'slate', themeFullWidth: true}))
    .pipe(gulp.dest('docs'));
});

gulp.task('server', function() {  
  connect.server({
    livereload: true,
    root: ['docs']
  });
});

gulp.task('livereload', function() {  
  gulp.src(['docs/*.html'])
    .pipe(plumber())
    .pipe(watch(['api/*.apib', 'api/*.md']))
    .pipe(connect.reload());
});

gulp.task('watch', function() {  
  gulp.watch(['api/*.apib', 'api/*.md'], ['docs']);
})

gulp.task('mock', function() {  
  var argv = {
    sourceFiles: 'api/*.apib',
    serverPort: 5557,
    disableCORS: false,
    discover: true
  }
  drakov.run(argv);
})

gulp.task('default', ['docs', 'server', 'livereload', 'watch']);

gulp.task('build', ['docs']);  

This allows me to start a livereload tab using gulp, to just build the docs I can use gulp build and then finally I can start a mock server using gulp mock. With this Gulp file I created the following directory structure:

├── api -> API files
│   ├── _zoos.md
│   ├── _animals.md
│   └── api.apib
├── docs -> HTML Result from Aglio
│   └── api.html
├── gulpfile.js
└── package.json

Extending the gulp-aglio

So I'm happy with the setup but I had one problem, there where no warnings from Aglio in my gulp setup. These warnings are quite handy because they indicate problems with the API documentation (ex: missing a response). Another thing I noticed is that Aglio allows to include different files with its own syntax but the warning system has no clue about the different files. Receiving a warning on line 507 is quite useless if your main API file has only 3 include tags.

I've created a PR for gulp-aglio that allows to show the warnings from Aglio and then also provide some context, the result:

aglio-extended-error

Conclusion

I really like using API Blueprint, it allows me to easily design an API upfront without touching a single line of code. I can easily see how different applications/integrations might work with the API and spot problems early on during the design phase.

The fact that API Blueprint allows me to start a mock server and much more is an absolute bonus. The downside at this moment is that not everything is supported yet. For example there is no syntax defined for Authentication (but there is a RFC proposal). I did noticed that nested embedded Data Structures (MSON) don't always render correctly and finally common Rails framework things like array query parameters are not supported (again RFC in the make).

Comments powered by Disqus