Roger Felipe Nsk
open main menu
blog placeholder

Deploy função lambda em nodejs com OpenTofu na AWS

/ 4 min read
Last updated:

Como Criar uma Lambda na AWS com Terraform: API com Express.js

Hoje em dia, ferramentas de automação de Infraestrutura como Código (IaC) como Terraform e OpenTofu são amplamente utilizadas para provisionar recursos na nuvem. A Amazon Web Services (AWS) é uma das principais plataformas de nuvem que suporta o OpenTofu, permitindo que você crie e gerencie recursos de forma programática.

Sem uma ferramenta de automação, você teria que criar manualmente cada recurso na AWS, o que pode ser demorado e propenso a erros. Com o OpenTofu, você pode definir sua infraestrutura como código e implantá-la de forma consistente e confiável.

É comum usar funções Lambda da AWS para executar código sem precisar provisionar ou gerenciar servidores. Neste artigo, vamos criar uma função Lambda com uma API usando Nodejs, Api Gateway e OpenTofu.

Esse é o primeiro passo, depois iremos melhorar o projeto adicionando CI/CD, testes e monitoramento.

Eu utilizei uma forma diferente de realizar a explicação do código, em cada bloco de código tem um áudio que explica o código e como ele funciona, espero que goste dessa abordagem.

Pré-requisitos

Certifique-se de ter as seguintes ferramentas instaladas em seu sistema:

  1. Node.js
  2. OpenTofu
  3. AWS-Cli

Passo 1: Configurar o Projeto Express.js

Ouça a explicação:

Primeiro, crie um novo diretório para o seu projeto e inicialize um novo projeto Node.js:

mkdir lambda-express-api
cd lambda-express-api
npm init -y

Instale as dependências que iremos utilizar:

npm i express serverless-http

Instale as dependências de desenvolvimento que iremos utilizar:

npm i -D nodemon

Alterar o package.json e adicionar type: module

{
  ...
  "type": "module",
  ...
}

Adicione o seguinte script no arquivo package.json:

{
  ...
  "scripts": {
    "start": "node local-server.mjs",
    "dev": "npx nodemon local-server.mjs"
  },
  ...
}

Crie um arquivo index.mjs e adicione o seguinte código:

import express from "express";
const app = express();

app.use(express.json());

app.get("/hello", (req, res) => {
  res.send("Hello from Lambda!");
});

export default app;

Crie um arquivo chamado handler.mjs para definir a função Lambda:

import serverless from "serverless-http";
import app from "./index.mjs";

const handler = serverless(app);
export { handler };

Crie um arquivo local-server.mjs para executar o servidor localmente e testar a API:

import app from "./index.mjs";
const port = 3000;

app.listen(port, () => {
  console.log(`Server is running on http://localhost:${port}`);
});

Depois de ter criado os arquivos e testado a api, precisamos empacotar o código para fazer o deploy na AWS.

Crie um arquivo chamado lambda-express-api.zip e adicione os arquivos index.mjs, handler.mjs, package.json e a pasta node_modules:

Unix/MacWindows
zip -r lambda-express-api.zip index.mjs handler.mjs package.json node_modulesCompress-Archive -Path index.mjs, handler.mjs, package.json, node_modules -DestinationPath lambda-express-api.zip

Passo 2: Configurar o tofu

Ouça a explicação:
Crie um diretório chamado opentofu no seu projeto:
mkdir tofu
cd tofu
Crie um arquivo chamado variables.tf com o seguinte conteúdo:
variable "region" {
  description = "The AWS region to deploy resources in"
  type        = string
  default     = "us-east-1"
}

variable "profile" {
  description = "Default AWS profile to use for deployment"
  type        = string
  default     = "default"
}

Crie um arquivo main.tf com o seguinte conteúdo:

provider "aws" {
  region  = var.region
  profile = var.profile
}

resource "aws_iam_role" "lambda_role" {
  name = "lambda_execution_role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = "sts:AssumeRole"
        Effect = "Allow"
        Sid    = ""
        Principal = {
          Service = "lambda.amazonaws.com"
        }
      },
    ]
  })
}

resource "aws_iam_role_policy_attachment" "lambda_policy" {
  role       = aws_iam_role.lambda_role.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}

resource "aws_lambda_function" "lambda_function" {
  filename         = "${path.module}/../lambda-express-api.zip"
  function_name    = "express_api_lambda"
  role             = aws_iam_role.lambda_role.arn
  handler          = "handler.handler"
  source_code_hash = filebase64sha256("${path.module}/../lambda-express-api.zip")
  runtime          = "nodejs20.x"
  memory_size      = 128
  timeout          = 10
}

resource "aws_api_gateway_rest_api" "api_gateway" {
  name        = "ExpressAPI"
  description = "API Gateway for Express.js Lambda"
}

resource "aws_api_gateway_resource" "hello_resource" {
  rest_api_id = aws_api_gateway_rest_api.api_gateway.id
  parent_id   = aws_api_gateway_rest_api.api_gateway.root_resource_id
  path_part   = "hello"
}

resource "aws_api_gateway_method" "hello_method" {
  rest_api_id   = aws_api_gateway_rest_api.api_gateway.id
  resource_id   = aws_api_gateway_resource.hello_resource.id
  http_method   = "GET"
  authorization = "NONE"
}

resource "aws_api_gateway_integration" "hello_integration" {
  rest_api_id             = aws_api_gateway_rest_api.api_gateway.id
  resource_id             = aws_api_gateway_resource.hello_resource.id
  http_method             = aws_api_gateway_method.hello_method.http_method
  integration_http_method = "POST"
  type                    = "AWS_PROXY"
  uri                     = "arn:aws:apigateway:${var.region}:lambda:path/2015-03-31/functions/${aws_lambda_function.lambda_function.arn}/invocations"
}

resource "aws_lambda_permission" "allow_api_gateway" {
  statement_id  = "AllowAPIGatewayInvoke"
  action        = "lambda:InvokeFunction"
  function_name = aws_lambda_function.lambda_function.function_name
  principal     = "apigateway.amazonaws.com"
  source_arn    = "${aws_api_gateway_rest_api.api_gateway.execution_arn}/*/*"
}

resource "aws_api_gateway_deployment" "api_deployment" {
  depends_on = [aws_api_gateway_integration.hello_integration]

  rest_api_id = aws_api_gateway_rest_api.api_gateway.id
  stage_name  = "prod"
}

output "api_url" {
  value = "https://${aws_api_gateway_rest_api.api_gateway.id}.execute-api.${var.region}.amazonaws.com/prod/hello"
}

Passo 3: Deploy

Dentro da pasta tofu, execute os seguintes comandos para fazer o deploy:
tofu init
tofu plan
tofu apply
Após o deploy, você verá a URL da API Gateway que você pode usar para acessar a função Lambda.

Algo parecido com https://<api-id>.execute-api.<region>.amazonaws.com/prod/hello

Passo 4: Testando a API

Para testar a API, você pode usar o cURL ou o Postman:
curl https://<api-id>.execute-api.<region>.amazonaws.com/prod/hello

Voce verá o retorno Hello from Lambda! na resposta.

Destruindo a Infraestrutura após os testes

Para destruir a infraestrutura, execute o seguinte comando:
tofu destroy

Conclusão

Se você chegou até aqui, quer dizer que você aprendeu como criar uma função Lambda com uma API usando Nodejs, Api Gateway e OpenTofu. Usamos o Express.js para criar a API, recursos nativos do sistema operacional para empacotar a função em arquivo .zip e o OpenTofu para fazer o deploy na AWS.