June 02, 2016

Documenting APIs using ApiDoc.js

by Caio Ribeiro Pereira

Documenting APIs using ApiDoc.js

Introduction to ApiDoc.js

In this post, we’ll learn how to write and generate elegant API documentation. After all, it is a good practice to provide detailed documentation about how the client applications can connect and consume the data from an API. The coolest thing is that we are going to use a simple tool which generates documentation through the code’s comments.

We’re gonna use the ApiDoc.js, which is a Node.js CLI module to generate the documentation.

site-apidocjs

You can install it as global module, to enable the new command apidoc into your terminal, so run this command:

npm install apidoc - g  

Building an API

Obs.: In our example we are going to use the Express web framework to build an API and to simplify things we won’t write any business rules in the routes, we’ll only create all empty routes to be able to write their necessary documentation.

So first, let’s create the task-api project and install the express module:

mkdir task - api  
cd task - api  
npm init  
npm install express--save  

To build our API code, let’s create the index.js:

var express = require('express');  
var app = express();

// Serving static files from "public" folder
app.use(express.static('public'));

app.get('/tasks', function(req, res) {  
    // business logic for list all tasks...
});
app.get('/tasks/:id', function(req, res) {  
    // business logic for find a task...
});
app.post('/tasks', function(req, res) {  
    // business logic for create a task...
});
app.put('/tasks/:id', function(req, res) {  
    // business logic for update a task...
});
app.delete('/tasks/:id', function(req, res) {  
    // business logic for delete a task...
});
app.listen(3000, function() {  
    console.log('Task api up and running...');
});

The app.use(express.static('public')) middleware will enable a static server for public folder, this directory will be used to put all the generated documentation’s file.

Documenting all API routes

Well, now we can write the documentation of our API. In order to do it, you just need to use some comments params provided by apidoc, you can see all params clicking here. To start our documentation process, first you need to create a descriptor file called apidoc.json in the root folder with theses attributes:

{
    "name": "Task API documentation",
    "version": "1.0.0",
    "description": "API task list manager",
    "template": {
        "forceLanguage": "en"
    }
}

The template.forceLanguage disables the browser language detection, in this case it will force the english language.

Now, let’s start the documentation process, by editing the index.js, route by route, the first will be the app.get('/tasks') function, and in this route we’re gonna use the following params:

  • @api: http method, the path address and the route’s title;
  • @apiGroup: route group name;
  • @apiSuccess: describes the fields and their data types for a successful response;
  • @apiSuccessExample: shows an output sample about a successful response.
  • @apiErrorExample: shows an output sample about a failed response.

Have a look:

/**
 * @api {get} /tasks List all tasks
 * @apiGroup Tasks
 * @apiSuccess {Object[]} tasks Task's list
 * @apiSuccess {Number} tasks.id Task id
 * @apiSuccess {String} tasks.title Task title
 * @apiSuccess {Boolean} tasks.done Task is done?
 * @apiSuccess {Date} tasks.updated_at Update's date
 * @apiSuccess {Date} tasks.created_at Register's date
 * @apiSuccessExample {json} Success
 *    HTTP/1.1 200 OK
 *    [{
 *      "id": 1,
 *      "title": "Study",
 *      "done": false
 *      "updated_at": "2016-02-10T15:46:51.778Z",
 *      "created_at": "2016-02-10T15:46:51.778Z"
 *    }]
 * @apiErrorExample {json} List error
 *    HTTP/1.1 500 Internal Server Error
 */
app.get('/tasks', function(req, res) {  
    // business logic for listing all tasks...
});

The next route, the app.get('/tasks/:id'), will use all params from the previous one and one more param:

  • @apiParam: describes the fields and their data types for a path parameter;
/**
 * @api {get} /tasks/:id Find a task
 * @apiGroup Tasks
 * @apiParam {id} id Task id
 * @apiSuccess {Number} id Task id
 * @apiSuccess {String} title Task title
 * @apiSuccess {Boolean} done Task is done?
 * @apiSuccess {Date} updated_at Update's date
 * @apiSuccess {Date} created_at Register's date
 * @apiSuccessExample {json} Success
 *    HTTP/1.1 200 OK
 *    {
 *      "id": 1,
 *      "title": "Study",
 *      "done": false
 *      "updated_at": "2016-02-10T15:46:51.778Z",
 *      "created_at": "2016-02-10T15:46:51.778Z"
 *    }
 * @apiErrorExample {json} Task not found
 *    HTTP/1.1 404 Not Found
 * @apiErrorExample {json} Find error
 *    HTTP/1.1 500 Internal Server Error
 */
app.get('/tasks/:id', function(req, res) {  
    // business logic for finding a task...
});

In the app.post('/tasks'), it will be used the @apiParam and @apiParamExample, to explain how to send data via a body request, and the @apiSuccess {Boolean} done=false Task is done? is different from the others, because the done=false means default values for a field, in this case the done field.

/**
 * @api {post} /tasks Register a new task
 * @apiGroup Tasks
 * @apiParam {String} title Task title
 * @apiParamExample {json} Input
 *    {
 *      "title": "Study"
 *    }
 * @apiSuccess {Number} id Task id
 * @apiSuccess {String} title Task title
 * @apiSuccess {Boolean} done=false Task is done?
 * @apiSuccess {Date} updated_at Update date
 * @apiSuccess {Date} created_at Register date
 * @apiSuccessExample {json} Success
 *    HTTP/1.1 200 OK
 *    {
 *      "id": 1,
 *      "title": "Study",
 *      "done": false,
 *      "updated_at": "2016-02-10T15:46:51.778Z",
 *      "created_at": "2016-02-10T15:46:51.778Z"
 *    }
 * @apiErrorExample {json} Register error
 *    HTTP/1.1 500 Internal Server Error
 */
app.post('/tasks', function(req, res) {  
    // business logic for creating a task...
});

To write the app.put('/tasks/:id') and app.delete('/tasks/:id') route’s documentation, there is no secret and no new params to explain, so take a look how both will be written:

/**
 * @api {put} /tasks/:id Update a task
 * @apiGroup Tasks
 * @apiParam {id} id Task id
 * @apiParam {String} title Task title
 * @apiParam {Boolean} done Task is done?
 * @apiParamExample {json} Input
 *    {
 *      "title": "Work",
 *      "done": true
 *    }
 * @apiSuccessExample {json} Success
 *    HTTP/1.1 204 No Content
 * @apiErrorExample {json} Update error
 *    HTTP/1.1 500 Internal Server Error
 */
app.put('/tasks/:id', function(req, res) {  
    // business logic for update a task
});

/**
 * @api {delete} /tasks/:id Remove a task
 * @apiGroup Tasks
 * @apiParam {id} id Task id
 * @apiSuccessExample {json} Success
 *    HTTP/1.1 204 No Content
 * @apiErrorExample {json} Delete error
 *    HTTP/1.1 500 Internal Server Error
 */
app.delete('/tasks/:id', function(req, res) {  
    // business logic for deleting a task
});

Let’s generate the docs! To do it, just run these commands below:

apidoc - e "(node_modules|public)" - o public / apidoc  
node index.js  

The apidoc command will filter for all files, except from the folders node_modules and public, because it was using the flag -e "(node_modules|public)" to ignore these directories, and all generated files will be into public/apidoc folder. After running these commands you will be able to access the address: http://localhost:3000/apidoc.

This time, we have a complete documentation page that describes step-by-step how to create a client application to consume data from the API. Take a look at the image below:

api-documented

Conclusion

Congratulations! Now you have a well-documented API and this will allow other developers to create client-side applications using the API through the rules of the API documentation.