Skip to main content

How to build a serverless API documentation portal in AWS

· 8 min read

Teaser image
Photo by Med Badr Chemmaoui on Unsplash

You've just built an awesome API as a product and want your customers to easily understand and explore its features? This will certainly make you wonder how you could best provide documentation about this API to your customers.

On first publication of our ship-data and analytics APIs we hit this question as well. After a quick research about available solutions we opted - due to a lack of other options - for the official AWS API Gateway Developer Portal.

Fast-forward: Not only did we have a hard time customizing the portal but AWS informed us about a severe security issue inside the portal which would force us to migrate to a newer version. Migration, however, was such a nasty experience, that we took this as a reason to search for better and easier maintainable solutions.

This was the trigger for coming up with our current solution. A stupid simple and yet fully serverless portal to document your public facing APIs - and all of this beautifully. Check it out at https://docs.hoppe-sts.com.

For the sake of simplicity we only provide a rough sketch of our solution to provide a starting point and to help you get going. We leave all further customizations and configurations to the implementing developer. Start building something beautiful!

Example Project Sources

You can find the sources for this example project for the frontend here and for the backend here.

Background

The main ingredients for our solution are

  • The actual-customer facing API hosted in AWS API Gateway which is set up and maintained by the serverless framework.
  • An automatically generated export of the swagger documentation which documents the API itself.
  • Means to host this swagger documentation and additional documentation in a portal. For this purpose we use the awesome docusaurus documentation framework and RapiDoc for beautiful API documentation.

All in all the following diagram gives a bird's eye perspective on the full stack

Serverless Documentation Portal Architecture

In the following, we will discuss the different parts one by one.

Prerequisites

Be sure to be familiar with basic concepts of AWS, the serverless framework, markdown and react before you try to follow along the rest of the post.

Bits and Pieces

The API in API Gateway

To get started we need a very simplistic API which we want to automatically document. For this purpose we create an AWS API Gateway which returns a static response:

backend/serverless.yaml
service: simple-api
provider:
name: aws
region: eu-west-1

package:
exclude:
- ./**
include:
- LICENSE

functions:
simple-api:
handler: handler.simple-api
description: return some mocked example data
events:
- http:
path: simple-api
cors: true
method: get
integration: mock
request:
template:
application/json: '{"statusCode": 200}'
response:
template: '{ "foo" : "$input.params(''bar'')" }'
statusCodes:
200:
pattern: ""

Deploying this with sls deploy will create a new mock API which we will use for the rest of the post to play with.

Serving a Swagger File

Next we want to export the Swagger/OpenAPI documentation which describes this API to S3 for public access. The serverless-export-swagger plugin comes into play here. Have a look at the plugin's documentation for further details. To use it in our project just add it via sls plugin install --name serverless-export-swagger. This serverless plugin exports the latest documentation of your API to a specified S3 bucket.

Let's add a publicly accessible S3 bucket, and the required configuration to store the swagger files in there.

CORS Settings

For simplicity we allowed all headers, methods and hosts in the CORS configuration. This needs careful consideration in a production system.

backend/serverless.yaml
resources:
Resources:
S3Assets:
Type: AWS::S3::Bucket
Properties:
BucketName: ${self:custom.swaggerHostingBucketName}
AccessControl: PublicRead
CorsConfiguration:
CorsRules:
- AllowedHeaders:
- "*"
AllowedMethods:
- GET
AllowedOrigins:
- "*"
ExposedHeaders:
- Content-Type
- Authorization
- Content-Range
- Accept
- Content-Type
- Origin
- Range
Id: myCORSRuleId1
MaxAge: 3600
S3AssetsBucketAllowPublicReadPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref S3Assets
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- "s3:GetObject"
Resource:
- !Join ["", ["arn:aws:s3:::", !Ref S3Assets, "/*" ] ]
Principal: "*"


custom:
swaggerHostingBucketName: ${self:service}-${self:provider.stage}-assets
swaggerHostingKey: 'swagger/simple-api.json'
swaggerDestinations:
s3BucketName: ${self:custom.swaggerHostingBucketName}
s3KeyName: ${self:custom.swaggerHostingKey}
acl: public-read

Last but not least we want to make the URL at which we can access the swagger file available to other serverless stacks for later use:

backend/serverless.yaml
outputs:
swaggerUrl:
name: 'https://${self:custom.swaggerHostingBucketName}.s3.amazonaws.com/${self:custom.swaggerHostingKey}'

This leaves us with the (arguably rather clunky) option to host our swagger files publicly and provide the URL of where to find the swagger file as an output variable of the cloudformation stack for further use in other projects.

tip

Instead of serving the swagger files directly from a public S3 bucket, if you require more fine-grained access control you might want to create an API for it which you can then use for rate limiting, authorization and authentication.

The Documentation Portal

Did you ever want to set up a simple documentation website which sources its text from simple markdown in no time? The awesome tool docusaurus provides exactly this feature set.

Not only does it provide state-of-the-art documentation rendering but provides full flexibility to use React components for customization.

Since setting up a docusaurus web page is very well documented at the Getting Started Guide we only focus here on the interesting parts of integrating RapiDoc for API documentation into the page.

The final result will structurally look like the following screenshot taken from our documentation portal

API Documentation Portal

The following is a step-by-step instruction which will tell you how to add the custom "Our APIs" page to docusaurus.

  1. First, we need to create a small component which can display our API documentation neatly. RapiDoc provides just this. Execute yarn add rapidoc to add this to your package.json file and install all dependencies. Some styling helps as well, to embed the API documentation easily later on. So we create /src/pages/components/ApiDoc.js with the following content:

    frontend/src/pages/components/ApiDoc.js
    import React from 'react';
    import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment';

    export default function ApiDoc(props) {
    const {swaggerUrl} = props;
    if (ExecutionEnvironment.canUseDOM) {
    /// Make sure we only import the rapidoc web component in case we are on the client side
    import('rapidoc');
    return (<rapi-doc header-color={"#004876"}
    nav-text-color={"#004876"}
    primary-color={"#ff9e1b"}
    nav-bg-color={"#ffffff"}
    id="thedoc"
    allow-search={false}
    render-style={"read"}
    allow-try={true}
    show-header={false}
    allow-authentication={true}
    regular-font="Roboto"
    use-path-in-nav-bar={true}
    style={{height: "80vh", width: "100%"}}
    mono-font="monospace" theme={"light"}
    spec-url={swaggerUrl}>
    </rapi-doc>)}
    return <div/>
    }

    The only noteworthy part here is that we wrap the component such that we only return the RapiDoc component for code that is rendered in the frontend.

  2. Add a custom React page to your docusaurus-bootstrapped project by following the steps described here. This is as simple as creating a file frontend/src/pages/apis.js with the following code:

    frontend/src/pages/apis.js
    import React from 'react';
    import Layout from '@theme/Layout';
    import ApiDoc from './components/ApiDoc';

    function APIs() {

    return (
    <Layout>
    <ApiDoc swaggerUrl={process.env.REACT_APP_SWAGGER_URL}/>
    </Layout>
    );
    }

    export default APIs;
  3. Here you see that we pass in the source URL of the swagger file via an environment variable which will be passed to the application by the AWS Amplify Console. This hosting part will be described in more detail below. However, to be allowed to use environment variables in docusaurus, you need to add docusaurus2-dotenv via yarn add docusaurus2-dotenv to your package.json.

Putting it all together

Now, that we have all the bits and pieces in place, we need a way to host our brand-new documentation portal. Since we are on AWS anyway, we can employ the AWS Amplify Console to host our portal easily.

To that effect, a simple addition to our backend/serverless.yaml file helps to get us going:

backend/serverless.yaml
plugins:
- serverless-amplify-plugin

custom:
amplify:
repository: https://github.com/USER/REPO
branch: master
buildSpec: |-
version: 1
frontend:
phases:
preBuild:
commands:
- nvm use $VERSION_NODE_12
- yarn install
build:
commands:
- nvm use $VERSION_NODE_12
- yarn build
- echo "REACT_APP_SWAGGER_URL=https://${self:custom.swaggerHostingBucketName}.s3.amazonaws.com/${self:custom.swaggerHostingKey}" >> ./.env
artifacts:
baseDirectory: build
files:
- '**/*'
cache:
paths:
- node_modules/**/*

Here, we make use of the @brettstack/serverless-amplify-plugin. Make sure to install it before via sls plugin install --name @brettstack/serverless-amplify-plugin inside the backend project.

Checkout the documentation for the serverless-amplify-plugin for how to set up the personal access token to allow AWS Amplify connecting to the github source repository for the frontend code.

Afterwards carry out an sls deploy and watch the magic happen! This concludes the final step to have a fully functioning serverless documentation portal up and running.

Advanced Topics and Next Steps

What is next? Here at Hoppe we have already integrated cognito user authentication via the AWS Amplify React library. This allows our customers easily to create accounts, update and reset their passwords, etc. with very little coding on our side.

Moreover, this is the basis for additional customer facing admin logic like extending API call quota, creating API keys, and more.

What ideas do you have in mind for extending this solution with? Get in touch with us via datasolutions@hoppe-marine.com or leave a comment in the comment section. We are always keen to get in contact.

About the Author
Dr. Klaus HueckDr. Klaus Hueck is lead developer at Hoppe Marine's R&D department. He has been responsible for leading the development of Hoppe's cloud infrastructure and the Ship-to-Shore data transmission technology. With a strong background in software development and physics he enjoys breaking down complex tasks into workable packages to build tailor made solutions focused on our customers needs.

In his free time, he is a passionate sailor and beekeeper.