This page looks best with JavaScript enabled

Apidoc: Practice and Automated Generation

 ·  ☕ 3 min read

In a frontend/backend separated architecture, API documentation changes hands frequently. When third-party interfaces are involved, API and documentation changes can be even faster in multi-party collaboration scenarios. To make maintaining the API and handing over documentation easier, here is a documentation generation tool worth recommending — apidoc.

1. Introduction to apidoc

apidoc is an API documentation generation tool built on nodejs. It extracts content in a specific format from code comments and generates API documentation.

The languages supported so far are: C#, C/C++, D, Erlang, Go, Groovy, Java, Javascript, Pascal/Delphi, Perl, PHP, Python, Rust, Ruby, Scala, and Swift.

Features:

  • Cross-platform: linux, windows, macOS, and more are all supported.
  • Broad language support.
  • Support for document version management.
  • Support for generating a single document from multiple projects in different languages.
  • Customizable output templates.

2. An Example

Taking Django as an example, write comments in the views function.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
def myview(request, id):
    """
    @api {GET} ci/app/:id/ 获取应用详情
    @apiVersion 1.0.0
    @apiDescription 获取应用详情
    @apiName getAppDetail
    @apiGroup Application
    @apiSuccessExample  请求成功
    HTTP/1.1 200 OK
    {
        "result": true,
        "data": [],
        "code": 0,
        "message": u'成功说明'
    }
    @apiSuccess {Boolean} result true
    @apiSuccess {Array} data  数据列表
    @apiSuccess {Number} code 0
    @apiSuccess {String} message 说明
    """

The generated API documentation

3. Keywords Supported by apidoc

apidoc generates API documentation by extracting code comments and following keyword syntax. So if you want to generate an ideal API document, you must follow apidoc’s keyword syntax. Below is a list of the keyword syntax, where { } denotes a variable to be replaced and [ ] denotes an optional parameter:

  • @api {method} path [title].
    Only comment blocks annotated with @api are parsed and turned into documentation; title is parsed as a sub-menu under the navigation menu (@apiGroup)
    method may contain spaces, such as {POST GET}
  • @apiGroup name.
    The group name, parsed as a navigation bar menu
  • @apiName name.
    The interface name. Within the same @apiGroup, @api entries with the same name are distinguished by @apiVersion; otherwise the later @api overrides the earlier one
  • @apiDescription text.
    The interface description, which supports html syntax
  • @apiVersion verison.
    The interface version, in the form major.minor.patch
  • @apiIgnore [hint].
    apidoc ignores interfaces annotated with @apiIgnore; hint is the description
  • @apiSampleRequest url.
    The interface test address for testing; when sending a request, the @api method must be one of POST/GET etc.
  • @apiDefine name [title] [description].
    Defines a comment block (which does not contain @api); combined with @apiUse it can be included elsewhere
    @apiUse cannot be used inside an @apiDefine
  • @apiUse name.
    Includes a comment block defined by @apiDefine
  • @apiParam [(group)] [{type}] [field=defaultValue] [description]. Request parameter
  • @apiHeader [(group)] [{type}] [field=defaultValue] [description]. Header parameter
  • @apiError [(group)] [{type}] field [description]. Parameter on an error response
  • @apiSuccess [(group)] [{type}] field [description]. Parameter on success,
    where group denotes the grouping of the parameter, type denotes the type (no spaces allowed), and input parameters may define a default value (no spaces allowed)
  • @apiParamExample [{type}] [title] example. Example parameter request
  • @apiHeaderExample [{type}] [title] example. Example header request
  • @apiErrorExample [{type}] [title] example. Example error request
  • @apiSuccessExample [{type}] [title] example. Example success request
    where type denotes the language type of the example; the example content is rendered directly.
  • @apiPermission name.
    name must be unique; describes the access permission of the @api, such as admin/anyone

4. Installing and Configuring apidoc

  • Install apidoc

This assumes nodejs is already installed; if not, download and install it yourself. Install apidoc globally:

1
npm  install apidoc -g
  • Configuration

Configuration is optional; without it document generation still works, you just lose some API documentation information. In the project root directory, create an apidoc.json file to configure the basic document information:

1
2
3
4
5
6
{
  "name": "项目API文档",
  "version": "1.0.0",
  "description": "项目API文档-说明",
  "title": "项目API文档-title"
}
  • Generate the document

In the directory where apidoc.json lives, run the command:

1
apidoc -i ./

The -i parameter denotes the input directory; by default the generated document goes to /doc under the current directory, and you can also specify it with the -o parameter.

5. Automated apidoc Generation

Every time you update the code comments, you have to run apidoc -i ./ once to see the API documentation, which is a bit tedious. Could the API documentation be generated automatically every time the comments change? Of course it can.

  • Install gaze

gaze is a nodejs-based file watching project.

1
npm  install gaze -g
  • Write the watch script

apidoc-watch.js

 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
var gaze = require("gaze");
var exec = require("child_process").exec;

function Watch() {
  gaze("./*.*", function (error, watcher) {
    this.on("all", function (event, filepath) {
      console.log(filepath + " was " + event);
      Geneartion();
    });
  });
}

function Geneartion() {
  var msg = exec("apidoc -e ./node_modules/");
  msg.stdout.on("data", function (data) {
    console.log("生成Api->" + data);
  });

  msg.stderr.on("data", function (data) {
    console.log("生成出错->" + data);
  });
}

Geneartion();
Watch();
console.log("正在监听......");
  • Run the watcher

You only need to run the command once, before updating the comments.

1
node apidoc-watch.js

6. Practical Advice for Projects

6.1 How to Hand Over the Documentation

It is recommended to generate the API documentation into the frontend’s static directory and hand it over as a link, for example: htttp://example.com/static/doc/index.html.

6.2 How to Organize Backend Comments

Taking Django as an example, since apidoc needs a fairly large number of comments, there are two options:

  • One is to write the comments directly inside each views function,
  • The other is to use a separate file or folder for the comments.

In django, skinny controller, fat model is recommended — write less code and more comments in views.py — so the first option is the better fit. At the same time, keeping comments and the interface implementation together lowers the learning cost of subsequent maintenance.

6.3 Using apiDefine and apiUse

Because frontend and backend often agree on a fixed response format, you can define the fixed-format part as a comment block with apiDefine and reference it elsewhere with apiUse. This effectively reduces the number of comments.

Defining a comment block — note that a comment block should be defined as a separate comment:

 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
"""
@apiDefine success
@apiSuccessExample  请求成功
HTTP/1.1 200 OK
{
    "result": true,
    "data": [],
    "code": 0,
    "message": u'成功说明'
}
@apiSuccess {Boolean} result true
@apiSuccess {Array} data  数据列表
@apiSuccess {Number} code 0
@apiSuccess {String} message 说明
"""

"""
@apiDefine error
@apiErrorExample  请求失败
HTTP/1.1 40X  error
{
    "result": false,
    "data": [],
    "code": -1,
    "message": u'失败提示'
}
@apiError {Boolean} result false
@apiError {Array} data  空
@apiError {Number} code  错误码
@apiError {String} message 提示
"""

Referenced elsewhere

1
2
3
4
5
6
7
8
9
def myview(request, id):
    """
    @api {GET} ci/app/:id/ 获取应用详情
    @apiVersion 1.0.0
    @apiDescription 获取应用详情
    @apiName getAppDetail
    @apiGroup Application
    @apiUse success
    """

6.4 Use apiGroup Grouping Sensibly

apiGroup and apiName get concatenated into the URL, for example: static/doc/index.html#api-apiGroup-apiName. Giving apiGroup and apiName easily understood names matters. Using apiGroup sensibly, gathering related frontend functionality together, helps the frontend understand the purpose of an API.

7. References


微信公众号
WRITTEN BY
微信公众号