Asciidoctor for documenting REST APIs

Since more than 30 years I have to write good documentation. On the university I used to write documents with TeX and LaTeX because of the high quality output. The more professional the output is, the more professional the content looks to the reader. LaTeX offers a lot of possibilities to style your document so the reading can be fun. But LaTeX isn’t that simple. In my current projects I searched for a documentation tool that is wide spread, free and simple. The answer was Asciidoctor for me.

Document structure

The task is to write an API documentation and produce a typical HTML and PDF document as the output. This kind of documentation should be well structured because it is not meant to read at once but to give detail information when the reader needs it.

Document header

At the beginning of the document you should place general information about the document like

  • title
  • author
  • date of creation

Here is my default header snippet:

= API documentation for ...
Bernd Kursawe <bernd.kursawe@praxisit.de>

:toc:
:sectanchors:
:sectnums:

After the title („API documentation for …“) the author information follows (Name <e-mail>). With „:toc:“ you place a table-of-content at the beginning of the document. „:sectanchors:“ sets anchors at every section and „:sectnums:“ care about the numeration of the sections.

Introduction

The reader should get an idea at the beginning of the document what it’s all about. Explain keywords and the goal of the API. This should be the first section. In Asciidoctor the only thing you need to do is define a new section on the top level (2 =):

== Introduction

This API is ...

A person is a human beeing with a first and a last name...

Standards

Defining standards is important for the whole documentation. According to the DRY1 principle you should define common uses of HTTP verbs and response codes at the beginning of the document. Here is an example:

HTTP verbs have the following default meanings:

.HTTP verbs
[width="80%",options="header",cols="1,4"]
|=======
|Verb   |Default usage of HTTP verb
|GET    |Get some information. Select resources by path variables. Query parameters can be used
         for multiple search parameters. Sending a request body is not allowed.
|POST   |Create new resources or start jobs (not idempotent!). The data for the new resource is send
         in the request body. The new resource or some information about the job are given back in
         the response body. Don't use query parameters.
|PUT    |Change a resource. To select the resource use path variables. The complete resource data
         has to be send in the request body and the changed data is returned in the response body.
         Don't use query parameters.
|DELETE |Delete a resource. The resource is identified by its id as a path variable. There should be
         no request body or any query parameters. A response body is not required.
|=======

Here are the meanings of most used HTTP response codes:

.Default response codes
[width="80%",options="header",cols="1,2,6,3"]
|=======
|Code   |Meaning    |Description                            |Used with
|200    |OK         |Response code if everything went OK.   |GET, PUT, DELETE
|201    |CREATED    |Successful created a new resource      |CREATED
|404    |NOT FOUND  |The resource(s) is/are not found.      |GET, PUT, DELETE
|=======

Group endpoints

Try to group endpoints (RequestMappings) of the same resource. Use the resource as a section and describe the raison d’être of the resource. Every endpoint should be described in its own subsection. List all endpoints in the same order (e.g. GET, POST, PUT, DELETE). Here is a simple example:

=== Person

Administers information about people.

==== Find all people

include::{snippets}/person/find-all-people/method-path.adoc[]

include::{snippets}/person/find-all-people/description.adoc[]

===== Response fields

The response body contains a list of <<person-data>>.

===== Example request / response

.Request
include::{snippets}/person/find-all-people/http-request.adoc[]

.Response
include::{snippets}/person/find-all-people/http-response.adoc[]

==== Find a single person

include::{snippets}/person/find-one-person/method-path.adoc[]

include::{snippets}/person/find-one-person/description.adoc[]

===== Path parameters

include::{snippets}/person/find-one-person/path-parameters.adoc[]

===== Response fields

The response body contains <<person-data>>.

===== Example request / response

.Request
include::{snippets}/person/find-one-person/http-request.adoc[]

.Response
include::{snippets}/person/find-one-person/http-response.adoc[]

===== Wrong id

.Request
include::{snippets}/person/find-one-person-with-wrong-id/http-request.adoc[]

.Response
include::{snippets}/person/find-one-person-with-wrong-id/http-response.adoc[]

==== Create a new person

include::{snippets}/person/create-new-person/method-path.adoc[]

include::{snippets}/person/create-new-person/description.adoc[]

===== Request fields

include::{snippets}/person/create-new-person/request-fields.adoc[]

===== Response fields

The response body contains <<person-data>>.

===== Example request / response

.Request
include::{snippets}/person/create-new-person/http-request.adoc[]

.Response
include::{snippets}/person/create-new-person/http-response.adoc[]

==== Delete a person

include::{snippets}/person/delete-person/method-path.adoc[]

include::{snippets}/person/delete-person/description.adoc[]

===== Path parameters

include::{snippets}/person/delete-person/path-parameters.adoc[]

===== Response fields

include::{snippets}/person/delete-person/response-fields.adoc[]

===== Example request / response

.Request
include::{snippets}/person/delete-person/http-request.adoc[]

.Response
include::{snippets}/person/delete-person/http-response.adoc[]

===== Wrong id

.Request
include::{snippets}/person/delete-person-with-wrong-id/http-request.adoc[]

.Response
include::{snippets}/person/delete-person-with-wrong-id/http-response.adoc[]

Data structures

Often you use the same resource (data structure) in several endpoints. This is boring for the reader and the document size becomes longer as necessary. So add a separate section at the end. Describe all common data structures like this:

== Data types

[[person-data]]
=== Person

include::{snippets}/person/find-one-person/response-fields.adoc[]

Alternatives

Are there any alternatives to Asciidoctor? Of course, but here are some and the reasons I don’t use them:

  • Microsoft Word / LibreOffice: Although WYSIWYG-editors has a lot of advantages it has drawbacks producing HTML output. In HTML everything looks a little bit different to the layout in the editor and this is at least misleading.
    Another problem is the document format. It is not simple text but (proprietary) XML. When you want to use a versioning system like GIT it is difficult to see the differences between 2 versions in a structured format like XML.
  • LaTeX: Complicated to learn and slow processing. It offers amazing possibilities to format and style your document in PDF. But it has no advantages when used to produce HTML.
  • Markdown: A ‚cousin‘ of Asciidoctor with lesser functionality but also usable for API documentation. It were my second choice.
  1. Don’t repeat yourself

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.