Using Swagger UI in AWS' serverless stack
Tags: aws, serverless, openapi, node
Published: 2021-06-08 09:47:29

Few things are as frustrating as working with an API that is not properly documented. That is why we aim to equip our applications with [Swagger / OpenAPI documentation](https://swagger.io/resources/open-api/). This exposes the url `/swagger` where a nice documentation for the API is available. At the same time have we been using more and more serverless technologies, which in our situation means [AWS Lambda](https://aws.amazon.com/lambda/). In order to use an AWS Lambda function as an HTTP API the function needs to be triggered by a route defined in [AWS API Gateway](https://aws.amazon.com/api-gateway/). I want to share the setup we use to quickly expose Swagger documentation for our serverless stacks. 

**NB** The code examples use NodeJS and [Cloudformation](https://aws.amazon.com/cloudformation/) as those are the tools we currently use at Coolblue. This trick is not very complex, so it should be easily translated to other languages. 

## Swagger. Or OpenAPI?
OpenAPI is a specification that is used to describe an API. It is the de facto industry standard for describing REST APIs and as such there is a lot of tooling available to generate or interpret OpenAPI specifications.

For clarification: the terms OpenAPI and Swagger are both used. As [the OpenAPI Initiative explains themselves](https://swagger.io/blog/api-strategy/difference-between-swagger-and-openapi/):

> The easiest way to understand the difference is:
> - OpenAPI = Specification
> - Swagger = Tools for implementing the specification

In order to serve a nice documentation we need three things; an OpenAPI specification of the API, a way to generate html from that specification and a way to serve the generated html. 

Creating an OpenAPI specification is outside the scope of this text - although that might be an interesting topic for a followup. For this text I will assume you already have an OpenAPI specification. Either generated from code (annotations) or created by hand, maybe using the online [Swagger Editor](https://editor.swagger.io/).

## Exposing the generated documentation
Given that you have an OpenAPI specification the next step is to convert this to html and expose it. There are a couple of ways to build up html based on an OpenAPI specification; The OpenAPI Initiative themselves offer two NPM packages to do this: [swagger-ui](https://www.npmjs.com/package/swagger-ui) and [swagger-ui-dist](https://www.npmjs.com/package/swagger-ui-dist). Both these packages offer an example html and the required javascript and css resources. The benefit of using `swagger-ui-dist` over `swagger-ui` is that it is dependency free and ready to use.

Using a serverless architecture has a downside; For every url we need to create an endpoint. We can either create a function and an endpoint for every resource or work with some form of wildcards. The first means additional work, where the latter introduces some additional complexity. Both result in Lambda invocations for static content. 

Another option that is actually suggested on the [installation instructions](https://swagger.io/docs/open-source-tools/swagger-ui/usage/installation/) of `swagger-ui` is to use a <abbr title="Content Delivery Network">CDN</abbr>. There are multiple CDNs that offer these files, like [jsdelivr](https://www.jsdelivr.com/package/npm/swagger-ui-dist) and [cdnjs](https://cdnjs.com/libraries/swagger-ui). I'll be using [unpkg](https://unpkg.com/) in these examples, just like in the installation instructions. 

In the scenario where you don't want to use an external CDN you can also expose these files through your own CDN solution, for example an S3 bucket. This approach has an additional bonus as you will be able to keep all of your Swagger documentations running on the same version of `swagger-ui` and you might be able to host additional CSS files to apply some house styles to your Swagger documentations. 

### Cloudformation
I'll be following the convention to offer the documentation on the `/swagger` url:

```yaml
Parameters:
  Environment:
    Type : "String"
    Default: "development"
    Description: "Environment in which resources are deployed."

Resources:
  # Lambdas
  SwaggerFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: swagger.swagger
      FunctionName: !Sub "${AWS::StackName}-swagger"
      CodeUri: .
      Events:
        GetHtml:
          Type: Api
          Properties:
            Path: /swagger
            Method: get
            RestApiId: !Ref "ServerlessApi"
        GetSpec:
          Type: Api
          Properties:
            Path: /swagger.json
            Method: get
            RestApiId: !Ref "ServerlessApi"

  # Api gateway
  ServerlessApi:
    Type: "AWS::Serverless::Api"
    Properties:
      StageName: !Ref "Environment"
```

I'm applying a little trick here; It is possible to add multiple events to the same `AWS::Serverless::Function` resource. The reason I am also routing `/swagger.json` to the Lambda function is to enable consumers to download the specification. This specification can be used to autogenerate client code for consuming the API. 

### Javascript
Now that Cloudformation end of things is set up we need to actually generate the documentation, which is where `swagger-ui` comes in play: 

```javascript
'use strict';

const { readFileSync } = require('fs');

const applicationName = 'My Awesome Application';

exports.swagger = async (event) => {
    if (event.path === '/swagger.json') {
        return {
            statusCode: 200,
            headers: {
                'content-type': 'application/json'
            },
            body: readFileSync('./etc/swagger.json')
        }
    }
    
    const body = `<!DOCTYPE html>
        <html lang="en">
        <head>
            <meta charset="UTF-8">
            <title>${applicationName}</title>
            <link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@3/swagger-ui.css">
        </head>
        <body>
            <div id="swagger"></div>
            <script src="https://unpkg.com/swagger-ui-dist@3/swagger-ui-bundle.js"></script>
            <script>
              SwaggerUIBundle({
                dom_id: '#swagger',
                url: '/swagger.json'
            });
            </script>
        </body>
        </html>`;
    
    return {
        statusCode: 200,
        headers: {
            ['Content-Type']: 'text/html',
        },
        body
    };
}
```

## Conclusion
Writing documentation is just as much part of the development process as writing code. OpenAPI offers a standardized way of describing an API and `swagger-ui` offers an off-the-shelf solution that can generate a very readable documentation. Exposing Swagger documentation on a standardized url - `/swagger` - adds a consistent way for any consumers of the API to know what endpoints the API exposes and how these endpoints can be exposed. With just a handful lines of code it is possible to expose Swagger documentation for your serverless stack.

## Continue reading
There are a lot of great resources about OpenAPI. I want to point out a couple of resources that have been very helpful to me while working with OpenAPI.
- [Swagger Petstore](https://petstore.swagger.io/) - A demo and showcase of all the features offered by OpenAPI and Swagger. A great resource if you prefer to read example code over reading the documentation of the OpenAPI specification.
- [OpenAPI specification](https://swagger.io/specification/) - An extensive documentation of all properties and their allowed values for the OpenAPI specification.