DEV Community

Aakash Choudhary
Aakash Choudhary

Posted on

What’s My IP — Build a Serverless API Using Lambda + API Gateway (with Terraform)

Image description

Want to integrate AWS Lambda to an API endpoint? This project will walk you through the exact steps needed to attach Lambda with API Gateway to returning the caller’s public IP using a simple Python Lambda.

What We’ll Build

In this article, we have a lightweight Python function running inside AWS Lambda. The function gets triggered when a user hits an API Gateway (HTTP API) URL. Once called, it grabs the user’s public IP and current timestamp and sends that back in the response.

Example of URL provided by API Gateway:

curl https://<your-api>.execute-api.<region>.amazonaws.com/<stage>/

Output on browser upon hitting URL:

{
  "timestamp": "2025-06-20T17:45:12.841111",
  "ip_address": "103.xx.x.xx"
}
Enter fullscreen mode Exit fullscreen mode

What’s happening behind the scenes ( Overview of request)

Image description

As a USER, you will be hitting a URL( that is provided by API Gateway ). Upon hitting the URL, the request will flow from API Gateway to AWS Lambda, which will invoke the python application.

You (the user) hit the URL provided by API Gateway.
API Gateway forwards the request to Lambda.
Lambda runs the Python code and sends back a response with your IP and the current timestamp.
Enter fullscreen mode Exit fullscreen mode

The python application will take “event” metadata from the API Gateway and use that to return the sourceIP. The timestamp is returned using the datetime() function.

Python Application

Here’s the Python function we’re using


import json
import datetime

def lambda_handler(event, context):
    # Fetch the IP from the API Gateway event
    ip_address = event['requestContext']['http']['sourceIp']

    # Get the current timestamp
    timestamp = datetime.datetime.utcnow().isoformat()
    response = {
            'timestamp': timestamp,
            'ip_address': ip_address
        }
        return {
            'statusCode': 200,
            'headers': { 'Content-Type': 'application/json' },
            'body': json.dumps(response)
        }
Enter fullscreen mode Exit fullscreen mode

event” Metadata from API Gateway

I believe the only component worth exploring here is:

ip_address = event['requestContext']['http']['sourceIp']
Enter fullscreen mode Exit fullscreen mode

event” is returned by API Gateway and contain some metadata that can be consumed by Lambda. This include the sourceIP field as well, that we will be using to display output on hitting URL.

Metadata in the event context of API Gateway

{
  "requestContext": {
    "accountId": "123456789012",
    "apiId": "a1b2c3d4",
    "http": {
      "method": "GET",
      "path": "/",
      "protocol": "HTTP/1.1",
      "sourceIp": "203.0.113.42",
      "userAgent": "curl/7.64.1"
    },
    "requestId": "abc123",
    "routeKey": "GET /",
    "stage": "$default",
    "time": "20/Jun/2025:10:45:00 +0000",
    "timeEpoch": 1718877900000
  }
}
Enter fullscreen mode Exit fullscreen mode

Why we don’t need any layer for this Python Lambda?

The packages that we are using ( json , datetime ), are part of Python’s standard library, and AWS Lambda already includes the entire standard library for whatever runtime we’re using (like python3.12, python3.11, etc.).

Tech Stack

  • Python 3.12
  • AWS Lambda
  • API Gateway (HTTP API)
  • Terraform
  • CloudWatch (for logging/debugging)

Project Structure

.
├── terraform/
│ ├── modules/
│ │ ├── iam/
│ │ │ ├── main.tf
│ │ │ └── outputs.tf
│ │ ├── lambda/
│ │ │ ├── main.tf
│ │ │ ├── variables.tf
│ │ │ └── outputs.tf
│ │ └── apigw/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ └── outputs.tf
│ ├── main.tf
│ ├── outputs.tf
│ └── provider.tf
├── app.py
├── .gitignore
└── README.md

CODE REPOSITORY ( follow the URL for Github Repository)

Follow the below github repository for the terraform code

https://github.com/ifaakash/whats-my-ip-lambda

Terraform Code Highlight

From terraform POV, we will need to create the components listed below:

`aws_lambda_function` → Lambda function configuration
`aws_apigatewayv2_api` → API for the end user
`aws_lambda_permission` → Ensure API GW have permission to invoke Lambda
`aws_apigatewayv2_integration` → Attach API GW with Lambda function
`aws_api_gatewayv2_route` → Define route for API GW to invoke Lambda
`aws_api_gatewayv2_stage` → Used to handle API GW in multi env( dev, stg, prod )
`aws_iam_role` and policy for Lambda execution
Enter fullscreen mode Exit fullscreen mode

Why create the IAM Role?

Without IAM role, the lambda function will not be able to perform operation like writing logs to cloudwatch, read from S3 bucket etc. So, we need IAM role to allot permission to AWS Lambda.

# IAM Role for AWS Lambda to assume
resource "aws_iam_role" "lambda_exec_role" {
  name = "lambda_exec_role"
  assume_role_policy = jsonencode({
    Version = "2012-10-17",
    Statement = [{
      Action = "sts:AssumeRole",
      Effect = "Allow",
      Principal = {
        Service = "lambda.amazonaws.com"
      }
    }]
  })
}
Enter fullscreen mode Exit fullscreen mode

# Required permission for AWS lambda to perform action on other resources
resource "aws_iam_policy" "permissions" {
  name        = "lambda_permissions"
  description = "Required IAM permission for AWS Lambda Execution"
  policy = jsonencode(
    {
      "Version" : "2012-10-17",
      "Statement" : [
        {
          "Effect" : "Allow",
          "Action" : [
            "logs:CreateLogGroup",
            "logs:CreateLogStream",
            "logs:PutLogEvents"
          ],
          "Resource" : "*"
        }
      ]
    }
  )
}
Enter fullscreen mode Exit fullscreen mode
resource "aws_iam_role_policy_attachment" "lambda_policy_attachment" {
  role       = aws_iam_role.lambda_exec_role.name
  policy_arn = aws_iam_policy.permissions.arn
}
Enter fullscreen mode Exit fullscreen mode

Permission for AWS Lambda

This will allow the API Gateway to invoke the lambda function, when the user hit the API URL.

resource "aws_lambda_permission" "allow_apigw" {
  statement_id  = "AllowAPIGatewayInvoke"
  action        = "lambda:InvokeFunction"
  principal     = "apigateway.amazonaws.com"
  function_name = var.lambda_name
}

Enter fullscreen mode Exit fullscreen mode

Lambda function Configuration

This block defines the python application that need to be used by AWS Lambda and the HASH code for the application to detect changes to the ZIP file.

resource "aws_lambda_function" "lambda" {
  function_name    = var.function_name
  role             = var.lambda_exec_role_arn
  description      = var.description
  filename         = var.filename # <zip_file_name>
  runtime          = var.runtime_env
  package_type     = var.package_type
  handler          = var.handler # <python_file_name.lambda_function_name>
  source_code_hash = filebase64sha256(var.filename)
}
Enter fullscreen mode Exit fullscreen mode

Deploying It

→ Initialize using terraform init

→ Plan the changes using terraform plan

→ Apply the changes using terraform apply

→ Grab the endpoint from API Gateway

output "api_url" {
  value = "${aws_apigatewayv2_api.http_api.api_endpoint}/${aws_apigatewayv2_stage.dev.name}/"
}
Enter fullscreen mode Exit fullscreen mode

Test the API

curl https://<api-id>.execute-api.us-west-1.amazonaws.com/dev/

Make sure the route is GET /, not /dev or /ping unless explicitly defined.

Cleaning Up

To destroy all resources

terraform destroy

Follow for More

If this helped or sparked an idea, drop a comment, a clap, or reach out!

[Github Repo](https://github.com/ifaakash)
[My Portfolio](https://hey-its-aakash.lovable.app/)
[LinkedIn](https://www.linkedin.com/in/aakashch2/)
Enter fullscreen mode Exit fullscreen mode

Top comments (1)

Collapse
 
asobko profile image
Alejandro Sobko • Edited

hey! that was very interesting! I wonder why you need AWS for this? it's to host your py code, or for another performance aspect? could you have used Azure or another random app hosting?