AWS SAM

[guía] deploy de lambda con typescript y sam

ed

Inicializando el proyecto

Comenzamos con la estructura básica de un proyecto Node.js:

npm init -y

Instalación de dependencias

Producción:

npm install @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb zod

Desarrollo:

npm install -D @types/aws-lambda @types/node @typescript-eslint/eslint-plugin @typescript-eslint/parser esbuild eslint typescript

Estructura de carpetas

Creamos una estructura simple y organizada:

.
├── src/
│   ├── functions/
│   │   └── reservations.ts
│   └── utils/
│       └── response.ts
├── dist/          # Output de TypeScript
├── events/        # Para pruebas locales
├── template.yaml  # CloudFormation template
├── samconfig.toml # Configuración de SAM
├── tsconfig.json
└── package.json

Configuración de TypeScript

Creamos tsconfig.json con configuración para Lambda:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "commonjs",
    "lib": ["ES2022"],
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "moduleResolution": "node",
    "resolveJsonModule": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

Y añadimos el script de build en package.json:

{
  "scripts": {
    "build": "tsc"
  }
}

Template CloudFormation con SAM

Creamos un template.yaml simple con API Gateway HTTP y una Lambda:

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: The dog farm API - Serverless Backend

Globals:
  Function:
    Runtime: nodejs22.x
    Timeout: 30
    MemorySize: 512

Parameters:
  Environment:
    Type: String
    Default: dev
    AllowedValues:
      - dev
      - staging
      - prod

Resources:
  # API Gateway HTTP
  Api:
    Type: AWS::Serverless::HttpApi
    Properties:
      StageName: !Ref Environment
      CorsConfiguration:
        AllowOrigins:
          - "*"
        AllowMethods:
          - GET
          - POST
          - PUT
          - DELETE

  # Lambda Function
  ReservationsFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: !Sub the-dog-farm-reservations-${Environment}
      CodeUri: dist/
      Handler: functions/reservations.handler
      Events:
        GetReservations:
          Type: HttpApi
          Properties:
            ApiId: !Ref Api
            Path: /reservations
            Method: GET

Outputs:
  ApiUrl:
    Description: The URL of the API Gateway
    Value: !Sub https://${Api}.execute-api.${AWS::Region}.amazonaws.com/${Environment}

Puntos clave del template:

  • Transform: AWS::Serverless-2016-10-31 activa la sintaxis SAM
  • CodeUri: dist/ apunta al código compilado de TypeScript
  • Handler: functions/reservations.handler sigue el formato archivo.función
  • Los Events conectan automáticamente API Gateway con Lambda

Utilidad de respuesta

Primero creamos una función helper en src/utils/response.ts para estandarizar las respuestas:

export const response = (statusCode: number, body: any) => {
  return {
    statusCode,
    headers: {
      'Content-Type': 'application/json',
      'Access-Control-Allow-Origin': '*',
    },
    body: statusCode === 204 ? '' : JSON.stringify(body),
  };
}

Esta función nos ahorra repetir código y garantiza que todas las respuestas tengan:

  • Headers CORS configurados
  • Content-Type JSON
  • Body serializado (excepto para 204 No Content)

Handler de Lambda

Creamos un handler simple en src/functions/reservations.ts:

import { APIGatewayProxyEventV2, APIGatewayProxyResultV2 } from 'aws-lambda';
import { response } from "../utils/response"

export const handler = async (
  event: APIGatewayProxyEventV2
): Promise<APIGatewayProxyResultV2> => {
  console.log('Reservation handler event', JSON.stringify(event, null, 2))

  try {
    const method = event.requestContext.http.method;

    if (method === 'GET') {
      return response(200, {
        message: 'reservations is running'
      })
    }

    return response(405, {
      message: 'Method Not Allowed'
    })
  } catch (error) {
    console.error('Error:', error);
    return response(500, {
      error: 'Internal server error',
      message: error instanceof Error ? error.message : 'Unknown error',
    });
  }
}

Compilación y validación

Compilamos TypeScript a JavaScript:

npm run build

Esto genera el código en dist/ que Lambda ejecutará.

Validamos el template de CloudFormation:

sam validate

Construimos el proyecto con SAM:

sam build

SAM empaqueta las dependencias y prepara todo para el deploy.

Configuración con samconfig.toml

Antes del deploy, creamos un bucket S3 para almacenar los artifacts:

aws s3 mb s3://stack-thedogfarm-dev --region us-east-1

El archivo samconfig.toml centraliza la configuración de deploy por ambiente:

version = 0.1

[default.global.parameters]
capabilities = "CAPABILITY_IAM"

[dev]
[dev.deploy]
[dev.deploy.parameters]
stack_name = "the-dog-farm-api-dev"
s3_bucket = "stack-thedogfarm-dev"
s3_prefix = "thedogfarm"
region = "us-east-1"
parameter_overrides = [
    "Environment=dev"
]

[prod]
[prod.deploy]
[prod.deploy.parameters]
stack_name = "the-dog-farm-api-prod"
s3_bucket = "stack-thedogfarm-prod"
s3_prefix = "thedogfarm"
region = "us-east-2"
confirm_changeset = true
parameter_overrides = [
    "Environment=prod"
]

¿Cómo funciona?

  • [dev] y [prod] son perfiles de configuración
  • Cada perfil tiene su propio stack, bucket y región
  • parameter_overrides pasa valores a los Parameters del template
  • confirm_changeset = true en prod requiere aprobación manual

Testing local

Creamos un archivo de evento de prueba en events/reservations-event.json:

{
  "requestContext": {
    "http": {
      "method": "GET"
    }
  }
}

Probamos la Lambda localmente con este evento:

sam local invoke ReservationsFunction -e events/reservations-event.json

Iniciamos API Gateway localmente:

sam local start-api

Ahora podemos hacer requests a http://localhost:3000/reservations

Deploy a AWS

Deployamos usando el perfil de dev:

sam deploy --config-env dev

SAM:

  1. Sube el código al bucket S3
  2. Crea/actualiza el CloudFormation stack
  3. Despliega API Gateway y Lambda
  4. Muestra la URL del API en los Outputs

Para producción:

sam deploy --config-env prod

Esto usará la configuración de prod con confirmación manual del changeset.

TL;DR

# Setup
npm init -y
npm install @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb zod
npm install -D @types/aws-lambda @types/node @typescript-eslint/eslint-plugin @typescript-eslint/parser esbuild eslint typescript

# Crear estructura src/functions/ y src/utils/
# Configurar tsconfig.json y template.yaml

# Build y deploy
npm run build
sam validate
sam build

# Testing local
sam local invoke ReservationsFunction -e events/reservations-event.json
sam local start-api

# Bucket para almacenar stack
aws s3 mb s3://stack-thedogfarm-dev --region us-east-1
# Deploy
sam deploy --config-env dev

Stack completo: TypeScript → Lambda → API Gateway, configurado con SAM templates y samconfig.toml para múltiples ambientes.