When you are developing an API and one or more developers (frontend, desktop, mobile, etc) will have to integrate their code with it, it’s very important to have it well documented so they know what and how they can use.

For that, in Node.js projects I’ve been using apiDoc, as it is able to generate documentation in HTML from annotations in the source code.

For this post, I’ll use the TODO List API I developed as an example, once more. So you can clone or download it from here.

Routes and annotations

On my post about tests with mocha and code coverage with istanbul, I showed the example of the Task endpoints in our TODO List API:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import Task from " ../controllers/tasks " ; export = ( app ) => { const endpoint = process . env . API_BASE + " tasks " ; app . post ( endpoint , Task . create ); app . delete ( endpoint + " /:id " , Task . delete ); app . get ( endpoint + " /:id " , Task . getOne ); app . get ( endpoint , Task . getAll ); app . put ( endpoint + " /:id " , Task . update ); };

This represents all our endpoints related to tasks in the system. How can we use them? What should the developers that will consume the API send to each of these endpoints?

So far there is no way for them to know other than looking at the code, which shouldn’t be needed.

Because of apiDoc, we can achieve that with annotations. The way I configure it, is by writing them before each endpoint configured in the files inside the routes directory. If you are not sure of what I’m talking about, check here when I mentioned how I configure and organize my Node.js projects.

With annotations, our Task endpoints (inside routes/tasks.ts) would look like this:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 import Task from " ../controllers/tasks " ; export = ( app ) => { const endpoint = process . env . API_BASE + " tasks " ; /** * @api {post} /api/v1/tasks Create a task * @apiVersion 1.0.0 * @apiName Create * @apiGroup Task * @apiPermission authenticated user * * @apiParam (Request body) {String} name The task name * * @apiExample {js} Example usage: * const data = { * "name": "Do the dishes" * } * * $http.defaults.headers.common["Authorization"] = token; * $http.post(url, data) * .success((res, status) => doSomethingHere()) * .error((err, status) => doSomethingHere()); * * @apiSuccess (Success 201) {String} message Task saved successfully! * @apiSuccess (Success 201) {String} id The campaign id * * @apiSuccessExample {json} Success response: * HTTPS 201 OK * { * "message": "Task saved successfully!", * "id": "57e903941ca43a5f0805ba5a" * } * * @apiUse UnauthorizedError */ app . post ( endpoint , Task . create ); /** * @api {delete} /api/v1/tasks/:id Delete a task * @apiVersion 1.0.0 * @apiName Delete * @apiGroup Task * @apiPermission authenticated user * * @apiParam {String} id The task id * * @apiExample {js} Example usage: * $http.defaults.headers.common["Authorization"] = token; * $http.delete(url) * .success((res, status) => doSomethingHere()) * .error((err, status) => doSomethingHere()); * * @apiSuccess {String} message Task deleted successfully! * * @apiSuccessExample {json} Success response: * HTTPS 200 OK * { * "message": "Task deleted successfully!" * } * * @apiUse UnauthorizedError */ app . delete ( endpoint + " /:id " , Task . delete ); /** * @api {get} /api/v1/tasks/:id Retrieve a task * @apiVersion 1.0.0 * @apiName GetOne * @apiGroup Task * @apiPermission authenticated user * * @apiParam {String} id The task id * * @apiExample {js} Example usage: * $http.defaults.headers.common["Authorization"] = token; * $http.get(url) * .success((res, status) => doSomethingHere()) * .error((err, status) => doSomethingHere()); * * @apiSuccess {String} _id The task id * @apiSuccess {String} name The task name * * @apiSuccessExample {json} Success response: * HTTPS 200 OK * { * "_id": "57e8e94ea06a0c473bac50cc", * "name": "Do the disehs", * "__v": 0 * } * * @apiUse UnauthorizedError */ app . get ( endpoint + " /:id " , Task . getOne ); /** * @api {get} /api/v1/tasks Retrieve all tasks * @apiVersion 1.0.0 * @apiName GetAll * @apiGroup Task * @apiPermission authenticated user * * @apiExample {js} Example usage: * $http.defaults.headers.common["Authorization"] = token; * $http.get(url) * .success((res, status) => doSomethingHere()) * .error((err, status) => doSomethingHere()); * * @apiSuccess {String} _id The task id * @apiSuccess {String} name The task name * * @apiSuccessExample {json} Success response: * HTTPS 200 OK * [{ * "_id": "57e8e94ea06a0c473bac50cc", * "name": "Do the disehs" * }, * { * "_id": "57e903941ca43a5f0805ba5a", * "name": "Take out the trash" * }] * * @apiUse UnauthorizedError */ app . get ( endpoint , Task . getAll ); /** * @api {put} /api/v1/tasks/:id Update a task * @apiVersion 1.0.0 * @apiName Update * @apiGroup Task * @apiPermission authenticated user * * @apiParam {String} id The task id * * @apiParam (Request body) {String} name The task name * * @apiExample {js} Example usage: * const data = { * "name": "Run in the park" * } * * $http.defaults.headers.common["Authorization"] = token; * $http.put(url, data) * .success((res, status) => doSomethingHere()) * .error((err, status) => doSomethingHere()); * * @apiSuccess {String} message Task updated successfully! * * @apiSuccessExample {json} Success response: * HTTPS 200 OK * { * "message": "Task updated successfully!" * } * * @apiUse UnauthorizedError */ app . put ( endpoint + " /:id " , Task . update ); };

As you can see, we have the HTTP method type (post, put, get, delete), the endpoint address, the api version, the kind of permission it needs, the params it requires, the response and the error if the user is not authorized.

In the official website you can check the documentation and available params for the annotations.

Where is this UnauthorizedError coming from?

apiDoc settings

There are some settings that can be done for apiDoc and this UnauthorizedError is the one I usually implement.

Create a file called _apidoc.js inside the routes directory with the following content:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 // ------------------------------------------------------------------------------------------ // General apiDoc documentation blocks and old history blocks. // ------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------ // Current Success. // ------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------ // Current Errors. // ------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------ // Current Permissions. // ------------------------------------------------------------------------------------------ /** * @apiDefine UnauthorizedError * @apiVersion 1.0.0 * * @apiError Unauthorized Only authenticated users can access the endpoint. * * @apiErrorExample Unauthorized response: * HTTP 401 Unauthorized * { * "message": "Invalid credentials" * } */ // ------------------------------------------------------------------------------------------ // History. // ------------------------------------------------------------------------------------------

I also create another file, also inside the routes directory, called apidoc.json

This file contains the following content (example):

1 2 3 4 5 6 7 { " name " : " Test API - This is my API " , " version " : " 1.0.0 " , " description " : " This is my very powerful API " , " title " : " Test API - This is my API " , " url " : " https://testapi.com " }

These two files will be used by apiDoc when generating the documentation.

Generating the documentation

After writing annotations for every endpoint and configuring our project, we need to configure a task to generate the documentation.

The way I do that is using gulp. Install the required dependencies:

npm i gulp gulp-apidoc --save-dev

Then create a file called gulpfile.js in the project’s root directory. If it’s there already, just add the parts related to apiDoc to it:

1 2 3 4 5 6 7 8 9 10 11 12 13 const gulp = require ( " gulp " ); const apidoc = require ( " gulp-apidoc " ); gulp . task ( " apidoc " , ( done ) => { apidoc ({ src : " ./routes " , dest : " ../docs/apidoc " }, done ); }); gulp . task ( " watch " , () => { gulp . watch ([ " ./routes/** " ], [ " apidoc " ]); });

You can change the “dest” there to another directory that suits you better. That’s where you want the output to be generated to.

Now all you need to do is to run:

gulp apidoc

After that, you just need to open the index.html file inside the directory you configured in the “dest” above and you’ll see something like this (Click on the image below to enlarge):

So the other developers can generate the same with gulp as well or you can even serve this generated directory via Nginx, for example.

Conclusion

In this post we saw how to document our APIs with annotations and generate the HTML for them using apiDoc.

Do you use another software for documenting your APIs or do you use apiDoc in a different way? Leave a comment below!