Eric Rybarczyk

Eric Rybarczyk

Enthusiastic and motivated software engineer with diverse experience

AWS & Java Certified Developer

Serverless Cloud API Development

Welcome to the world of AWS Lambda and API Gateway

Eric Rybarczyk

Eyeglasses in front of computer screens
Feature photo by Kevin Ku on Unsplash

If you look closely, yes, there are certainly servers running our serverless applications in the cloud. However, the serverless approach and the platforms that enable it provide impressive capabilities without requiring us to manage the server environment. In this post I will begin developing a basic REST API with a serverless approach on AWS, starting with Lambda and API Gateway.

This series of articles expects that you have basic experience with AWS. If you are looking to kickstart your cloud developer journey, I highly recommend A Cloud Guru. In addition to providing a lot of great learning material, their Cloud Playground lets you spin up an actual - but sandboxed - cloud environment for AWS, Azure, and GCP. This allows you to practice in a real cloud environment without the risk of accidentally paying for the resources you forgot to terminate after your experimentation.

Required Tools

I will be using the AWS Serverless Application Model (SAM) and the SAM CLI (command line interface). The main AWS CLI is also quite useful, including for configuring AWS credentials needed to use the SAM CLI tool.

Java 11 will be used to implement the Lambda functions described here, but you can certainly use any of the languages & platforms that Lambda supports.

A bonus benefit of using a command line tool like SAM CLI is that these tools work extremely well in automated workflows like a CI/CD pipeline.

After your install SAM, a great way to get started is to run   sam init as described in Tutorial: Deploying a Hello World application in the AWS documentation. You can skip the deployment step if you just want to see the generated code.

Source Code

You can find the source code for this project in my GitHub repo. I will start with the Lambda implementation, beginning with a simple Maven-based Java project using IntelliJ and including the dependencies shown below.

Initial Maven Dependencies & Plugins

Initial Data Model

This will be a simple implementation, but I still prefer to pick a subject other than the well traveled To Do List. In keeping with my preferred theme, I’ll go with a very basic Ride Log system, using the data model shown below.

The LocalDateTime accessors are annotated with @JsonSerialize and @JsonDeserialize which, via the using element, provide specific formatting of the string value when marshalled to & from JSON.

package dev.ericrybarczyk.ridelog;

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import java.time.LocalDateTime;

public class RideLog {
  private LocalDateTime startDateTime;
  private LocalDateTime endDateTime;
  private double startLatitude;
  private double startLongitude;
  private double endLatitude;
  private double endLongitude;
  private double distance;
  private String rideTitle;
  private String rideLocation;

  @JsonSerialize(using = LocalDateTimeSerializer.class)
  public LocalDateTime getStartDateTime() {
    return startDateTime;
  }

  @JsonDeserialize(using = LocalDateTimeDeserializer.class)
  public void setStartDateTime(LocalDateTime startDateTime) {
    this.startDateTime = startDateTime;
  }

  @JsonSerialize(using = LocalDateTimeSerializer.class)
  public LocalDateTime getEndDateTime() {
    return endDateTime;
  }

  @JsonDeserialize(using = LocalDateTimeDeserializer.class)
  public void setEndDateTime(LocalDateTime endDateTime) {
    this.endDateTime = endDateTime;
  }

  // remaining simple getters and setters omitted for brevity
}

Lambda Implementation

You might expect the Lambda function to use the RideLog class in the method signature. However, this system will use API Gateway to route HTTP requests & responses to/from the Lambda functions. This makes APIGatewayProxyRequestEvent and APIGatewayProxyResponseEvent the appropriate types. The RideLog instance is obtained from the input event object, using Jackson’s ObjectMapper to deserialize it from the input event.

For this initial implementation, no data processing or persistence is implemented. For now, the code simply generates a random UUID value which simulates an ID value generated when the data object is saved. Following best practices, this ID is used to identify the created resource and is used in the Location HTTP header returned with the 201 response code.

public class CreateRideLogHandler 
        implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {

  @Override
  public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent input, 
                                                    Context context) {

    final String requestBody = input.getBody();
    final ObjectMapper objectMapper = new ObjectMapper();

    try {
        final RideLog rideLog = objectMapper.readValue(requestBody, RideLog.class);
    } catch (JsonProcessingException e) {
        return new APIGatewayProxyResponseEvent()
                .withStatusCode(HttpURLConnection.HTTP_BAD_REQUEST)
                .withBody(e.getMessage());
    }

    // TODO: persistence of the RideLog instance
    final UUID rideLogId = UUID.randomUUID();

    final Map<String, String> headers = new HashMap<>();
    headers.put("Location", "/ridelogs/" + rideLogId.toString());

    return new APIGatewayProxyResponseEvent()
            .withStatusCode(HttpURLConnection.HTTP_CREATED)
            .withHeaders(headers);
  }
}

SAM Template

The AWS Serverless Application Model uses a YAML template file, an extension of CouldFormation templates, to specify the resources and configuration for the application deployment to AWS.

Below is the initial SAM template for this project, adapted from the template provided by sam init. Combined with the JAR file built from the Lambda implementation, this template is all that is necessary to get a basic API Gateway and Lambda function deployed and operational. Impressive indeed!

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  RideLog-Serverless-API
  Demo project with AWS Lambda, API Gateway, and DynamoDB

Globals:
  Function:
    Timeout: 30
    Runtime: java11
    Architectures:
      - x86_64
    MemorySize: 512

Resources:
  CreateRideLogHandlerFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: RideLog-Lambda-Functions
      Handler: dev.ericrybarczyk.ridelog.CreateRideLogHandler::handleRequest
      Events:
        RideLogs:
          Type: Api
          Properties:
            Path: /ridelogs
            Method: Post

Outputs:
  RideLogsStageApi:
    Description: "API Gateway STAGE endpoint URL to POST a Ride Log"
    Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Stage/ridelogs/"
  RideLogsProdApi:
    Description: "API Gateway PRODUCTION endpoint URL to POST a Ride Log"
    Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/ridelogs/"
  CreateRideLogHandlerFunction:
    Description: "CreateRideLogHandler Lambda Function ARN"
    Value: !GetAtt CreateRideLogHandlerFunction.Arn
  CreateRideLogHandlerFunctionIamRole:
    Description: "Implicit IAM Role created for CreateRideLogHandler Lambda Function"
    Value: !GetAtt CreateRideLogHandlerFunction.Arn

The optional Outputs section of the SAM template is quite useful, allowing you to retrieve and display values that are not available until AWS generates the resources during the deployment. The intrinsic functions !Sub and !GetAtt (using the short-form syntax) are used here to obtain the generated URL for the API Gateway endpoints, as well as generated ARN values.

Try It Out

The SAM toolkit is used to build and deploy the serverless application. To compile the code, run the sam build command, from the directory containing the template.yaml file. The CodeUri property indicates the location of the Lambda function source code to be built. Alternatively, the -t parameter allows you to specify a different location for the template.

Build
$ sam build
Building codeuri: /some/path/RideLog-Serverless-API/RideLog-Lambda-Functions runtime: java11 metadata: {} architecture: x86_64 functions: ['CreateRideLogHandlerFunction']
Running JavaMavenWorkflow:CopySource
Running JavaMavenWorkflow:MavenBuild
Running JavaMavenWorkflow:MavenCopyDependency
Running JavaMavenWorkflow:MavenCopyArtifacts

Build Succeeded

Built Artifacts  : .aws-sam/build
Built Template   : .aws-sam/build/template.yaml

Commands you can use next
=========================
[*] Invoke Function: sam local invoke
[*] Test Function in the Cloud: sam sync --stack-name {stack-name} --watch
[*] Deploy: sam deploy --guided

Local Execution

SAM also supports local execution of the application. This requires Docker on your local system, and the first time you run this for a serverless application, the tool will download a Docker image from AWS to provide the runtime for this invocation. Subsequent invocations will reuse this image, as shown in the example output below.

$ sam local invoke CreateRideLogHandlerFunction -e ./events/post-event.json 
Invoking dev.ericrybarczyk.ridelog.CreateRideLogHandler::handleRequest (java11)
Skip pulling image and use local one: public.ecr.aws/sam/emulation-java11:rapid-1.36.0-x86_64.

Mounting /some/path/RideLog-Serverless-API/.aws-sam/build/CreateRideLogHandlerFunction as /var/task:ro,delegated inside runtime container
START RequestId: 078c8d00-ede8-4b70-8c78-45333d1205a3 Version: $LATEST
{"statusCode":201,"headers":{"Location":"/ridelogs/a66911ff-956d-4b9e-8f45-5add7db0a908"}}END RequestId: 078c8d00-ede8-4b70-8c78-45333d1205a3
REPORT RequestId: 078c8d00-ede8-4b70-8c78-45333d1205a3	Init Duration: 0.23 ms	Duration: 865.80 ms	Billed Duration: 866 ms	Memory Size: 512 MB	Max Memory Used: 512 MB	

Of particular interest in the output above is the response from the Lambda execution:
{“statusCode”:201,“headers”:{“Location”:"/ridelogs/a66911ff-956d-4b9e-8f45-5add7db0a908"}}
This simply confirms the Lambda implementation was executed.

Deployment

Proceed at your own risk. Running the deploy command will create resources in your AWS account, and may result in charges on your AWS bill.

TIP: If you have access to a paid A Cloud Guru account, you can use their Cloud Playground and configure your AWS CLI to use the access keys provided when you launch an AWS Sandbox. This will allow you to deploy resources to the Sandbox rather than to your own AWS account.

Using the --guided option with the sam deploy command provides an interactive deployment with prompts for a variety of options, as shown below. Simply pressing enter accepts the default value for that item.

$ sam deploy --guided 

Configuring SAM deploy
====================== 

	Looking for config file [samconfig.toml] :  Not found

	Setting default arguments for 'sam deploy'
	========================================= 
	Stack Name [sam-app]: ridelogs-api
	AWS Region [us-east-1]: 
	#Shows you resources changes to be deployed and require a 'Y' to initiate deploy
	Confirm changes before deploy [y/N]: N
	#SAM needs permission to be able to create roles to connect to the resources in your template
	Allow SAM CLI IAM role creation [Y/n]: Y
	#Preserves the state of previously provisioned resources when an operation fails
	Disable rollback [y/N]: N
	CreateRideLogHandlerFunction may not have authorization defined, Is this okay? [y/N]: Y
	Save arguments to configuration file [Y/n]: Y
	SAM configuration file [samconfig.toml]: 
	SAM configuration environment [default]: 

While the stack is being deployed, the CLI will update with status for each resource, such as CREATE_IN_PROGRESS and CREATE_COMPLETE. Upon successful completion, the result of the Outputs section of the template.yaml file will be displayed, such as the example shown below.

CloudFormation outputs from deployed stack
---------------------------------------------------------------------------------------------------------------------------
Outputs
---------------------------------------------------------------------------------------------------------------------------
Key                 RideLogsStageApi                                                                                                              
Description         API Gateway STAGE endpoint URL to POST a Ride Log                                                                             
Value               https://da----9f.execute-api.us-east-1.amazonaws.com/Stage/ridelogs/

Key                 RideLogsProdApi                                                                                                               
Description         API Gateway PRODUCTION endpoint URL to POST a Ride Log                                                                        
Value               https://da----9f.execute-api.us-east-1.amazonaws.com/Prod/ridelogs/

Key                 CreateRideLogHandlerFunction                                                                                                  
Description         CreateRideLogHandler Lambda Function ARN                                                                                      
Value               arn:aws:lambda:us-east-1:1234567890:function:ridelogs-api-CreateRideLogHandlerFunction-0x----SG

Key                 CreateRideLogHandlerFunctionIamRole                                                                                           
Description         Implicit IAM Role created for CreateRideLogHandler Lambda Function                                                            
Value               arn:aws:lambda:us-east-1:1234567890:function:ridelogs-api-CreateRideLogHandlerFunction-0x----SG
---------------------------------------------------------------------------------------------------------------------------

AWS Console

After the deployment completes, the resources created can be seen in the AWS console. First, the CloudFormation stack is named according to the Stack Name [sam-app]: ridelogs-api value used in the deployment. You can review all the resources created.

CloudFormation stack resources

The API Gateway resources and endpoint information, with Lambda proxy integration, can be reviewed and even tested using the built-in testing capability in the web console.

API Gateway resources

Finally, the deployed Lambda function can be reviewed, and likewise tested using the built-in testing options in the web console.

Lambda function

External Test via Postman

Using the endpoint provided by the SAM CLI Outputs, or obtained from the AWS console, we can run a simple test in Postman.

Postman request

In addition to the successful 201 created response code, we see the expected Location HTTP header.

Postman response

Cleanup

When you are finished exploring the results of the deployment, you likely want to terminate all the resources created to minimize any costs. The SAM tool provides the sam delete command which you can run to delete the entire CloudFormation stack that was created by the deployment action.

Where Do We Go From Here?

We now have a functional, deployed API working with a simple Lambda function. However, we only have a POST endpoint, and we aren’t actually handling the inbound data yet. We will tackle persistence next, serverless-style.

 


Article Series

Software Projects

Recent Posts

Categories

About

Enthusiastic and motivated software engineer