REST API Versioning on AWS API Gateway

REST API Versioning on AWS API Gateway

When it comes to API versioning there are a couple of ways and places of doing that:

  • URI <- we're gonna do this one if you're wondering
  • Custom request header
  • Accept header

Header versioning is, in theory, the best way to do it and get the ideal RESTful API. URI versioning however is apparently not the best way, since it changes the URI, which violates RESTful API rules, bla bla bla... From the API user perspective, you can programmatically set the version in the header or URI, so I believe it all comes down to what is more convenient for the API developer.

If you want to read more about it, go to the link below or just do some googling. Still, some lesser-known companies like Google, Facebook, or AWS are using URI versioning and they still exist.

API Versioning Do’s and Don’ts
It’s never too soon to get started crafting your versioning strategy

Going back to header versioning, it is doable on AWS, but it introduces some complexity, you need to use CloudFront with Lambda@Edge, and decide on routing the request to the proper API Gateway... If you want to explore that solution a bit more, here's a whole blog post from AWS about it.

Implementing header-based API Gateway versioning with Amazon CloudFront | Amazon Web Services
This post is written by Amir Khairalomoum, Sr. Solutions Architect. In this blog post, I show you how to use Lambda@Edge feature of Amazon CloudFront to implement a header-based API versioning solution for Amazon API Gateway. Amazon API Gateway is a fully managed service that makes it easier for dev…

AWS API Gateway & URI Versioning

URI versioning on AWS can be achieved in a more simple way, in comparison to the header versioning solution, mentioned above. This might seem a bit tricky to set up at first, but honestly, this is a pretty simple and awesome way to version your API while making your life less complicated in terms of code management and complexity.

The solution is to use Stage Variables in your API URI for versioning. You can read more about them in the official docs -  Deploying a REST API in Amazon API Gateway. However, the versioning idea is quite simple. Below you can see the stage variables example...

https://{restapi-id}.execute-api.{region}.amazonaws.com/{stageName}

where we end up with...

https://{restapi-id}.execute-api.{region}.amazonaws.com/v1/
https://{restapi-id}.execute-api.{region}.amazonaws.com/v2/

Now that you have v1 and v2 as your stage variables, you can assign them to point to proper versions of your Lambdas.

How it is not done.

When you hear the phrase "stage variables" and "Lamba versions" or "Lambda versioning", you might think this will use the classic approach that you learn for certification, where you use Lambda aliases that point to different versions of the same lambda. Well, that's not the case here.

How it is done.

The approach I'm presenting here points to totally different Lambdas and does not use Lambda aliases. Why this way? Well, I believe this is an excellent solution when you have version number 1.x, and 2.x of your Lamba and you need to maintain both of them. You can simply update each of the Lambda versions independently, and you can even use different programming languages for them.

Now, this is a simple example, that you can definitely build upon, for example, adding Lambda aliases to do Canary deployments for each lambda version independently and so on, maybe that's something I'll cover in the future, we'll see.

Solution

The solution I'm presenting here is done via Terraform. However, if you feel like doing this through AWS Console, you simply need to add your stage variable to "Lambda Function" in API Gateway endpoint "Method Execution" configuration. Check the screenshot, that's a bit further down.

If you want to test it on your own and play around with it using Terraform, you can find the code here: https://github.com/FliesWithWind/aws-api-versioning-tf

The solution in theory is quite simple, but when I did some research about how you could achieve this using IaC and Terraform, I found nothing, therefor I hope someone will find this useful.

In the example, we have an API with one GET endpoint that comes in two versions. Each version points to different lambda so:

https://{restapi-id}.execute-api.{region}.amazonaws.com/v1/ping

points to Lambda named "ping-v1", and

https://{restapi-id}.execute-api.{region}.amazonaws.com/v2/ping

points to Lambda named "ping-v2".

The above is achieved by having the Lambda version variable in your API deployment stage, which is used in your lambda name. Terraform example for version v1 is below.

resource "aws_api_gateway_stage" "v1" {
  deployment_id = aws_api_gateway_deployment.api-serverless-gateway-deployment.id
  rest_api_id   = aws_api_gateway_rest_api.api-serverless.id
  stage_name    = "v1"
  variables     = {
    lambda_version = "-v1"
  }
}
This part represents v1 stage

Then we have part of the openAPI doc, where we define APIGateway integration with our Lambda.

"x-amazon-apigateway-integration": {
    "httpMethod": "POST",
    "uri": "arn:aws:apigateway:${aws_region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${aws_region}:${aws_account_id}:function:${ping_lambda_name}$${stageVariables.lambda_version}/invocations",
    "responses": {
    "default": {
    "statusCode": "200"
    }
    },
    "passthroughBehavior": "when_no_match",
    "timeoutInMillis": 200,
    "type": "aws_proxy"
}
This represents part of openAPI doc for Lambda integration

The most important part here is the "uri" format. The highlighted part of the URI presented below (you will probably have to scroll to see it), refers to the lambda_version variable. Note that ${what_ever_is_here} references Terraform variables, while $${stageVariables.lambda_version} references to AWS Stage variable.

arn:aws:apigateway:${aws_region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${aws_region}:${aws_account_id}:function:${ping_lambda_name}$${stageVariables.lambda_version}/invocations

Once deployed, this is how it looks like this in AWS console if you're wondering.

Finally, we can test our so solution by calling /ping endpoint of our v1 and v2 API. The results look like this.

Final remarks

Since this is my first post on this blog, I hope I didn't take too much for granted. I might follow up on this in the future, which will end up with this being updated or a totally new post. Hope you found this useful, and if you have any questions or remarks, feel free to reach out to me via LinkedIn.