REST APIs using go-swagger, Lambda functions, and the API Gateway

Seán Murphy
Level Up Coding
Published in
5 min readJan 11, 2021

--

Photo by Jeong Ho Choi on Unsplash

AWS provides support for Swagger/OpenAPI defined REST APIs as well as a go runtime; however, it is not entirely clear how best to work with these, especially if you’re used to using go-swagger for go REST APIs in a non-AWS context. Here I describe how the API Gateway, go Lambda functions and the go-swagger module can work together. Two specific points are addressed here:

  • how to use go-swagger in Lambda functions called from the API Gateway
  • how request validation is performed both from the API Gateway perspective and the go-swagger perspective.

A simple example of how these technologies can be used together is provided in this github repo.

Context

Swagger/OpenAPI has established itself as a standard way of defining APIs with advanced tooling for many languages and platforms. In the go context, go-swagger is a widely used mature implementation of v2.0 of the Swagger/OpenAPI standard which supports data validation and marshaling for a REST API and provides go data models derived from the API definition. Similarly, the AWS API Gateway supports the creation of REST APIs defined in Swagger/OpenAPI files (both v2.0 and v3.0): creating an API in this way will create the appropriate endpoints and data models within the API Gateway. It is still, of course, necessary to link it to the API implementation — Lambda functions in this case.

Glue: linking the API Gateway, Lambda functions and go-swagger

The basic variant of using go-swagger in a Lambda function is straightforward and quite similar to the non Lambda function context. The API endpoints, parameters and models are defined in the Swagger file and code for the server REST API is generated by the go-swagger tools; this then needs to be bound to the actual implementation.

Particular to the Lambda function case is that the entry point is now a specific handler which takes an APIGatewayProxyRequest as input and returns an APIGatewayProxyResponse. AWS provides a httpadaptormodule in their aws-lambda-go-api-proxy tools which can map from these AWS specific data types to general HTTP requests as would be expected by any standard go HTTP server. This is the glue that is necessary to support go-swagger integration.

A simplified variant of this is shown below and slightly more complex example is provided in the github repo.

package mainimport (
...snip...
)
var httpAdapter *httpadapter.HandlerAdapterfunc init() {
swaggerSpec, err := loads.Embedded(restapi.SwaggerJSON, restapi.FlatSwaggerJSON)
if err != nil {
panic(err)
}
api := operations.NewLambdaGoSwaggerTestAPIAPI(swaggerSpec)
api.OpenGetAPIIdentifierHandler = open.GetAPIIdentifierHandlerFunc(handlers.GetApiIdentifier)
server := restapi.NewServer(api)
server.ConfigureAPI()
httpAdapter = httpadapter.New(server.GetHandler())
}
// Handler handles API requests
func Handler(req events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
return httpAdapter.Proxy(req)
}
func main() {
lambda.Start(Handler)
}

In the above, there is only a single endpoint — GetAPIIdentifier— and this is bound to its implementation in handlers.GetApiIdentifier() (not included above); this binding is done by setting the api.OpenGetAPIIndentifierHandler variable.

As with the non-Lambda function case, the linkage between the swagger.yaml REST API endpoint and the Go handler function is specified via the operationId as can be seen in the snippet from the swagger definition below:

paths:
/:
get:
tags:
- open
summary: API Identifier endpoint
operationId: getApiIdentifier
description:
Endpoint which returns the API version and the running backend version

go-swagger performs some naming conversions to ensure that names are compatible with go’s typical camelcase naming.

The workflow, then, of creating the REST API is to generate the code stubs using go-swagger and bind them to the functions implementing the API logic similar that shown above; this creates the executable that will be uploaded as a Lambda function. The API Gateway configuration and the link to the Lambda function can be done using SAM tooling as shown in the github repo — this provides a straightforward way to link the API definition (in the Swagger file), the API Gateway configuration and the Lambda function. Of course other solutions based on eg CloudFormation could also be used for this.

Request validation

Using these technologies in this way gives rise to the issue that there is now some duplication of functionality: both the go-swagger auto generated code and the API Gateway perform request validation. In the case of go-swagger, request validation is always performed when mapping data to the defined data models and errors are generated in case the request is not as expected; in the case of the API Gateway, it is an optional functionality which can be enabled.

API Gateway request validation offers two basic capabilities: parameter validation — primarily ensuring query parameters are present — and request body validation which can be used to ensure that the body of an HTTP request is consistent with a specific data model; in the Swagger context, this data model would typically be defined in the Swagger file. This validation can be triggered via AWS specific extensions to the Swagger file as shown in the snippet below.

swagger: '2.0'
# define two validators - basic and params-only
x-amazon-apigateway-request-validators:
basic:
validateRequestBody: true
validateRequestParameters: true
params-only:
validateRequestBody: false
validateRequestParameters: true
# default is no validation
x-amazon-apigateway-request-validator: NONE
paths:
/query-param/simple-response:
get:
produces:
- application/json
x-amazon-apigateway-request-validator: params-only
parameters:
- in: query
name: query-param
type: integer
required: true
/body-param/simple-response:
post:
produces:
- application/json
consumes:
- application/json
x-amazon-apigateway-request-validator: basic
parameters:
- in: body
name: bodyParam
schema:
$ref: '#/definitions/InputObject'
required: true

In the above, no validation — the NONE validator — is the default, but params-only or body validation can be performed, the former validating request query parameters and the latter validating body content.

Using the request validation function of the API Gateway has the benefit that it can reduce the amount of Lambda function invocations as invalid REST API calls can be blocked by the API Gateway itself; ultimately, this can result in lower costs. It is worth noting also that there are small differences between the request validation processes that take place within the API Gateway and such validation within go-swagger: failed validations in go-swagger return a HTTP 422 (Unprocessable Entity) while those generated by the API Gateway return HTTP 400 (Bad Request); also the API Gateway performs a more lightweight validation of parameters, only checking that they are present, while go-swagger performs a proper type-based validation.

More details in this github repo.

--

--

Tech Tinkerer, Curious Thinker(er). Lost Leprechaun. Always trying to improve.