Alright, let’s cut through the marketing fluff and talk about what these three API types in API Gateway actually are. You’re not choosing between three fundamentally different technologies; you’re choosing between three different levels of abstraction and feature sets that AWS has packaged up for you. Think of it like ordering a car: REST API is the fully-loaded sedan with every bell and whistle, HTTP API is the zippy, affordable compact car, and WebSocket API is the motorcycle for real-time, two-way communication. They all get you from A to B, but the experience and cost are wildly different.

The Granddaddy: REST API

This is the original, the kitchen sink, the “we built everything you could possibly need and then some” option. When people say “API Gateway,” they’re usually thinking of this one. It’s packed with features like request/response transformation, caching, custom authorizers, and API keys with usage plans. It’s incredibly powerful and equally complex. The “REST” label is a bit of a misnomer; it’s more of a HTTP API that can be used to build RESTful services if you’re disciplined. Out of the box, it doesn’t enforce REST principles—that’s on you.

The main reason you’d pick this now is if you need one of its unique features, like:

  • API Keys and Usage Plans: For simple metering and throttling of API consumers.
  • Request/Response Transformation: Using Velocity Template Language (VTL) to mold the incoming request before it hits your integration or to reshape the response from your backend. This is powerful but feels like writing XML—a special kind of torture.
  • Caching: The ability to cache endpoint responses.

Here’s a simple example using the AWS SDK for Python (Boto3) to create a REST API endpoint that integrates with a Lambda function. Notice the clunky, explicit setup of the integration and method. This is the price of power.

import boto3

apigw = boto3.client('apigateway')

# Create the API (the container)
rest_api = apigw.create_rest_api(name='MyOldReliableAPI')

# Get the root resource ID
root_resource = apigw.get_resources(restApiId=rest_api['id'])
root_resource_id = root_resource['items'][0]['id']

# Create a method on the root resource
apigw.put_method(
    restApiId=rest_api['id'],
    resourceId=root_resource_id,
    httpMethod='GET',
    authorizationType='NONE'
)

# Set up the Lambda integration
apigw.put_integration(
    restApiId=rest_api['id'],
    resourceId=root_resource_id,
    httpMethod='GET',
    type='AWS_PROXY',
    integrationHttpMethod='POST',
    uri=f"arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-1:123456789012:function:MyFunction/invocations"
)

# Deploy it to a stage
apigw.create_deployment(
    restApiId=rest_api['id'],
    stageName='prod'
)

The Modern Upstart: HTTP API

HTTP API is AWS’s attempt to say, “Hey, 90% of you don’t need all that VTL nonsense, you just want a fast, cheap, simple proxy to your Lambda or other service.” And they’re right. It’s significantly cheaper (up to 70% cheaper) and boasts lower latency than REST API. It has built-in JWT authorization, CORS configuration is a breeze, and it’s just… simpler.

You lose the super-advanced features like transformation and API keys, but you gain developer sanity. If you’re building a new serverless application and your auth needs are met by Amazon Cognito or another JWT issuer, this is almost always the correct choice. It’s the default for a reason.

Creating the same GET endpoint is vastly simpler. The create-api command handles the method and integration in one go because it assumes you’re doing the common thing.

aws apigatewayv2 create-api --name MyZippyAPI --protocol-type HTTP --target arn:aws:lambda:us-east-1:123456789012:function:MyFunction

See? No separate steps for put_method and put_integration. It just knows. It’s beautiful.

The Oddball: WebSocket API

This one is a different beast entirely. While REST and HTTP APIs are for request-response communication (you ask, I answer), WebSocket API is for persistent, two-way communication between the client and server. Think chat applications, real-time dashboards, collaborative editing—anything where the server needs to push updates to the client without the client constantly asking.

It manages the WebSocket connection lifecycle for you. Your backend functions are triggered based on the route a message is sent to (e.g., $connect, $disconnect, $default, or a custom route like sendmessage) rather than the HTTP method.

The critical thing to understand here is the connection ID. API Gateway assigns a unique ID to each connected client, and it’s the key to everything. Your backend must store this ID (e.g., in DynamoDB) when a client connects and use it later to send messages back to that specific client.

Here’s a snippet of a Lambda function handling the $connect route:

// AWS Lambda function for WebSocket $connect route
const AWS = require('aws-sdk');
const ddb = new AWS.DynamoDB.DocumentClient();

exports.handler = async (event) => {
    const connectionId = event.requestContext.connectionId;
    const tableName = 'ActiveConnections';

    // Store the connection ID in DynamoDB
    const putParams = {
        TableName: tableName,
        Item: {
            connectionId: connectionId,
            connectedAt: Date.now()
        }
    };

    try {
        await ddb.put(putParams).promise();
        return { statusCode: 200, body: 'Connected.' };
    } catch (err) {
        console.error(err);
        return { statusCode: 500, body: 'Failed to connect: ' + JSON.stringify(err) };
    }
};

So, Which One Do You Pick?

The decision tree is actually pretty simple:

  1. Do you need real-time, two-way communication? -> WebSocket API. No contest.
  2. Do you need API keys, usage plans, or request/response transformation? -> REST API. You’re stuck with it, my friend. Embrace the VTL.
  3. For everything else? -> HTTP API. It’s faster, cheaper, and easier. It’s the right default for the modern, serverless application. Don’t overcomplicate it.