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. 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. 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. 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 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:
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.
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 and 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 of swagger-ui
is to use a CDN. There are multiple CDNs that offer these files, like jsdelivr and cdnjs. I'll be using unpkg 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:
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:
'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 - 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 - An extensive documentation of all properties and their allowed values for the OpenAPI specification.
Top comments (1)
I did something similar, with the Swagger UI protected by Cognito auth, and OpenAPI spec being fetched directly from the frontend using credentials from Cognito Identity. You can check it out here: betterdev.blog/serverless-swagger-...