A Practical Introduction to AWS Lambda
Tags: aws, serverless
Published: 2020-05-20 07:33:38

In this tutorial I'm writing about [what AWS Lambda is](#what-is-the-aws-lambda), [how it works](#how-it-works), its [pricing model](#pricing-as-of-march-2020), and how to use [AWS API Gateway with Lambda](#aws-api-gateway) to handle HTTP requests. In the end I'll show how to create a Lambda function to process HTTP requests from an API Gateway route.

## What is the AWS Lambda?

AWS Lambda is a service which allows you to run your code in the cloud in response to some event without provisioning a server. You write some code and tell AWS when to execute it. For example, you can run your code in response to an HTTP request (from the API Gateway service), or process a file when it is uploaded to S3, or run some code when you create a row in DynamoDB, and other use cases. I'm really happy that AWS Lambda provides automatic scaling, it launches as many copies of your code as needed to satisfy the amount of incoming requests (events). This means you can use lambda for web applications without worrying what to do when the traffic grows. Well, you should still worry about the billing, because you pay money according to your usage. Basically, you can start from $0 if there are no events to process (e.g., no visitors), but be cautious, because when your traffic grows your bills grow as well.

### Useful Resources

- [AWS Lambda Homepage](https://aws.amazon.com/lambda/)
- [Common Lambda Application Types and Use Cases](https://docs.aws.amazon.com/lambda/latest/dg/applications-usecases.html)

## How It Works?

First, you create a function for a supported runtime, like Node.js, Python, Ruby, Java, Go, or .NET. Then, you post this function to the AWS Lambda service which invokes it in response to some event (trigger). When your function is invoked, AWS Lambda spins up an execution context for your function and executes it in this context.

The execution context is a temporary runtime environment initialized with the dependencies of your code (like database connections). AWS Lambda maintains the execution context for some time to be able to reuse it for subsequent invocations of the function.

Initializing the context takes time and resources, so AWS Lambda tries to reuse it for your function as much as it can. However, you shouldn't assume that AWS Lambda automatically reuses the context for subsequent function invocations. Various conditions may cause AWS Lambda to initialize a new context.

Let's have a look at a Lambda function example (for the Node.js runtime):

```javascript
const db = createConnection();

export const handler = async (event, context) => {
  const posts = await db.getPosts();
  return posts;
};
```

When invoked, the async handler function receives two arguments from the runtime:

1. `event` is the object which contains some data from the invoker.
2. `context` is the object which contains the data about the invocation, function, and execution environment.

This function returns a promise which resolves to the value of the `posts` variable.

I initialized the database connection outside of the handler function to be able to reuse the connection between the function calls, because establishing a connection on each request is costly.

Also, the discussion about using databases in Lambda (or serverless overall) would take another blog post. You don't want to establish a traditional database connection (e.g., MySQL connection) on every function call, because this will exhaust your database server. So, the simplest and quite naive approach is to share a connection between function invocations like in the above example to reduce the load on the database. A more sophisticated approach would be to use [Amazon RDS Proxy](https://aws.amazon.com/rds/proxy/) to handle connections to relational databases. Or, you can look at using databases which work nicely with serverless out of the box, like DynamoDB, FaunaDB, or Amazon Aurora.

### Useful Resources

- [AWS Lambda Execution Context](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-context.html)
- [AWS Lambda Function Handler in Node.js](https://docs.aws.amazon.com/lambda/latest/dg/nodejs-handler.html)
- [AWS Lambda Function Errors in Node.js](https://docs.aws.amazon.com/lambda/latest/dg/nodejs-exceptions.html)

## Pricing (as of March, 2020)

You pay only for what you use. So, it's great for running a side project (e.g., a web or a mobile app). You pay nothing until you get to some threashold of code invocations.

You pay for the number of _requests_ of your code and the time it takes for your code to execute, the _duration_.

It costs approximately $0.20 per 1M requests and $0.0000166667 for every GB-second. The GB-second unit measures how long your code is running on the machine taking into account the machine's memory (RAM). The more memory you choose for running your code the more you pay for a GB-second.

X GB-seconds = `machine's memory in gigabytes (e.g., 1GB) * X seconds`

Now that we know the prices, let's calculate how much would we pay if our code got 1M requests and each request lasted for half a second, and this code ran on a 512MB machine:

1. We should convert 512MBs to GBs: `512MB / 1024MB = 0.5GB`.
2. Let's calculate how long all the requests lasted: `1,000,000 requests * 0.5 seconds = 500,000 seconds`.
3. Let's calculate the number of GB-seconds: `500,000 seconds * 0.5GB = 250,000 GB-seconds`.
4. Finally, let's calculate the money:

- For requests: `$0.20 / 1M requests * 1M requests = $0.20`
- For GB-seconds: `$0.0000166667 * 250,000 Gb-seconds = approx. $4.17`

So, the total cost would be: `$0.20 + $4.17 = $4.37`

And, good news, AWS Lambda has a free usage tier. The first 1M requests per month and the first 400,000 GB-seconds per month are free.

### Useful Resources

- [AWS Lambda Pricing](https://aws.amazon.com/lambda/pricing/)
- [Understanding and Controlling AWS Lambda Costs](https://serverless.com/blog/understanding-and-controlling-aws-lambda-costs/)

## AWS API Gateway

Earlier I've mentioned that a Lambda function is invoked in response to an event. One of such events may be an HTTP request from AWS API Gateway.

By combining Lambda functions with API Gateway we can build a scalable API. API Gateway is a service which allows you to receive HTTP and WebSocket requests and route them for processing to some backend endpoint, like a Lambda function, or another AWS service like EC2, or some other existing HTTP endpoint.

You can choose one of the following options to build your API:

- **REST API**. This option offers various features for building RESTful APIs.
- **HTTP API**. This option is cheaper than the REST API, but it offers only the API proxy functionality.
- **WebSocket API**. This option offers persistent connections over the WebSocket protocol. You can use it to build chat apps and real-time dashboards.
- **REST API Private**. It's a REST API which is accessible only from a VPC.

API Gateway is a paid service. The pricing differs based on the service you choose: HTTP API, REST API, or WebSocket API. Please check the [pricing page](https://aws.amazon.com/api-gateway/pricing/) for more details.

### Useful resources

- [Amazon API Gateway Documentation](https://docs.aws.amazon.com/apigateway/index.html)
- [Learn about the difference between AWS API Gateway's HTTP API and REST API](https://aws.amazon.com/about-aws/whats-new/2019/12/amazon-api-gateway-offers-faster-cheaper-simpler-apis-using-http-apis-preview/)
- [Amazon API Gateway pricing](https://aws.amazon.com/api-gateway/pricing/)

## Creating the Lambda Function

I assume you already have an AWS account.

Go to the [AWS management console](https://console.aws.amazon.com/console/home), choose a region and search for `Lambda`.

Open the Lambda dashboard and click _Create function_ and fill in the function name (e.g., "hello-world") and choose the runtime (I chose Node.js 12.x):

![Create Function - Basic Information](https://dev-to-uploads.s3.amazonaws.com/i/m7o8lndr5a72iff4mudx.jpg)

Click _Create function_.

Once AWS finishes creating your function it should redirect you to your function's dashboard. Here you can edit the code and various settings, like the function's role, the execution environment's memory, and other settings. You can edit your function's permissions to allow it to access other AWS services.

This is how the initial code of my function looks like:

```javascript
exports.handler = async (event) => {
  // TODO implement
  const response = {
    statusCode: 200,
    body: JSON.stringify('Hello from Lambda!'),
  };
  return response;
};
```

I'll leave it like this.

## Creating the API Route

Search for the `API Gateway` service in the AWS Console.

Go to the service dashboard and click _Create API_. Select _HTTP API_ and click _Build_. Fill in the API name (e.g., "hello-world") and click _Next_.

You'll get to the _Configure routes_ step. We'll add a route later. Click _Next_. Then, you'll get to the _Define stages_ step. Keep the defaults and click _Next_. Finally, you'll get to the _Review and create_ step. Review your selections and click _Create_.

Once AWS creates your API, it should redirect you to your brand new API's dashboard.

Now, let's create a route. In the sidebar, click _Routes_. On the _Routes_ page, click _Create_. Choose _GET_ for the method and enter "/hello-world" for the path:

![Create a Route - method and path](https://dev-to-uploads.s3.amazonaws.com/i/502x5quk5ztubfqheykm.jpg)

Click _Create_.

Now let's attach the lambda function we created earlier to this route. Select the _GET_ method under the "/hello-world" route and click _Attach integration_:

![Create a Route - Attach integration button](https://dev-to-uploads.s3.amazonaws.com/i/hjonbrfrded548hp3m4f.jpg)

Then, click _Create and attach an integration_. In the _Integration with_ section, choose _Lambda function_. In the _integration details_ section select the Lambda function you created earlier ("hello-world"). And click _Create_.

So far you've created the route and attached your "hello-world" lambda to this route. Now to be able to call this route you should deploy the API. Your changes to the API won't get online until you deploy them.

Before you deploy your API for the first time, you should create the stage you'd like to deploy to. A stage is a named reference to a particular deployment of your API. You can create multiple stages, like _dev_, _test_, _prod_, _beta_, etc. For example, you can demo a new version of your API to a client by deploying it to a stage called _beta_.

So, let's create the stage for production naming it _prod_. In the sidebar, click _Stages_, click _Create_. Enter _prod_ for the name and click _Create_.

Finally, in the upper right corner click _Deploy_. Select _prod_ for the stage and describe the changes somehow (e.g, "Initial deployment"). And click _Deploy to stage_.

In the _Stage details_ block, you'll find the _Invoke URL_. This is the base URL of the API you deployed:

![Stage details block](https://dev-to-uploads.s3.amazonaws.com/i/xpyx0ra1oog0zo90w77c.jpg)

Open this URL in the browser and add the path of the route you've created earlier ("/hello-world"):

![Visiting the route in the browser](https://dev-to-uploads.s3.amazonaws.com/i/rnhdn9q68c2ctttlearu.jpg)

The endpoint should display the reponse of your "hello-world" lambda function.

Finally, do not forget to remove both the example function and the API you've created by following this post to keep your account neat.

So, that's it for now. We created a simple API endpoint which is handled by a Lambda function.

In the next blog post I'll explain how to create this setup using the [Serverless framework](https://serverless.com/).

### Useful Resources

- [Deploying a REST API in Amazon API Gateway](https://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-deploy-api.html)