Automate Your API Gateway Setup with Boto3: Rendering HTML from Lambda

Creating RESTful endpoints using the AWS Management Console can quickly become frustrating — especially when dealing with API Gateway. If you've ever felt like the Console experience is too slow for repeatable tasks, you're not alone.

In this post, we’ll walk through a Python script using the boto3 library that automates the creation of an AWS API Gateway REST API backed by a Lambda function — and returns HTML, not JSON. This makes it ideal for rendering mobile-friendly forms or web content directly from serverless logic.

Why Use Python Over the Console?

  • Repeatability: Run your script anytime without clicking through dozens of console menus.
  • Version Control: Store and track API configurations in Git.
  • Speed: Automate CORS setup, deployment stages, permissions — in seconds.
  • Precision: Avoid human error by defining everything in code.

Use Case

This script will:

  • Create a REST API in API Gateway.
  • Add a /form route with a GET method.
  • Integrate it with a Lambda function that renders raw HTML (not JSON).
  • Enable CORS for cross-origin usage.
  • Deploy the API and output the public URL.

Python Script

Paste the following into a file, such as deploy_form_api.py:


import boto3

region = 'us-east-1'
lambda_arn = 'arn:aws:lambda:us-east-1:8145480xxxxx:function:FormTestRender'
account_id = '814548xxxxxx'

apigateway = boto3.client('apigateway', region_name=region)
lambda_client = boto3.client('lambda', region_name=region)

def create_rest_api(name):
    print(f"Creating REST API '{name}'...")
    response = apigateway.create_rest_api(
        name=name,
        description='API with Lambda Proxy Integration and full CORS support',
        endpointConfiguration={'types': ['REGIONAL']}
    )
    api_id = response['id']
    print(f"Created REST API with id: {api_id}")
    return api_id

def get_root_resource_id(rest_api_id):
    resources = apigateway.get_resources(restApiId=rest_api_id)
    for r in resources['items']:
        if r['path'] == '/':
            return r['id']
    raise Exception("Root resource not found")

def create_resource(rest_api_id, parent_id, path_part):
    print(f"Creating resource '/{path_part}'...")
    response = apigateway.create_resource(
        restApiId=rest_api_id,
        parentId=parent_id,
        pathPart=path_part
    )
    resource_id = response['id']
    print(f"Resource '/{path_part}' created with id: {resource_id}")
    return resource_id

def add_lambda_permission(rest_api_id):
    statement_id = f'APIInvokePermission-{rest_api_id}'
    source_arn = f'arn:aws:execute-api:{region}:{account_id}:{rest_api_id}/*/GET/formv4'
    try:
        print("Adding permission to Lambda for API Gateway invoke...")
        lambda_client.add_permission(
            FunctionName=lambda_arn,
            StatementId=statement_id,
            Action='lambda:InvokeFunction',
            Principal='apigateway.amazonaws.com',
            SourceArn=source_arn
        )
        print("Permission added.")
    except lambda_client.exceptions.ResourceConflictException:
        print("Lambda permission already exists, skipping.")

def put_method(rest_api_id, resource_id, http_method):
    print(f"Creating {http_method} method with no authorization...")
    apigateway.put_method(
        restApiId=rest_api_id,
        resourceId=resource_id,
        httpMethod=http_method,
        authorizationType='NONE',
        apiKeyRequired=False
    )
    print(f"{http_method} method created.")

def put_integration(rest_api_id, resource_id, http_method, integration_type, uri=None, request_templates=None):
    print(f"Setting up {http_method} integration...")
    params = dict(
        restApiId=rest_api_id,
        resourceId=resource_id,
        httpMethod=http_method,
        type=integration_type
    )
    if integration_type == 'AWS_PROXY':
        params['integrationHttpMethod'] = 'POST'
        params['uri'] = uri
    if integration_type == 'MOCK':
        params['requestTemplates'] = request_templates
    apigateway.put_integration(**params)
    print(f"{http_method} integration created.")

def put_method_response(rest_api_id, resource_id, http_method):
    print(f"Setting method response for {http_method}...")
    apigateway.put_method_response(
        restApiId=rest_api_id,
        resourceId=resource_id,
        httpMethod=http_method,
        statusCode='200',
        responseParameters={
            'method.response.header.Access-Control-Allow-Headers': True,
            'method.response.header.Access-Control-Allow-Methods': True,
            'method.response.header.Access-Control-Allow-Origin': True
        }
    )
    print(f"Method response for {http_method} set.")

def put_integration_response(rest_api_id, resource_id, http_method):
    print(f"Setting integration response for {http_method}...")
    apigateway.put_integration_response(
        restApiId=rest_api_id,
        resourceId=resource_id,
        httpMethod=http_method,
        statusCode='200',
        responseParameters={
            'method.response.header.Access-Control-Allow-Headers': "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'",
            'method.response.header.Access-Control-Allow-Methods': "'GET,OPTIONS'",
            'method.response.header.Access-Control-Allow-Origin': "'*'"
        }
    )
    print(f"Integration response for {http_method} set.")

def deploy_api(rest_api_id, stage_name):
    print(f"Deploying API to stage '{stage_name}'...")
    apigateway.create_deployment(
        restApiId=rest_api_id,
        stageName=stage_name,
        description='Deployment for Lambda Proxy Integration API with CORS'
    )
    print("Deployment complete.")

def main():
    api_name = 'FormRenderAPIv4'
    stage_name = 'formv4'

    rest_api_id = create_rest_api(api_name)
    root_id = get_root_resource_id(rest_api_id)
    resource_id = create_resource(rest_api_id, root_id, 'formv4')

    add_lambda_permission(rest_api_id)

    # GET method setup
    put_method(rest_api_id, resource_id, 'GET')
    uri = f'arn:aws:apigateway:{region}:lambda:path/2015-03-31/functions/{lambda_arn}/invocations'
    put_integration(rest_api_id, resource_id, 'GET', 'AWS_PROXY', uri=uri)
    put_method_response(rest_api_id, resource_id, 'GET')
    put_integration_response(rest_api_id, resource_id, 'GET')

    # OPTIONS method setup for CORS preflight
    put_method(rest_api_id, resource_id, 'OPTIONS')
    put_integration(rest_api_id, resource_id, 'OPTIONS', 'MOCK', request_templates={'application/json': '{"statusCode": 200}'})
    put_method_response(rest_api_id, resource_id, 'OPTIONS')
    put_integration_response(rest_api_id, resource_id, 'OPTIONS')

    deploy_api(rest_api_id, stage_name)

    invoke_url = f"https://{rest_api_id}.execute-api.{region}.amazonaws.com/{stage_name}/formv4"
    print(f"\nYour API Gateway is ready! Invoke URL:\n{invoke_url}")

if __name__ == '__main__':
    main()

What You’ll See

If your Lambda function returns raw HTML (not JSON), the browser will render the HTML directly — which is perfect for mobile-friendly forms or visual outputs.

Example Use Case

  • Create a mobile data collection form using Bootstrap
  • Render it with Lambda
  • Serve it using this API Gateway endpoint

Final Thoughts

Using boto3 and a script-first approach to AWS API Gateway gives you control, speed, and automation — all while avoiding the web console. It’s cleaner, repeatable, and production-ready.

Comments

Popular posts from this blog

Building and Deploying a Fargate Container that runs Python and performs CloudWatch Logging

Setting up an AWS Cognito User Pool and building a React login component

Deploying a Java web application to Amazon ECS