Automatic Swagger Docs Generation

Motivation

If you are developing a REST API to support websites or mobile applications, there comes a time when you have to communicate the signatures, and authorization schemes of new or existing endpoints to inform frontend development. Instead of communicating imprecisely with prose, it ia much better to communicate with structured documentation. It is even better if that structured documentation can be automatically generated and shared. Here we show you how we implemented automatic Swagger API documentation for our server project.

Frontend Integration

Giving the frontend team access to Swagger docs provides a clear reference for them to develop their client code and utilize the server resources. Also once you have Swagger documentation (json or yaml) it is possible to use it to generate client-side code That way you can load swagger.yaml as a git submodule, or add it to your frontend continous integration to always stay in sync with new updates in the REST API, and avoid writing boilerplate code.

QA

Testers can load automatic swagger documentation (json or yaml) into their REST API testing clients, like Postman or Insomnia. They can stay up to date with the latest changes, and know exactly the request parameters and expected responses. This can help backend engineers test their changes when they hit the pre-production environment, as well QA Engineers help in validation, and checking edge cases.

Third-party Integrations

If you plan on licensing access to your REST API, automatic Swagger documentation can help communicate with business partners exactly how to use and test your endpoints, and build using your API.

Implementation

Server code

We have a REST API defined in JAX-RS in our server. We didn’t want to have to write new Swagger annotations for every API defintion existing on our code base. So we looked for a Maven plugin that is able to interpret the JAX-RS annotations and produce the Swagger documentation. We found this one which was perfect for our use case.

We added the plugin to our pom.xml

    <build>
        ...
        <plugins>
            <plugin>
                <groupId>com.github.kongchen</groupId>
                <artifactId>swagger-maven-plugin</artifactId>
                <version>3.1.8</version>
                <configuration>
                    <apiSources>
                        <apiSource>
                            <info>
                                <title>Hologram Backend APIs</title>
                                <version>v1</version>
                                <description>

                                </description>
                                <contact>
                                    <email>${enginering_email}</email>
                                    <name>${engineering_team_name}</name>
                                </contact>
                                <license>
                                    <url>http://www.apache.org/licenses/LICENSE-2.0.html</url>
                                    <name>Apache 2.0</name>
                                </license>
                            </info>
                            <springmvc>false</springmvc>
                            <locations>
                                <location>${jax_rs_package}</location>
                            </locations>
                            <outputFormats>
                                yaml
                            </outputFormats>
                            <securityDefinitions>
                                <securityDefinition>
                                    <type>apiKey</type>
                                    <name>Authorization</name>
                                    <in>header</in>
                                </securityDefinition>
                            </securityDefinitions>

                            <swaggerApiReader>com.github.kongchen.swagger.docgen.reader.SwaggerReader</swaggerApiReader>
                            <operationIdFormat></operationIdFormat>
                            <swaggerDirectory>
                                ${basedir}/target/swagger
                            </swaggerDirectory>
                            <schemes>
                                <scheme>https</scheme>
                            </schemes>
                            <host>${pre_production_host}</host>
                            <basePath>/</basePath>
                        </apiSource>
                    </apiSources>
                    <skipSwaggerGeneration>${swagger.skip}</skipSwaggerGeneration>
                </configuration>
                <executions>
                    <execution>
                        <phase>compile</phase>
                        <goals>
                            <goal>generate</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            ...
        </plugins>
        ...
    </build>

Make sure to use a good value for operationIdFormat like

{{className}}_{{methodName}}_{{httpMethod}}

Then we added some annotations to group endpoints under tags:

  ...
  @Api(value = "/customers", description = "CRUD APIs for Customers", authorizations = {@Authorization(HttpHeaders.AUTHORIZATION)})
  ...

Finally when we compile our code, we Swagger maven plugin handles the rest and produces a file in target/swagger/swagger.yaml

mvn -Dswagger.skip=false compile

Script to update swagger documentation

Finally approach we took was to run a script as part of our continuous deployment workflow in CircleCI, which committed the swagger.yaml file to another repository to be read and loaded by other members of the organization.

#!/bin/sh

WD=`pwd`

if [ -d "${WD}/${DOCS_REPO_NAME}" ]; then
  echo "Directory to repo exists already, cd'ing"
  cd ${DOCS_REPO_NAME} || exit 1
  git checkout master || exit 1
  echo "Pulling in latest master"
  git pull || exit 1
  echo "Done pulling"
else
  echo "Directory to repo does not exist, cloning and cd'ing"
  git clone git@github.com:${GITHUB_ORG}/${DOCS_REPO_NAME}.git || exit 1
  cd ${DOCS_REPO_NAME} || exit 1
  echo "Done cloning"
fi

cp ../target/swagger/swagger.yaml swagger/swagger.yaml

changes=$(git status --porcelain | wc -l)

if [ "${changes}" -gt "0" ]; then
  echo "Some local changes, pushing to the remote"
  git config --global user.email "${ENG_EMAIL}"
  git config --global user.name "CircleCI Job"

  git add swagger/

  if [ -z "${CIRCLE_TAG}" ]; then
    release_note="Swagger updates for ${CIRCLE_BRANCH}"
    git commit -m "$release_note"
    git push origin master 2>&1 | grep -v 'To https'
  else
    release_note="Swagger updates for ${CIRCLE_TAG}"
    git commit -m "$release_note"
    git tag ${CIRCLE_TAG}
    git push --atomic origin master ${CIRCLE_TAG} 2>&1 | grep -v 'To https'
  fi
else
  echo "No local changes, so not pushing to the remote"
fi

We run this script whenever new code updates are pushed to our pre-production environment, and everyone is kept in sync with the latest changes.