# Creating AWS Lambda Function URLs with LocalStack

AWS Lambda is a compute service that lets you run code without provisioning or managing servers. AWS recently launched support for [Function URL](https://aws.amazon.com/blogs/aws/announcing-aws-lambda-function-urls-built-in-https-endpoints-for-single-function-microservices/) that allows you to invoke your Lambda function using a URL. It is a simplistic solution to create HTTP(s) endpoints for your Lambda functions and invoke them using requests.

Before introducing the Lambda Function URL, we would have to invoke the Lambda functions through the API Gateway. Now you can create a Lambda function and create a Function URL with little to no customizations. When the Lambda Function URL is invoked, the `$LATEST` version of the code is automatically retrieved and executed. You can further test it using `cURL`, Postman, or any other API testing tool.

LocalStack supports Lambda Function URL, and you can use it to test your Lambda functions locally via HTTP(s). You can go from creating a Lambda function locally to invoking it using a URL in just a few steps for testing and integration. We will be using creating a Lambda Function URL to scrap the [GitHub Trending page](https://github.com/trending) and return the top repositories in a JSON response.

![Architecture of Lambda Function URL – Users will invoke a REST API which will trigger a Lambda function and return the scrapped web data from GitHub as a JSON response](https://cdn.hashnode.com/res/hashnode/image/upload/v1667392286791/EFMp9em4k.png align="left")

## Prerequisites

For this tutorial, you will need the following:

- [LocalStack](https://github.com/localstack/localstack) to create a Lambda Function URL
- [`awslocal` command-line interface](https://github.com/localstack/awscli-local) to run local AWS commands against LocalStack
- [Terraform](https://terraform.io) & `tflocal` to automatically create a local infrastructure against LocalStack
- [`jq`](https://stedolan.github.io/jq/) & [`cURL`](https://curl.se/)

If you have not installed LocalStack before, you can do it via `pip`:

```bash
pip install localstack
```

You can then start LocalStack inside a Docker container by running:

```bash
     __                     _______ __             __
    / /   ____  _________ _/ / ___// /_____ ______/ /__
   / /   / __ \/ ___/ __ `/ /\__ \/ __/ __ `/ ___/ //_/
  / /___/ /_/ / /__/ /_/ / /___/ / /_/ /_/ / /__/ ,<
 /_____/\____/\___/\__,_/_//____/\__/\__,_/\___/_/|_|

 💻 LocalStack CLI 1.2.0

[12:03:04] starting LocalStack in Docker mode 🐳                                                        localstack.py:140
           preparing environment                                                                         bootstrap.py:667
           configuring container                                                                         bootstrap.py:675
           starting container                                                                            bootstrap.py:681
[12:03:06] detaching    
```

LocalStack will now run on `localhost:4566`, and you can access multiple AWS services locally, including AWS Lambda.

## Getting started with Lambda

AWS Lambda is a Serverless Function as a Service (FaaS) system that allows you to write code in your favourite programming language and run it on the AWS ecosystem. Unlike deploying your code on a server, you can now break down your application into many independent functions and deploy them as singular units.

Using LocalStack, as a cloud service emulator, you can deploy your Lambda functions over a mock AWS environment. You can tightly integrate it with other AWS services, like S3, SQS, and more, while running everything inside an ephemeral environment which can be tested and debugged rapidly. 

A standard Python Lambda function looks similar to this:

```python
import json

def lambda_handler(event, context): 
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }
```

We have a `lambda_handler` function executed by the Lambda service every time a trigger event occurs. We would specify this function as an entrypoint for the Lambda function inside a runtime environment (`python3.9` in our case). We have also specified `event` and `context`, the two arguments containing detailed information about the event and the properties that provide information about the invocation.

Let us create a Python script to scrap [GitHub Trending](https://github.com/trending) repositories. We will use `requests` and `bs4` as external libraries for our purpose. Create a file named `lambda_function.py` and add the following code there:

```python
import json
import requests
from bs4 import BeautifulSoup as bs

def trending():
    url = "https://github.com/trending"
    page = requests.get(url)
    soup = bs(page.text, 'html.parser')
    data = {}
    repo_list = soup.find_all('article', attrs={'class':'Box-row'})
    for repo in repo_list:
        full_repo_name = repo.find('h1').find('a').text.strip().split('/')
        developer_name = full_repo_name[0].strip()
        repo_name = full_repo_name[1].strip()
        data[developer_name] = repo_name
    return data

def lambda_handler(event, context):
    data = trending()
    return {
        'statusCode': 200,
        'body': json.dumps(data)
    } 
```

In the above function, we use `BeautifulSoup` to scrap the Trending page and store all the repositories inside a dictionary before returning the data to be sent as a JSON response. We will also add a `requirements.txt` file to allow us to add dependencies to our deployment package:

```txt
requests
bs4
```

Let us create a deployment package for our Lambda function:

```bash
$ pip install -r requirements.txt -t .
$ zip -r function.zip .
```

It will install all the dependencies inside our root directory and create a deployment package named `function.zip`. Let us go ahead and create a Lambda function now:

```bash
$ awslocal lambda create-function \
    --function-name trending \
    --runtime python3.9 \
    --timeout 10 \
    --zip-file fileb://function.zip \
    --handler lambda_function.lambda_handler \
    --role cool-stacklifter

{
    "FunctionName": "trending",
    "FunctionArn": "arn:aws:lambda:us-east-1:000000000000:function:trending",
    "Runtime": "python3.9",
    "Role": "cool-stacklifter",
    "Handler": "lambda_function.lambda_handler",
    "CodeSize": 1545070,
    "Description": "",
    "Timeout": 10,
    "LastModified": "2022-10-17T17:15:32.573+0000",
    "CodeSha256": "5Bck4osMFtRqssI1TSzHyrG2aM46BBpcXdZDF0+4s70=",
    "Version": "$LATEST",
    "VpcConfig": {},
    "TracingConfig": {
        "Mode": "PassThrough"
    },
    "RevisionId": "ace2eb2a-767f-4be6-9299-d31aa5de0eae",
    "State": "Active",
    "LastUpdateStatus": "Successful",
    "PackageType": "Zip",
    "Architectures": [
        "x86_64"
    ]
}
```

In the above command, we have outlined the function name `trending` and the runtime environment, zip file for our deployment package, handler, timeout seconds, and role. The Lambda function has been created, and you can comfortably invoke it using the `awslocal` CLI:

```bash
$ awslocal lambda invoke --function-name trending output.txt
```

In `output.txt`, you will find a JSON response containing all the trending repositories on GitHub right now.

## Creating a Lambda Function URL

Let us go ahead and create a Lambda Function URL for the above Lambda. To create a Lambda Function URL, all you need to do is to use the `create-function-url-config` command and specify the Lambda Function name that we created earlier:

```bash
$ awslocal lambda create-function-url-config \
    --function-name trending \
    --auth-type NONE

{
    "FunctionUrl": "http://607a5a09d5d45a8febd22d62c9bb672e.lambda-url.us-east-1.localhost.localstack.cloud:4566/",
    "FunctionArn": "arn:aws:lambda:us-east-1:000000000000:function:trending",
    "AuthType": "NONE",
    "CreationTime": "2022-10-17T17:23:07.224906Z"
}
```

The Lambda Function URL that is created would be visible in the following format:

```bash
https://<url-id>.lambda-url.<region>.on.aws
```

You can now test the above URL using `cURL` or Postman. Let us call our Lambda Function URL using `cURL`:

```bash
$ curl -X GET http://607a5a09d5d45a8febd22d62c9bb672e.lambda-url.us-east-1.localhost.localstack.cloud:4566/ | jq

{
  "dragonflydb": "dragonfly",
  "dunglas": "frankenphp",
  "cisagov": "RedEye",
  ....
}
```

Note that the above domain name is unique to the Lambda function and ephemeral in nature. If you spin down LocalStack, the above URL would no longer be accessible. By invoking the URL, you invoke the Lambda function handler and get a JSON output alongside a status code.

## Deploying a Lambda Function URL via Terraform

![Simplistic description of how Terraform works](https://cdn.hashnode.com/res/hashnode/image/upload/v1667392422749/pIfcVR7mP.png align="left")

You can automate the above process by orchestrating your AWS infrastructure using Terraform. Terraform allows you to automate the management of AWS resources such as Containers, Lambda functions and so on by declaring them in the HashiCorp Configuration Language (HCL). Terraform ships with [`terraform` CLI](https://learn.hashicorp.com/tutorials/terraform/install-cli), which allows you to initialize the Terraform backend and apply the resources in a declarative manner.

To create a local infrastructure against LocalStack, we will use the [`tflocal` CLI](https://github.com/localstack/terraform-local). `tflocal` uses the Terraform Override mechanism to create a temporary `localstack_providers_override.tf` file which is deleted after the infrastructure is created. You can install it via `pip`:

```bash
 pip install terraform-local
```

Let us create a new file named `main.tf`, and use the `aws_lambda_function` resource type to create a new Lambda function:

```tf
resource "aws_lambda_function" "trending" {
  filename         = "function.zip"
  function_name    = "trending"
  role             = "cool-stacklifter"
  handler          = "lambda_function.lambda_handler"
  source_code_hash = filebase64sha256("function.zip")
  runtime          = "python3.9"
}
```

We can now use the `aws_lambda_function_url` resource type to create a Lambda Function URL:

```tf
resource "aws_lambda_function_url" "lambda_function_url" {
  function_name      = aws_lambda_function.trending.arn
  authorization_type = "NONE"
}

output "function_url" {
  description = "Function URL."
  value       = aws_lambda_function_url.lambda_function_url.function_url
}
```

The final configuration in our `main.tf` file would look like this:

```tf
resource "aws_lambda_function" "trending" {
  filename         = "function.zip"
  function_name    = "trending"
  role             = "cool-stacklifter"
  handler          = "lambda_function.lambda_handler"
  source_code_hash = filebase64sha256("function.zip")
  runtime          = "python3.9"
}

resource "aws_lambda_function_url" "lambda_function_url" {
  function_name      = aws_lambda_function.trending.arn
  authorization_type = "NONE"
}

output "function_url" {
  description = "Function URL."
  value       = aws_lambda_function_url.lambda_function_url.function_url
}
```

Let us go ahead and initialize our Terraform configuration and apply them:

```bash
$ tflocal init

...
Terraform has been successfully initialized!
...

$ tflocal apply --auto-approve

...
aws_lambda_function.trending: Creating...
aws_lambda_function.trending: Creation complete after 7s [id=trending]
aws_lambda_function_url.lambda_function_url: Creating...
aws_lambda_function_url.lambda_function_url: Creation complete after 0s [id=arn:aws:lambda:us-east-1:000000000000:function:trending]

Apply complete! Resources: 2 added, 0 changed, 0 destroyed.

Outputs:

function_url = "http://d8d26ebfb3fedc0cbb0e4c97bd0612ab.lambda-url.us-east-1.localhost.localstack.cloud:4566/"
...
```

Since we are using LocalStack, Terraform will create no actual AWS resources. Instead, LocalStack will create ephemeral development resources, which will automatically be cleaned once you stop LocalStack (using `localstack stop`). You can now use `cURL` with the `function_url` again to test the Lambda Function invocation.

## Conclusion

While Lambda Function URLs solve well for simplistic use-cases, like creating webhooks or form validators, as it sends the HTTP(s) requests to a single Lambda function. For a more complex use case, [API Gateway](https://docs.localstack.cloud/aws/apigatewayv2/) serves better since it routes requests to various Lambda functions and requires advanced authorization. Using LocalStack, you can develop, debug, and test your Lambda functions in conjunction with a wide range of AWS services. Check out our other blogs on how to [hot-reload your Lambda functions using LocalStack](https://localstack.cloud/blog/2022-03-07-hot-swapping-python-lambda-functions-using-localstack/) and [debug it straight from your IDE](https://localstack.cloud/blog/2022-05-09-debug-your-lamda-functions-from-your-ide-using-localstack/)!

Check out the above [code](https://github.com/localstack/localstack-pro-samples/tree/master/lambda-function-urls/python) and our [extended Lambda Function URL documentation](https://docs.localstack.cloud/aws/lambda/#lambda-function-url). Thanks to [Hashicorp's `What is Infrastructure as Code with Terraform?
` tutorial](https://developer.hashicorp.com/terraform/tutorials/aws-get-started/infrastructure-as-code) for the picture used to describe how Terraform works.



