Building a serverless API with email notifications in AWS with Terraform - Part 1
Andre Lopes
Posted on January 9, 2024
Cover Image by Fotis Fotopoulos on Unsplash
Hi earthlings!
In this journey, we'll build a system using Serverless architecture, Terraform, and GitHub Actions. Buckle up as we embark on an adventure.
Our main project will now be a serverless architecture featuring API Gateway, Lambdas, DynamoDB, SNS, and SQS. We'll work with Terraform, our infrastructure-as-code tool, and use GitHub Actions for our continuous integration and deployment, where we'll deploy our infrastructure and lambda apps with a simple push to the main branch.
We'll organize our HTTP methods, configure the AWS provider in Terraform, and even set up an S3 bucket for storing our precious Terraform state.
Let us begin!
Requirements
- An AWS account
- Any Code Editor of your choice - I use Visual Studio Code
- NodeJS
- GitHub account - We'll be using GitHub Actions to deploy our Terraform code
Regarding AWS Costs
Everything we'll be using here is free or has a very low cost that will not be charged unless you add very high usage. If you are afraid of unknown charges, you can set up a $0.01 budget to alert you if you are being charged for anything.
The Project
Here, we are going to be building a whole serverless architecture:
- API Gateway: This is where the endpoints will be mapped and exposed.
- Lambdas: They will handle API Gateway events and the SQS events.
- DynamoDB: It will be our database.
- SQS: Our message queue, where the email notification lambda will be notified whenever a movie is created, deleted, or updated.
- SNS: Notification services to send events to SQS for a fanout pattern
- SES: AWS Simple Email System to manage and send emails from AWS
We'll also be using:
- Terraform: Our infrastructure as code that will create and manage our whole AWS infrastructure.
- GitHub Actions: Our CI/CD, which will build and deploy our infrastructure and our lambdas
In this part 1, we'll be implementing the following components of our project.
Why Serverless?
Serverless computing is a cloud computing model where you don't have to provision or manage servers. Instead, the cloud provider automatically manages the infrastructure, allowing developers to focus solely on writing code and deploying applications.
The term "serverless" doesn't mean no servers are involved. It means you don't have to worry about the underlying server infrastructure.
Some of the benefits of serverless are:
- Cost Savings: You only pay for the computing resources your code consumes.
- Scalability: Serverless platforms automatically scale your applications based on demand without manual intervention.
- Zero Idle Capacity: Your resources are only allocated when needed, so you won't have provisioned resources without being used.
Let's Start
Let's begin our project. We are adding our first lambda to get a movie by its ID.
Lambda Module
Create a folder for your project, and inside it, create a folder named iac
. This is where we'll be adding all our infrastructure as a code. Now create a new folder inside it named modules
. Here, we'll be adding our reusable Terraform modules. And now, add a folder lambda
for our Lambda function module.
Inside the lambda
folder, create three files: main.tf
, datasources.tf
, and variables.tf
.
main.tf:
resource "aws_iam_role" "iam_for_lambda" {
name = "${var.name}-lambda-role"
assume_role_policy = data.aws_iam_policy_document.assume_role.json
}
resource "aws_lambda_function" "lambda" {
filename = data.archive_file.lambda.output_path
function_name = var.name
role = aws_iam_role.iam_for_lambda.arn
handler = var.handler
runtime = "nodejs20.x"
}
We also declare a role, which all lambda functions need, and the lambda code itself in aws_lambda_function
.
Note the keywords data and var. The first is for data from the data sources, and the second is for anything passed to the module through variables.
Now for the variables.tf
:
variable "name" {
description = "The name of the Lambda function"
type = string
nullable = false
}
variable "handler" {
description = "The handler function in your code for he Lambda function"
type = string
default = "index.handler"
}
And for the datasources.tf
:
locals {
filename = strcontains(var.runtime, "node") ? "index.mjs" : "main"
}
data "archive_file" "lambda" {
type = "zip"
source_file = "./modules/lambda/init_code/${local.filename}"
output_path = "${var.name}_lambda_function_payload.zip"
}
data "aws_iam_policy_document" "assume_role" {
statement {
effect = "Allow"
principals {
type = "Service"
identifiers = ["lambda.amazonaws.com"]
}
actions = ["sts:AssumeRole"]
}
}
Here, we define the IAM policy for the lambda role and the file that will be added to the lambda.
This file is required for our Terraform code, even if you are doing deployment in a different flow, which you will be doing.
This is also seen in the filename local variable, where we assign the file depending on the lambda runtime. So, let's add our seed code.
data "aws_iam_policy_document" "lambda_logging" {
statement {
effect = "Allow"
actions = [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
]
resources = ["arn:aws:logs:*:*:*"]
}
}
And then, in the main.tf
, add the policy attachment:
resource "aws_iam_policy" "lambda_logging" {
name = "lambda_logging_${aws_lambda_function.lambda.function_name}"
path = "/"
description = "IAM policy for logging from a lambda"
policy = data.aws_iam_policy_document.lambda_logging.json
}
resource "aws_iam_role_policy_attachment" "lambda_logs" {
role = aws_iam_role.iam_for_lambda.name
policy_arn = aws_iam_policy.lambda_logging.arn
}
Create a folder named init_code
under the lambda
module folder.
For the Node.js seed code, you can create a new file index.mjs
and add the following code:
// Default handler generated in AWS
export const handler = async (event) => {
const response = {
statusCode: 200,
body: JSON.stringify('Hello from Lambda!'),
};
return response;
};
Note that it needs to be mjs
file because we are not adding a project.json
file to define the module. The file needs to be with this extension so Node.js will handle the code as ECMAScript modules.
Adding the main infra code
In the iac
folder, create a lambdas.tf
file with the following code:
module "get_movie_lambda" {
source = "./modules/lambda"
name = "get-movie"
runtime = "nodejs20.x"
handler = "index.handler"
}
We also need to configure Terraform to use AWS as its provider. Create a provider.tf
file with the following code:
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
# Configure the AWS Provider
provider "aws" {
region = var.region
}
Now, create two files, variables.tf
to declare the default variables of our IaC:
variable "region" {
description = "Default region of your resources"
type = string
default = "eu-central-1"
}
And for variables.tfvars
to pass variable values that are not secret, but we might want to change depending on the deployment configuration:
region="eu-central-1" // Chage here to your region here
If you'd like Terraform to keep track of the changes to update the components, you need to add where it will save and manage the state. Here, we'll be using an S3 bucket for that.
Create an S3 bucket with the name terraform-medium-api-notification
and modify the provider.tf
file with the following code:
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
backend "s3" {
bucket = "terraform-medium-api-notification"
key = "state"
region = "eu-central-1" // Chage here to your region here
}
}
# Configure the AWS Provider
provider "aws" {
region = var.region
}
Note that you can choose the region that you are nearest to instead of eu-central-1
. I just chose it because it is the closest to me. You could also provide this through the AWS_DEFAULT_REGION
or AWS_REGION
environment variables.
We are ready to build the workflow to deploy our infrastructure to AWS.
Deploying the infrastructure
To deploy our infrastructure, we'll be using Github Actions.
The CI solutions in GitHub allow us to run scripts in our code when we change it. If you'd like to know more about it, check the documentation here.
To perform this step, you'll need to generate an AWS Access Key and Secret for a user that has the rights to create the resources you define in AWS.
Add these secrets to your repository action secrets in your in Settings
:
Now, in the root folder, let's create a .github
folder and a workflows folder inside of it. Now, create a file named deploy-infrastructure.yml
and add the following code:
name: Deploy Infrastructure
on:
push:
branches:
- main
paths:
- iac/**/*
- .github/workflows/deploy-infra.yml
defaults:
run:
working-directory: iac/
jobs:
terraform:
name: "Terraform"
runs-on: ubuntu-latest
steps:
# Checkout the repository to the GitHub Actions runner
- name: Checkout
uses: actions/checkout@v3
- name: Configure AWS Credentials Action For GitHub Actions
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: eu-central-1 # Use your preferred region
# Install the latest version of Terraform CLI and configure the Terraform CLI configuration file with a Terraform Cloud user API token
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
# Initialize a new or existing Terraform working directory by creating initial files, loading any remote state, downloading modules, etc.
- name: Terraform Init
run: terraform init
# Checks that all Terraform configuration files adhere to a canonical format
- name: Terraform Format
run: terraform fmt -check
# Generates an execution plan for Terraform
- name: Terraform Plan
run: terraform plan -out=plan -input=false -var-file="variables.tfvars"
# On push to "main", build or change infrastructure according to Terraform configuration files
# Note: It is recommended to set up a required "strict" status check in your repository for "Terraform Cloud". See the documentation on "strict" required status checks for more information: https://help.github.com/en/github/administering-a-repository/types-of-required-status-checks
- name: Terraform Apply
run: terraform apply -auto-approve -input=false plan
Every time you push a change to a file inside the iac
folder, it will trigger this action, and it will automatically generate the resources in AWS for you:
Note that in the Terraform Plan
step, terraform outputs all the changes it will perform in AWS based on the current state in the S3 bucket.
Now you can go to AWS on the Lambda page and see your recently created function:
To test it, you click on it and then on the Test
tab.
There, you can give a name to the test event that will be sent to the Lambda and then click on the Test
button.
You should see a success notification with the return from the lambda:
Adding GET endpoint
Now, let's add a GET endpoint through API Gateway so we can call our lambda through HTTP requests.
Let's first create a module for our HTTP methods. Under the folder modules
, create a folder rest-api-method
. Then, create three files: main.tf
, variables.tf
, and outputs.tf
.
For the variables.tf
, add the following code:
variable "http_method" {
description = "The HTTP method"
type = string
}
variable "resource_id" {
description = "The ID of the resource this method is attached to"
type = string
}
variable "api_id" {
description = "The ID of the API this method is attached to"
type = string
}
variable "integration_uri" {
description = "The URI of the integration this method will call"
type = string
}
variable "resource_path" {
description = "The path of the resource"
type = string
}
variable "lambda_function_name" {
description = "The name of the Lambda function that will be called"
type = string
}
variable "region" {
description = "The region of the REST API resources"
type = string
}
variable "account_id" {
description = "The ID of the AWS account"
type = string
}
For the outputs.tf
:
output "id" {
value = aws_api_gateway_method.method.id
}
output "integration_id" {
value = aws_api_gateway_integration.integration.id
}
Now for the main.tf
:
resource "aws_api_gateway_method" "method" {
authorization = "NONE"
http_method = var.http_method
resource_id = var.resource_id
rest_api_id = var.api_id
}
resource "aws_api_gateway_integration" "integration" {
http_method = aws_api_gateway_method.method.http_method
integration_http_method = "POST" # Lambda functions can only be invoked via POST
resource_id = var.resource_id
rest_api_id = var.api_id
type = "AWS_PROXY"
uri = var.integration_uri
}
resource "aws_lambda_permission" "apigw_lambda" {
statement_id = "AllowExecutionFromAPIGateway"
action = "lambda:InvokeFunction"
function_name = var.lambda_function_name
principal = "apigateway.amazonaws.com"
source_arn = "arn:aws:execute-api:${var.region}:${var.account_if}:${var.api_id}/*/${aws_api_gateway_method.method.http_method}${var.resource_path}"
}
This will generate an HTTP method attached to your API and use lambda proxy integration. We want our Lambda to be responsible for the HTTP behavior of the request and response. So, only the API Gateway will pass it through.
Now, in the root iac
folder, create a rest-api.tf
file and add the following code:
# API Gateway
resource "aws_api_gateway_rest_api" "movies_api" {
name = "movies-api"
}
resource "aws_api_gateway_deployment" "movies_api_deployment" {
rest_api_id = aws_api_gateway_rest_api.movies_api.id
stage_name = aws_api_gateway_stage.live.stage_name
triggers = {
redeployment = sha1(jsonencode([
aws_api_gateway_resource.movies_root_resource.id,
module.get_movie_method.id,
module.get_movie_method.integration_id,
]))
}
lifecycle {
create_before_destroy = true
}
}
resource "aws_api_gateway_stage" "live" {
deployment_id = aws_api_gateway_deployment.movies_api_deployment.id
rest_api_id = aws_api_gateway_rest_api.movies_api.id
stage_name = "live"
}
resource "aws_api_gateway_resource" "movies_root_resource" {
parent_id = aws_api_gateway_rest_api.movies_api.root_resource_id
path_part = "movies"
rest_api_id = aws_api_gateway_rest_api.movies_api.id
}
module "get_movie_method" {
source = "./modules/rest-api-method"
api_id = aws_api_gateway_rest_api.movies_api.id
http_method = "GET"
resource_id = aws_api_gateway_resource.movies_root_resource.id
resource_path = aws_api_gateway_resource.movies_root_resource.path
integration_uri = module.get_movie_lambda.invoke_arn
lambda_function_name = module.get_movie_lambda.name
region = var.region
account_id = vat.account_id
}
In the variables.tf, add the variable for account_id:
variable "account_id" {
description = "The ID of the default AWS account"
type = string
}
You can either add your account ID to the variables.tfvars
files, or you can add it as an inline variable to the workflow Terraform Plan
and the Terraform Apply
steps:
# Generates an execution plan for Terraform
- name: Terraform Plan
run: terraform plan -out=plan -input=false -var-file="variables.tfvars" -var account_id="YOUR_ACCOUNT_ID"
# On push to "main", build or change infrastructure according to Terraform configuration files
# Note: It is recommended to set up a required "strict" status check in your repository for "Terraform Cloud". See the documentation on "strict" required status checks for more information: https://help.github.com/en/github/administering-a-repository/types-of-required-status-checks
- name: Terraform Apply
run: terraform apply -auto-approve -input=false plan
Just remember to replace the value of YOUR_ACCOUNT_ID
with your account account ID value.
This will generate an API with the resource movies
, which will be the path /movies
of your API.
It will also create a stage named live
. Stages are the equivalent of deployment environments. You need the API deployed to a stage to call it. So for our movies
endpoint, it will be /live/movies
.
Then, it will create a deployment that will configure a rule that it should deploy to the live
stage whenever we make changes to the method, integration, or resource.
Now, push it to GitHub and wait for the workflow and your API to be created. After it is finished, you can go to the API Gateway page of AWS and see your API.
And when you click on it, you can see all the details about the resources:
To see the public URL, you can go to the Stages section:
Now, if you call the /movies
in your browser, you should get the response from the Lambda
We must make one adjustment to ensure we use the correct path.
We created the resource /movies
and added the method GET
there, but our lambda will fetch a movie by ID in the future, so we need to create a new resource to attach our lambda to it correctly.
So, let's create a new resource by adding the following code to the root rest-api.tf
file:
resource "aws_api_gateway_resource" "movie_resource" {
parent_id = aws_api_gateway_resource.movies_root_resource.id
path_part = "{movieID}"
rest_api_id = aws_api_gateway_rest_api.movies_api.id
}
Add it to the redeployment
trigger in the movies_api_deployment
:
resource "aws_api_gateway_deployment" "movies_api_deployment" {
rest_api_id = aws_api_gateway_rest_api.movies_api.id
triggers = {
redeployment = sha1(jsonencode([
aws_api_gateway_resource.movies_root_resource.id,
aws_api_gateway_resource.movie_resource.id,
module.get_movie_method.id,
module.get_movie_method.integration_id,
]))
}
lifecycle {
create_before_destroy = true
}
}
And then modifying the get_movie_method module
to point to the new resource:
module "get_movie_method" {
source = "./modules/rest-api-method"
api_id = aws_api_gateway_rest_api.movies_api.id
http_method = "GET"
resource_id = aws_api_gateway_resource.movie_resource.id
resource_path = aws_api_gateway_resource.movie_resource.path
integration_uri = module.get_movie_lambda.invoke_arn
lambda_function_name = module.get_movie_lambda.name
}
Push the code to GitHub, and Terraform will modify your infrastructure.
Your API should look like this:
Adding DynamoDB
Now that we have a functioning API let's add our database, DynamoDB, and hook it to our GET
endpoint with some seed data.
Terraforming DynamoDB
So, let's start by adding a new file to our iac
folder named dynamodb.tf
with the following code:
resource "aws_dynamodb_table" "movies-table" {
name = "Movies"
billing_mode = "PROVISIONED"
read_capacity = 1
write_capacity = 1
hash_key = "ID"
range_key = "Title"
attribute {
name = "ID"
type = "S"
}
}
This will generate a minimum capacity table named Movies
, and with a partition key named ID
of type string.
When you push the code to GitHub, and the action runs, you can go to the DynamoDB section of AWS and see the Movies table there.
Let's add a few seed items. In the dynamodb.tf
file, add the following code for four table items:
resource "aws_dynamodb_table_item" "the_matrix" {
table_name = aws_dynamodb_table.movies-table.name
hash_key = aws_dynamodb_table.movies-table.hash_key
range_key = aws_dynamodb_table.movies-table.range_key
item = jsonencode(
{
ID = { S = "1" },
Title = { S = "The Matrix" },
Genres = { SS = [
"Action",
"Sci-Fi",
]
},
Rating = { N = "8.7" }
}
)
}
resource "aws_dynamodb_table_item" "scott_pilgrim" {
table_name = aws_dynamodb_table.movies-table.name
hash_key = aws_dynamodb_table.movies-table.hash_key
range_key = aws_dynamodb_table.movies-table.range_key
item = jsonencode(
{
ID = { S = "2" },
Title = { S = "Scott Pilgrim vs. the World" },
Genres = { SS = [
"Action",
"Comedy",
]
},
Rating = { N = "7.5" }
}
)
}
resource "aws_dynamodb_table_item" "star_wars" {
table_name = aws_dynamodb_table.movies-table.name
hash_key = aws_dynamodb_table.movies-table.hash_key
range_key = aws_dynamodb_table.movies-table.range_key
item = jsonencode(
{
ID = { S = "3" },
Title = { S = "Star Wars: Episode IV - A New Hope" },
Genres = { SS = [
"Action",
"Adventure",
"Fantasy",
"Sci-Fi",
]
},
Rating = { N = "8.6" }
}
)
}
resource "aws_dynamodb_table_item" "star_wars_v" {
table_name = aws_dynamodb_table.movies-table.name
hash_key = aws_dynamodb_table.movies-table.hash_key
range_key = aws_dynamodb_table.movies-table.range_key
item = jsonencode(
{
ID = { S = "4" },
Title = { S = "Star Wars: Episode V - The Empire Strikes Back" },
Genres = { SS = [
"Action",
"Adventure",
"Fantasy",
"Sci-Fi",
]
},
Rating = { N = "8.7" }
}
)
}
Now push to GitHub, wait for the workflow to run, and go to the DynamoDB Table in AWS to explore the table items and see the created records:
Updating the Lambda to fetch by ID
Now that we have our data, we need to modify our lambda to fetch our items.
First, we need to give our Lambda role rights to do GetItem
actions in the Movies
table.
Add the following code to the outputs.tf
file in the lambda module folder:
output "role_name" {
value = aws_iam_role.iam_for_lambda.name
}
Now, in the iac
folder, add a file named iam-polices.tf
with the following code:
data "aws_iam_policy_document" "get_movie_item" {
statement {
effect = "Allow"
actions = [
"dynamodb:GetItem",
]
resources = [
aws_dynamodb_table.movies-table.arn
]
}
}
resource "aws_iam_policy" "get_movie_item" {
name = "get_movie_item"
path = "/"
description = "IAM policy allowing GET Item on Movies DynamoDB table"
policy = data.aws_iam_policy_document.get_movie_item.json
}
resource "aws_iam_role_policy_attachment" "allow_getitem_get_movie_lambda" {
role = module.get_movie_lambda.role_name
policy_arn = aws_iam_policy.get_movie_item.arn
}
This will generate a policy that allows GetItem
in the Movies
table and attach it to the current lambda IAM role.
Now, in the root folder, create a folder named apps
, and then a folder get-movie
.
Inside this folder, let's start a npm project with:
npm init -y
This will generate a new package.json
file.
Most packages required for the lambda to work and connect with AWS are already packed in AWS and updated occasionally. We are creating this mostly to have the packages available in our local development environment and to set up our module types.
In the package.json
file, add the following property:
"type": "module"
Your file should look similar to:
{
"name": "get-movie",
"version": "1.0.0",
"description": "",
"main": "index.js",
"type": "module",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
Note that if the package is unavailable in the AWS environment, you must pack the node_modules folder with your lambda function code. Or create a Lambda layer that will hold the
node_modules
and can be shared between lambdas.
Let's install the packages we'll need with:
npm i --save aws-sdk @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb
Now, create a folder named src
and add a file index.js
in it.
We'll add the code to fetch a movie by its ID with:
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { DynamoDBDocumentClient, GetCommand } from "@aws-sdk/lib-dynamodb";
const tableName = "Movies";
export const handler = async (event) => {
const movieID = event.pathParameters?.movieID;
if (!movieID) {
return {
statusCode: 400,
body: JSON.stringify({
message: "Movie ID missing",
}),
};
}
console.log("Getting movie with ID ", movieID);
const client = new DynamoDBClient({});
const docClient = DynamoDBDocumentClient.from(client);
const command = new GetCommand({
TableName: tableName,
Key: {
ID: movieID.toString(),
},
});
try {
const dynamoResponse = await docClient.send(command);
if (!dynamoResponse.Item) {
return {
statusCode: 404,
body: JSON.stringify({
message: "Movie not found",
}),
};
}
const body = {
title: dynamoResponse.Item.Title,
rating: dynamoResponse.Item.Rating,
id: dynamoResponse.Item.ID,
};
body.genres = Array.from(dynamoResponse.Item.Genres);
const response = {
statusCode: 200,
body: JSON.stringify(body),
};
return response;
} catch (e) {
console.log(e);
return {
statusCode: 500,
body: JSON.stringify({
message: e.message,
}),
};
}
};
This lambda gets the event sent by API Gateway and extracts the movie ID. Then we do some simple validations and get the movie from DynamoDB
, transform the data to an API resource so we don't expose our data model, and return it to the API Gateway to send to the client.
You can see the documentation here if you'd like to learn more about the event from API Gateway with Lambda proxy integration.
Remember to stringify the body, or you'll face 500 errors.
Building and deploying
Lastly, we must create a quick build script to organize our code.
First, install the following package:
npm i -D copyfiles
I'm using it because it makes commands to copy files independent from operating systems.
In the package.json
file, add the following build script:
{
"name": "get-movie",
"version": "1.0.0",
"description": "",
"main": "index.js",
"type": "module",
"scripts": {
"build": "copyfiles -u 1 src/**/* build/ && copyfiles package.json build/",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@aws-sdk/client-dynamodb": "^3.468.0",
"@aws-sdk/lib-dynamodb": "^3.468.0",
"aws-sdk": "^2.1513.0"
},
"devDependencies": {
"copyfiles": "^2.4.1"
}
}
And now, let's add the workflow that will push our code to the get-movie
lambda.
Create a deploy-get-movie-lambda.yml
file in the .github/workflows
folder and add the following code:
name: Deploy Get Movie Lambda
on:
push:
branches:
- main
paths:
- apps/get-movie/**/*
- .github/workflows/deploy-get-movie-lambda.yml
defaults:
run:
working-directory: apps/get-movie/
jobs:
terraform:
name: "Deploy GetMovie Lambda"
runs-on: ubuntu-latest
steps:
# Checkout the repository to the GitHub Actions runner
- name: Checkout
uses: actions/checkout@v3
- name: Setup NodeJS
uses: actions/setup-node@v4
with:
node-version: 20
- name: Configure AWS Credentials Action For GitHub Actions
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: eu-central-1
- name: Install packages
run: npm install
- name: Build
run: npm run build
- name: Zip build
run: zip -r -j main.zip ./build
- name: Update Lambda code
run: aws lambda update-function-code --function-name=get-movie --zip-file=fileb://main.zip
Remember to set the correct region.
Now push the code to GitHub, wait for it to run, and then call the API Gateway endpoint /movies/1
. You should receive a response similar to:
{
"id":"1",
"title":"The Matrix",
"rating":8.7,
"genres":[
"Action",
"Sci-Fi"
]
}
Amazing! We have our first endpoint completed!
Conclusion
In this first part, we built the foundation for our project, a serverless CRUD API with email notification.
We saw how to write and deploy our infrastructure using Terraform, a powerful IaC tool that facilitates how we build and manage our infrastructure.
We wrote our first lambda using NodeJS and easily connected it to DynamoDB to fetch our data.
In the next part, we'll dive deeper and finish our CRUD API with more lambdas, but we'll build some of them using other languages, like Go.
Hope to see you there!
The code for this project can be found here.
Happy coding! 💻
Posted on January 9, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.