API Gateway vs Application Load Balancer—Technical Details

At re:Invent 2018, AWS gave us a new way of using Lambda functions to power APIs or websites: an integration with their Elastic Load Balancing Application Load Balancer. Previously, the go-to way of powering an API with Lambda was with API Gateway. What are the differences in the two? Can you have one Lambda function that works for both API Gateway and Application Load Balancer? When should you choose one over the other? This article answers all those questions.

Note that this article is a technical deep-dive into the differences in these two technologies. If you’re looking more for the business case between the two, please read this article:

Saving Money By Replacing API Gateway With Application Load Balancer's Lambda Integration

Or, if you want to get started right away with working examples, I’ve done the work for you and explained how you can deploy a function that runs with both API Gateway and Application Load Balancer in this article:

How to Set Up Application Load Balancer With Lambda

How Does Application Load Balancer Work Compared to API Gateway?

On an application load balancer, you map certain paths (e.g. /api/*) to a “target group”. Until the integration with Lambda was announced, you could think of a target group as a group of resources - like EC2 instances - that could respond to the request. In the context of a Lambda function, a target group can have one Lambda function associated with it so that whenever the target group needs to respond to a request, the ALB will send your Lambda function a request object as the function’s event, and your function will respond with a response object. The ALB - just like API Gateway - handles all the actual HTTP(S) interaction.

API Gateway only supports SSL (HTTPS), whereas with ALB you can also support HTTP - even though HTTP support is becoming mostly irrelevant as the entire web continues its transition to HTTPS-only.

Fundamentally, the way you write your Lambda functions for Application Load Balancer and API Gateway is basically the same. But, there are some subtle - and mostly undocumented - differences between the two. Hopefully AWS improves the documentation for the ALB/Lambda integration soon, but in the meantime, here’s the differences that I’ve noted. If you want to jump right in with some examples, you can also go straight to my tutorial with examples on how to use Lambda with Application Load Balancer.

Feature Differences Between Application Load Balancer and API Gateway

When asked when to use API Gateway over Application Load Balancer, Dougal Ballantyne, the Head of Product for Amazon API Gateway pointed to the features that APIGW has that ALB doesn’t:

And he’s right - APIGW offers you features that just aren’t available in ALB. Here’s details on a couple of the ones he mentioned:

  • Authentication and Authorization: API Gateway has a robust set of features surrounding auth and auth. For example:
    • Token-based authentication
    • Resource-based permissions
    • Integration with IAM for controlling access to the API
    • Custom authorizers that handle authorization and define custom IAM policies that the API function will run with
  • Request and Response Mapping: API Gateway supports integration with legacy systems by being able to map requests and responses to the format required by the backend system.
    • That said, I rarely see this needed for serverless stacks because if you’re using APIGW with Lambda, it’s likely new code - not legacy code that needs mapping - and you’re able to use the API Gateway “proxy” integration for Lambda.

But, you do pay a bit of a premium for those features, as explained in my article on saving money by replacing API Gateway with Application Load Balancer. If you need help choosing between the two options, read that article for a complete analysis.

Lambda Event Request / Response Details for Application Load Balancer

If you’ve decided you don’t need those extra API Gateway features and you have enough traffic to make an Application Load Balancer make sense, then you need to know how to write your Lambda function to respond to ALB requests. For the most part, your function will work just like it does with API Gateway - your Lambda function will be passed an event object with details about the request, and you’ll respond (or invoke the Lambda callback) with an object representing an HTTP(S) response.

Note that when I’m talking about the request and response event formats for API Gateway, I’m assuming that you’re using the API Gateway proxy integration, which is the most common way of using API Gateway with Lambda. If you’re using the custom request or response mapping features of API Gateway, then your event and response object format will depend on how you’ve configured your mappings.

Here’s the differences I’ve found in the default API Gateway proxy integration request and response objects when compared to Application Load Balancer’s:

Application Load Balancer Request Event Format Differences

Compared to the request object of the API Gateway proxy integration, the Application Load Balancer’s request event that it sends your Lambda function will have these differences:

  • evt.resource: This property does not exist on the ALB request object because ALB doesn’t have the same notion of resources that API Gateway has.
  • evt.headers: On APIGW, the headers are case-sensitive, but on ALB the header names you get in the request object will be all lower-cased.
    • Note, too, that API Gateway endpoints are always part of a CloudFront distribution, so you get CloudFront headers in your request headers object for determining things like the country the user is connecting from and approximate device type that the user is using. You don’t get those headers with ALB natively (readers from AWS - consider this a feature request for ALB), but if you really need them, you could always put a CloudFront distribution in front of your ALB - which will still be more cost effective than using API Gateway just for these headers.
  • evt.queryStringParameters: If there are no query string parameters, APIGW sets this field to null, whereas the ALB gives you an empty object {}.
    • Note that both APIGW and ALB are the same in that they send the query string parameters with the exact case that they appeared in the URL (as opposed to headers as described above).
    • Note that the keys and values of evt.queryStringParameters will be:
      • already URL decoded for you on API Gateway
        • e.g. ?foo%5Ba%5D=foo%5B will be { 'foo[a]': [ 'foo[' ] }
      • but not URL decoded on ALB
        • e.g. ?foo%5Ba%5D=foo%5B will be { 'foo%5Ba%5D': [ 'foo%5B' ] }
  • evt.multiValueHeaders: On APIGW, you automatically get a field named multiValueHeaders in your request object. It contains the header values in a different format which allows for multiple headers to be sent with the same name. On ALB, you can enable this feature by turning on the lambda.multi_value_headers.enabled attribute on the ALB target group.
    • To read more about multi-value headers with ALB, see their Lambda functions documentation.
    • Note that if you enable this feature on the requests, then your response can no longer just use the standard headers format - you also must respond with a multiValueHeaders field in the same format as your request multiValueHeaders.
    • Similar to queryStringParameters, APIGW gives you null and ALB gives you {} for this field when there are no headers.
  • evt.multiValueQueryStringParameters: This behaves the same way as evt.multiValueQueryStringParameters, and is controlled by the same lambda.multi_value_headers.enabled attribute (making the naming of that attribute a bit unfortunate since query string parameters aren’t really headers).
    • The ALB / Lambda multi-value headers docs give an example of a query string parameter that appears multiple times in the URL.
    • Similar to queryStringParameters, APIGW gives you null and ALB gives you {} for this field when there are no query string parameters.
  • evt.requestContext: On APIGW, you get a lot of information in the request context - details about the API Gateway resources involved in the request, as well as identity information if you’re using any authentication / authorization features of APIGW. On the other hand, this object is very simple on an ALB, giving you only the ARN of the target group that is being invoked.
    • Note: as discussed below, you can use the presence of evt.requestContext.elb to determine that your Lambda function was invoked by an Application Load Balancer if your function is configured to be used with both an APIGW and ALB. Knowing that the function was invoked by the ALB will allow you to make the subtle changes necessary to your response format when responding to the request.
  • evt.pathParameters: is an APIGW-only construct since you map “path parameters” into the URLs that you configure for APIGW.
    • On ALB, you don’t specify path parameters - you just specify URL patterns that map to various target groups (a target group can have a single Lambda function). Any other parsing of the URL to extract parameters from it will need to be done in your Lambda function.
  • evt.stageVariables: is an APIGW-only construct because it supports deploying multiple “stages” of your app.

If you want to see a request object in its entirety, I recommend deploying the example services I made for you, as explained in the how to set up ALB with Lambda article. In those examples, you’ll deploy a simple “echo” service that echos back the Lambda input event, making it easy to see for yourself the exact differences between the two request formats.

Application Load Balancer Response Event Format Differences

Compared to the response object of the API Gateway proxy integration, the response object that Application Load Balancer expects your function to return (or pass to its callback) has these differences:

  • statusDescription: This field must be defined in addition to the normal statusCode. The statusDescription field is a concatenation of the HTTP status code (e.g. 200) and the “description” of that code (e.g. “OK”), with a space between the code and description.
    • For example:
      • 200 OK
      • 301 Moved Permanently
      • 302 Found
      • 404 Not Found
    • Important: For whatever reason, APIGW will throw an error (respond with “Internal server error”) if you include a statusDescription field in your response for APIGW.
  • isBase64Encoded: This field is a boolean that indicates whether your response has been base64 encoded or not (e.g. if you were responding with binary data, you would base64 encode it first). The field is optional on APIGW, but required on ALB.
  • headers: On APIGW, it’s optional to return headers, but on ALB, you must specify a headers field, even if you only set it to an empty object {}.
  • multiValueHeaders: As mentioned above, if you’ve enabled the multi-value headers feature on the target group associated with your Lambda function, you must also respond with a multiValueHeaders field in the format that it expects.

Here’s a complete example of a response your Lambda function would send:

   "statusCode": 200,
   "statusDescription": "200 OK",
   "headers": {
      "Content-Type": "application/json"
   "multiValueHeaders": {
      "Content-Type": [
   "isBase64Encoded": false,
   "body": "{\"hello\":\"world\"}"

How to Use Lambda With Application Load Balancer and API Gateway Simultaneously

It is possible (and fairly simple) to use the same Lambda function behind both API Gateway and an Application Load Balancer. While this may not be something that you do all the time, it can be very helpful to a migration strategy where you’re already using API Gateway and want to transition to an ALB. You can deploy a function that works behind both for that interim period where you’re still running both.

Basically, there are two steps to make your function work in both environments simultaneously:

  • Adjust your code to handle the differences in the event’s request object. The list above should help you with that. The primary things to watch for are:
    • Path parameters don’t exist in ALB, so you might need some evt.path parsing in your function.
    • Headers names are all lower-cased in ALB, so make sure you’re doing a case-insensitive search in the headers object when retrieving request headers.
  • Adjust your response format for ALB when the request comes from ALB. Unfortunately, there’s no “one response object fits all” because of the minor differences in APIGW and ALB - especially the problem mentioned above with statusDescription.
    • Thus, you’ll need to detect if the request came from an ALB (by looking for the presence of the evt.requestContext.elb field), and if the request is from an ALB, add the statusDescription field, make sure that the headers and isBase64Encoded fields are there, and - if you’re receiving multi-value headers, make sure to include your response headers in the same format as multiValueHeaders.

Here’s a complete (and simple) example of responding to both APIGW and ALB requests from the same function:

'use strict';

module.exports.echo = function(evt, context, cb) {
   var resp;

   // What it would look like for API Gateway
   resp = {
      statusCode: 200,
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ hello: "world" }),

   // But if your function is invoked by ALB, you need to do this, too:
   if (evt.requestContext && evt.requestContext.elb) {
      resp.statusDescription = '200 OK';
      resp.isBase64Encoded = false;

      // Similarly, if the ALB is configured to send you multi-value headers for the
      // request, then you must also respond with multi-value headers, so we make this
      // simple map function.
      if (evt.multiValueHeaders) {
         resp.multiValueHeaders = Object.keys(resp.headers).reduce((memo, k) => {
            memo[k] = [ resp.headers[k] ];
            return memo;
         }, {});

   cb(null, resp);


If you want to go try all this out on your own, I’ve already done the work for you. I highly recommend reading the next article in this series which gives you examples you can have deployed and running in minutes so that you can experiment with running a Lambda function behind both API Gateway and Application Load Balancer simultaneously. Check it out.

How to Set Up Application Load Balancer With Lambda

Are there other differences between APIGW and ALB that you found in your experimentatation? Or did this article help you build something cool? Please, leave a comment below!

Stay Informed

Subscribe to the Serving of Serverless newsletter or follow me on Twitter. I’ll keep you current with the serverless world, new training resources, and I will never sell or share your contact information.