Configure AWS API Gateway with Swagger

Photo by Ernest Brillo on Unsplash

Categories

  • Serverless
  • Howto

From the OpenAPI Specification (aka Swagger):

The OpenAPI Specification (OAS) defines a standard, language-agnostic interface to RESTful APIs which allows both humans and computers to discover and understand the capabilities of the service without access to source code, documentation, or through network traffic inspection.

Based on the definition of an API using OAS it is possible for tools to work with it. For example documentation can be generated or client code can be created.

In previous sample projects described in other posts to this blog the AWS API Gateway has been used for exposing a RESTful API. The AWS API Gateway itself supports the OpenAPI Specification as an input for its configuration. This way the API Gateway can be configured from the same specification that is used to generate documentation and therefore ensures a consistent view on the actual capabilities of a service.

This blog post will show how Swagger can be used to configure the REST API endpoints of a sample microservice implementation, how it can be used to configure the API Gateway and how this can be integrated to the CI/CD pipeline presented in the last blog post.

Configure an endpoint in Swagger

The format for describing APIs with Swagger is either JSON or YAML. An API specification contains general information about the API as well as the technical details about every endpoint it exposes. Additionally, it might also define the actual data structures consumed and returned by the API itself. This way it can provide a complete picture of the API to humans as well as machines.

The following example shows the definition of a simple endpoint:

openapi: "3.0.1"
info:
  title: "tasks-api"
  version: "v1.0"
paths:
  /:
    get:
      summary: List tasks
      description: Returns a list of tasks
      responses:
        200:
          description: "OK"
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Task"
        500:
          description: "Internal Server Error"
          content: {}
components:
  schemas:
    Task:
      type: object
      properties:
        id:
          type: string
        title:
          type: string

In the example an API endpoint for the root path (/) is defined. It accepts requests using the HTTP GET method and returns either the status code 200 OK or 500 Internal Server Error. When the requests is successful an array of Task objects is returned in JSON format. Each of these objects has the attributes id and title. Therefore, the actual response looks something like this:

[
  {
    "id": "",
    "title": "Task 1"
  },
  {
    "id": "",
    "title": "Task 2"
  }
]

In the same way all the other endpoints can be defined. For a more complete example have a look at the Swagger file used in the sample project.

Integrate Swagger with API Gateway for SAM applications

As mentioned before, AWS API Gateway can be configured by using API specifications written in Swagger. Additionally, a set of extensions have been defined for the API Gateway to capture most of its specific properties, like integrating Lambda functions or using Authorizers. This way it is possible to use Swagger to automatically provision an AWS API Gateway by the various means provided by AWS (Console, CLI, SDK, CloudFormation).

The following steps are need to be performed to use Swagger to configure an API Gateway for an application configured via the Serverless Application Model (SAM).

First, the Swagger file presented above is modified to reference the appropriate Lambda function for handling incoming requests to the API endpoint. For this, the custom extensions x-amazon-apigateway-integration is used:

openapi: "3.0.1"
info:
  title: "tasks-api"
  version: "v1.0"
paths:
  /:
    get:
      summary: List tasks
      description: Returns list of tasks
      responses:
        200:
          description: "OK"
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Task"
        500:
          description: "Internal Server Error"
          content: {}
      x-amazon-apigateway-integration:
        uri:
          Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${ListTasksFunction.Arn}/invocations
        responses:
          default:
            statusCode: "200"
        passthroughBehavior: "when_no_match"
        httpMethod: "POST"
        contentHandling: "CONVERT_TO_TEXT"
        type: "aws_proxy"

In this case the Lambda function ListTasksFunction should be invoked for each GET request send the the root path of the API. It is dynamically referenced by replacing the the variables ${AWS::Region} and ${ListTasksFunction.Arn} with the actual values which are created during the creation of the CloudFormation stack launched from the SAM template which uses this Swagger file.

To perform these replacements the API Gateway resource must be configured in the SAM template. It uses a Swagger file uploaded to a S3 bucket:

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31

Parameters:
  S3BucketName:
    Type: String
    Description: The name of the S3 bucket in which the Swagger specification is stored
  StageName:
    Type: String
    Description: The name of the stage, e.g. "dev", "preprod", "prod"
    Default: dev

Resources:

  RestApi:
    Type: AWS::Serverless::Api
    Properties:
      Name:
        Fn::Sub: todo-app-api-${StageName}
      StageName:
        Ref: StageName
      DefinitionBody:
        Fn::Transform:
          Name: AWS::Include
          Parameters:
            Location:
              Fn::Join:
                - ''
                - - 's3://'
                  - Ref: S3BucketName
                  - '/swagger.yaml'

In this example the name of the S3 bucket in which the Swagger file is stored is provided as a parameter to the template. Additionally, the name of the stage is also provided as a parameter. This way it is possible to have multiple instances of the same API provisioned in the same AWS account and region.

Instead of simply referring to the Swagger file for the DefinitionBody property of the RestApi resource a Fn::Transform is applied to it. This way it is possible for CloudFormation to preprocess the Swagger file before using it to configure the API Gateway. This is necessary to dynamically referencing the right AWS region and ARN of the Lambda function when the API is provisioned.

Finally, the Lambda function itself must be bound to the API Gateway endpoint. Therefore, it must be configured to handle events coming from the specific instance of the API Gateway endpoint. This is done in the SAM template and looks like this:

ListTasksFunction:
  Type: AWS::Serverless::Function
  Properties:
    FunctionName:
      Fn::Sub: todo-app-${StageName}-list-tasks
    CodeUri: src/
    Handler: index.listTasks
    Policies:
      DynamoDBReadPolicy:
        TableName:
          Ref: TasksTable
    Environment:
      Variables:
        TABLE_NAME:
          Ref: TasksTable
    Events:
      Get:
        Type: Api
        Properties:
          RestApiId:
            Ref: RestApi
          Path: /
          Method: GET

With this, the deployment of the SAM application becomes a four step process:

  1. Upload the Swagger file to the appropriate S3 bucket
  2. Build the application
  3. Package the application
  4. Deploy the application

This can be performed by a script similar this one:

#!/usr/bin/env bash

S3_BUCKET=com.carpinuslabs.sam.deployment
INPUT_FILE=sam-template.yaml
OUTPUT_FILE=sam-template-output.yaml
STAGE_NAME=dev
STACK_NAME=todo-app-$STAGE_NAME

# Copy Swagger file to S3 to be able to transform its content
# for "deploying" it to API Gateway
aws s3 cp swagger.yaml s3://$S3_BUCKET/swagger.yaml

# Build the application by installing its dependencies
cd src && npm install --production && cd ..

# Package the application
aws cloudformation package --template-file $INPUT_FILE \
                           --output-template-file $OUTPUT_FILE \
                           --s3-bucket $S3_BUCKET
# Deploy the application
aws cloudformation deploy --template-file $OUTPUT_FILE \
                          --stack-name $STACK_NAME \
                          --parameter-overrides StageName=$STAGE_NAME S3BucketName=$S3_BUCKET \
                          --capabilities CAPABILITY_IAM

Next Steps

Once the API is described using Swagger it makes sense to incorporate it into the existing CI/CD pipeline. This is pretty easy since the only change that needs to be applied is to upload the Swagger file to the appropriate S3 bucket as part of the build process. Coming back to the build configuration presented in the blog post A Serverless CI/CD Pipeline for SAM applications it can be done in the post_build phase of the CodeBuild project just before the application is packaged for deployment. The buildspec.yaml changes to something like this:

version: 0.2

phases:
  pre_build:
    commands:
      - cd src
      # Install the dependencies
      - npm install
  build:
    commands:
      # Run tests
      - npm test
      # Remove all dependencies not relevant for production
      - npm prune --production
      - cd ..
  post_build:
    commands:
      # Copy Swagger file to S3 to be able to transform its content
      # for "deploying" it to API Gateway
      - aws s3 cp swagger.yaml s3://$S3_BUCKET/swagger.yaml
      # Create and upload a deployment package
      - aws cloudformation package --template-file sam-template.yaml --s3-bucket $S3_BUCKET --output-template-file sam-template-output.yaml

artifacts:
  files:
    - sam-template-output.yaml

With this change the configuration of the API Gateway is updated whenever a deployment is performed. Therefore, the process is completely automated and all the parts are managed through source control.

Having the definition and documentation of the API in Swagger opens up a whole lot of opportunities. One thing is the generation of documentation from Swagger file. There are a bunch of tools available that handle this task. Again, generating the documentation can be part of the CI/CD pipeline to ensure that the documentation reflects the current state of the API. Since the documentation is only comprised of static files (HTML, CSS, JavaScript, etc.) it’s possible to host it as a static website and restrict access to it. As discussed in previous blog posts that can be done in a very cost-effective manner.

Finally, Swagger can also be helpful when it comes to testing the API. Tools like Postman support the import of Swagger files. This makes it pretty easy to configure the tool for interacting with the API.

Summary

This blog post showed the steps necessary to describe the REST API of a serverless microservice via Swagger. Although this is an additional step for a developer to perform it provides a lot of benefits. First, it provides the opportunity to put the configuration of the API into source control. Next, this information can be used to automatically update the configuration of the API Gateway as part of the standard CI/CD process. Finally, having the API specification in Swagger helps to integrate it with other tools, like documentation generators or API testing tools.

The code of the sample project has been updated to implement the changes presented in this blog post.