Serverless API with TypeScript on AWS

  • Traditionally APIs are packaged into one deliverable; serverless architecture encourages to divide solutions up into many smaller units that are packaged and deployed individually.
  • Traditionally APIs are stateful applications that are started and stopped infrequently; serverless architecture encourages to start up compute tasks only on demand.
  • Use AWS HTTP Gateway to route HTTP requests to AWS Lambda functions
  • Develop handlers for HTTP routes in TypeScript
  • Deploy every handler in a separate Lambda for minimal cold-start times
  • Generate configuration for the API Gateway automatically by mapping .ts files in a src/routes folder to HTTP routes
  • Have all infrastructure defined in Terraform for easy extensibility and configuration

TL;DR

Overall Architecture

Overview of Architecture

AWS API Gateway HTTP API

  • Provide us with a public HTTP endpoint that clients can call
  • Route requests to Lambdas based on the request path
resource "aws_apigatewayv2_api" "api" {
name = "lambda-api-gateway-${random_id.id.hex}"
description = "API for Goldstack lambda deployment"
protocol_type = "HTTP"
}
resource aws_route53_record a {
type = "A"
name = var.api_domain
zone_id = data.aws_route53_zone.main.zone_id

alias {
evaluate_target_health = false
name = aws_apigatewayv2_domain_name.domain.domain_name_configuration[0].target_domain_name
zone_id = aws_apigatewayv2_domain_name.domain.domain_name_configuration[0].hosted_zone_id
}
}
resource "aws_acm_certificate" "wildcard" {

domain_name = var.api_domain
subject_alternative_names = ["*.${var.api_domain}"]
validation_method = "DNS"

tags = {
ManagedBy = "goldstack-terraform"
Changed = formatdate("YYYY-MM-DD hh:mm ZZZ", timestamp())
}

lifecycle {
ignore_changes = [tags]
}
}
{
"lambdas": {
"$default": {
"function_name": "serverless-api-default_gateway_lambda_2281"
},
"ANY /admin/{proxy+}": {
"function_name": "serverless-api-admin-_proxy__"
},
"ANY /cart/{sessionId}/items": {
"function_name": "serverless-api-cart-_sessionId_-items"
},
"ANY /echo": {
"function_name": "serverless-api-echo"
},
"ANY /order/{id}": {
"function_name": "serverless-api-order-_id_"
},
"ANY /user": {
"function_name": "serverless-api-user-index_root_lambda_4423"
},
"ANY /user/{userId}": {
"function_name": "serverless-api-user-_userId_"
}
}
}

TypeScript Handlers in Lambdas

import { Handler, APIGatewayProxyEventV2 } from 'aws-lambda';

type ProxyHandler = Handler<APIGatewayProxyEventV2, any>;

// eslint-disable-next-line @typescript-eslint/no-unused-vars
export const handler: ProxyHandler = async (event, context) => {
const message = event.queryStringParameters?.message || 'no message';

return {
message: `${message}`,
};
};
resource "aws_lambda_function" "this" {
for_each = var.lambdas

function_name = lookup(each.value, "function_name", null)

filename = data.archive_file.empty_lambda.output_path

handler = "lambda.handler"
runtime = "nodejs12.x"

memory_size = 2048
timeout = 27 # default Gateway timeout is 29 s

role = aws_iam_role.lambda_exec.arn
}
resource "aws_apigatewayv2_integration" "this" {
for_each = var.lambdas
api_id = aws_apigatewayv2_api.api.id
integration_type = "AWS_PROXY"

payload_format_version = "2.0"
connection_type = "INTERNET"
description = "Dynamic lambda integration"
integration_method = "POST"
integration_uri = aws_lambda_function.this[each.key].invoke_arn
}

Route Configuration

resource "aws_apigatewayv2_route" "this" {
for_each = var.lambdas

api_id = aws_apigatewayv2_api.api.id
route_key = each.key

target = "integrations/${aws_apigatewayv2_integration.this[each.key].id}"
}

Conclusion

Further Reading

--

--

--

Technical Lead and explorer of things based in Melbourne, Australia.

Love podcasts or audiobooks? Learn on the go with our new app.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Max Rohde

Max Rohde

Technical Lead and explorer of things based in Melbourne, Australia.

More from Medium

How to use MongoDB with Serverless Cloud

Serverless + Express + DynamoDB (LocalStack)

Unit testing for Node.js Serverless projects with Jest

Serverless Examples