Automatically Generate a Nice Looking Serverless REST API Documentation

In the past years, technology made huge progress and automating your processes is more important than ever. Documenting your REST APIs is not an exception here. It’s even more important in the fast moving serverless world to automate your serverless REST API documentation to always keep it up-to-date. The OpenAPI (Swagger) standard helps you describing your REST APIs in a consistent and machine-readable format. This blog post describes the basic steps and explains how you can generate a nice looking serverless REST API documentation!

The goal of this blog post is to build an automated process for creating and describing your serverless REST API. We’ll use an OpenAPI document for describing your endpoints and then generate a nice looking documentation out of it. All examples are based on my most recent side project saas-marketplaces.com where I’ve used the same approach. You can checkout the results under dev.saas-marketplaces.com.

This blog post is using the Serverless Application Model (SAM) by AWS to describe the serverless functions on AWS Lambda. There are similar approaches available for other frameworks, e.g. using the Serverless framework.

Creating A Serverless REST API

To start off, we’ll define a new serverless function using SAM. The function is using the OpenAPI 3.0 specification for the API definition. It’s returning a list of SaaS Marketplaces:

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  A simple serverless function using OpenAPI spec.

Resources:
  # An API definition for multiple serverless functions
  DefaultApi:
    Type: AWS::Serverless::Api
    Properties:
      StageName: Prod
      DefinitionBody:
        'Fn::Transform':
          Name: 'AWS::Include'
          Parameters:
            Location: !Sub s3://${TemplateBucket}/spec/backend-api-spec.yaml

  GetMarketplacesFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: dist/
      Handler: index.handler
      Runtime: nodejs12.x
      Events:
        GetMarketplaces:
          Type: Api
          Properties:
            RestApiId: !Ref DefaultApi
            Path: /api/1/marketplaces
            Method: GET

There are two important definitions here. One is the DefaultApi resource which references a backend-api-spec.yaml in its DefinitionBody. The backend-api-spec.yaml file is used to describe the REST API and also to generate the documentation. The other important definition is the line where we reference DefaultApi in the serverless function using RestApiId: !Ref DefaultApi. These are the main ingredients for a serverless REST API and its documentation using OpenAPI (Swagger). If you have questions about the other elements, I advise you to read through the comprehensive SAM specification.

Describe Your Serverless REST API Using OpenAPI

The next step is to write our OpenAPI document backend-api-spec.yaml. Such a document consists of some meta information (under info), the different endpoints identified by their path (under paths) and other definitions like a response model (under components). We’ll focus on the paths definitions for now.

openapi: "3.0.1"
info:
  version: "1"
  title: "Serverless REST API Documentation"

paths:
  /api/1/marketplaces:
    get:
      summary: "Get all marketplaces"
      operationId: getMarketplaces
      responses:
        200:
          description: Successful response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Marketplaces"
      x-amazon-apigateway-integration:
        responses:
          default:
            statusCode: 200
        uri:
          Fn::Sub: "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${GetMarketplacesFunction.Arn}/invocations"
        passthroughBehavior: when_no_match
        httpMethod: POST
        type: aws_proxy

Multiple lines are important here. First, you define some meta information in the beginning of the file. Then, you define all endpoints that your REST API supports. They are denoted by a path (e.g. /api/1/marketplaces/) and a method (e.g. get). Each path + method combination is considered as an endpoint that you can describe further. For example, you can provide meta information like a summary but also the different responses a client can expect. In this case we’re referencing a schema describing the JSON response data. I’m skipping the response model here as this is not the most interesting point.

The OpenApi spec also allows adding extensions denoted by x-. These are custom extensions for any kind of provider for your REST API schema. Now, you want to create a REST API with AWS Api Gateway and hence, you need to check what kind of extensions the Api Gateway supports. For example, x-amazon-apigateway-integration defines what should happen when a request comes in to the Api Gateway for a GET /api/1/marketplaces request. In this case it’ll forward the request to a Lambda function referenced by the ARN. Here, the type property defines the type of integration, in this case aws_proxy which forwards the complete HTTP event to the Lambda function.

Deploy Your Stack

The first part is ready. Assuming you have written some code for the Lambda function, you can now deploy it to AWS. The following steps are required:

  1. Upload the backend-api-spec.yaml file to an S3 bucket.
  2. Package the SAM template using aws cloudformation package or sam package if you have SAM CLI installed.
  3. Deploy a stack using the SAM template and aws cloudformation deploy or sam deploy.

Afterwards, you can access your REST API under the url <YourApiGatewayUrl>/api/1/marketplaces.

Generate a Nice Looking Serverless REST API Documentation

Until now, you haven’t generated a nice looking serverless REST API documentation. And as you might have recognized, you shouldn’t use your current OpenAPI document for it. The reason is that it still includes internal information like Lambda function ARNs. Instead, we need to clean the documents content first.

Prepare The OpenAPI Document

I have written a custom Node.js script for my own purposes. It reads the backend-api-spec.yaml, removes the internal data and generates a new file backend-api.yaml. The generated file will serve as our public file which can be published on the internet or used for generating documentation (see next section). You can start with a simplified version of my script and extend it to your own needs:

const fs = require('fs');
const jsYaml = require('js-yaml');

// Define the path to the backend-api-spec.yaml and the target file backend-api.yaml
const inputSpec = backend-api-spec.yaml;
const targetSpec = backend-api.yaml;

// Load OpenAPI document
const spec = jsYaml.safeLoad(fs.readFileSync(inputSpec, 'utf8'));

// remove all Api Gateway extensions to not reveal the internal AWS details
function removeApiGatewayExtensions(spec) {
    Object.keys(spec).forEach(property => {
        if (property.indexOf('x-amazon-apigateway') > -1) {
            delete spec[property];
        } else if (typeof spec[property] == "object") {
            removeApiGatewayExtensions(spec[property]);
        }
    });
}

removeApiGatewayExtensions(spec);

// Potential extension: Adjust more things in the OpenAPI document, e.g. base url

// After correcting all data, write it back to target file
fs.writeFileSync(targetSpec, jsYaml.safeDump(spec));

Generate Documentation With Different Tools

Now you can use the output file and generate a nice looking serverless REST API documentation. There are various tools for this use case, like openapi-generator, swagger-codegen, or redoc. (Note: openapi-generator and swagger-codegen are pretty similar and I can recommend reading the FAQ about their difference) redoc is a special case here because it only focuses on generating documentation whereas the other two can also generate server and client code. Generating client code is a really cool feature if you’re providing a (public) API and don’t want to (or can’t?) write the code by yourself. Here are the steps to generate the documentation using each of the three tools.

openapi-generator

# Install it on your machine as a CLI command, e.g. on MacOS using Homebrew:
brew install openapi-generator

# Generates the documentation at openapi-generator-docs/index.html
openapi-generator generate -i backend-api.yaml -g html2 -o openapi-generator-docs
A nice looking serverless REST API documentation using openapi-generator
Resulting HTML document using openapi-generator

swagger-codegen

# Install it on your machine as a CLI command, e.g. on MacOS using Homebrew:
brew install swagger-codegen

# Generates the documentation at swagger-codegen-docs/index.html
swagger-codegen generate -i backend-api.html -l html2 -o swagger-codegen-docs

Note that the parameter for specifying the generator template is different between openapi-generator and swagger-codegen. openapi-generator requires -g (for generator) and swagger-codegen requires -l (for language).

A nice looking serverless REST API documentation using swagger-codegen
Resulting HTML document using swagger-codegen

redoc

# Install it on your machine as a CLI command, e.g. on MacOS using npm
npm i -g redoc-cli

# Generates the documentation at redoc-docs/index.html
redoc-cli bundle backend-api.yaml -o redoc-docs/index.html
A nice looking serverless REST API documentation using redoc
Resulting HTML document using redoc

Next steps

The final step is to use the generated HTML file and integrate it into your documentation section. If you already have some developer documentation for your app or software, you can simply take the resulting HTML file and copy it to the appropriate location. In my case, I’m uploading it to S3 and put CloudFront in front of it to cache the content. Then it’s available under dev.saas-marketplaces.com. As a side note: I’m managing my stack using CloudFormation and also generate different environments for production and development. This helps separating all the things and lets me try out changes first.

As you can see, the generated HTML files of openapi-generator and swagger-codegen look really similar. As you can read in the FAQ linked above, the tools are similar due to their history and hence, their results and usage are similar. In the end I chose redoc because I preferred their styling. Also, I didn’t want a custom branding at the moment. However, the other two tools are still a very good choice if you want to generate your client libraries as well.

Conclusion

Generating a nice looking documentation for OpenAPI specifications is easy nowadays. Tools like openapi-generator, swagger-codegen or redoc are doing a great job! This is a great support for your serverless functions and their documentation. Unfortunately the time to setup such a process takes time. Also, if you’re not comfortable with the tools yet, you have to learn quite a bit before every step can be automated. I really like the end result now and my next step is to generate the client libraries now.

Have you ever worked with any of the tools? Let me know your experiences or best practices, especially in the context of serverless functions.