Definir o manipulador de função do Lambda em TypeScript - AWS Lambda

Definir o manipulador de função do Lambda em TypeScript

O manipulador da função do Lambda é o método no código da função que processa eventos. Quando sua função é invocada, o Lambda executa o método do manipulador. A função é executada até que o manipulador retorne uma resposta, seja encerrado ou atinja o tempo limite.

Esta página descreve como trabalhar com manipuladores de função do Lambda em TypeScript, incluindo opções para a configuração de projeto, convenções de nomenclatura e práticas recomendadas. Além disso, esta página apresenta um exemplo de uma função do Lambda em TypeScript que aceita informações sobre um pedido, produz um recibo em formato de texto e armazena esse arquivo em um bucket do Amazon Simple Storage Service (Amazon S3). Para obter mais informações sobre como implantar a função após gravá-la, consulte Implantar código TypeScript transcompilado no Lambda com arquivos .zip ou Implantar código TypeScript transcompilado no Lambda com imagens de contêiner.

Configurar seu projeto em TypeScript

Use um ambiente de desenvolvimento integrado (IDE) ou editor de texto local para escrever o código de função TypeScript. Não é possível criar código TypeScript no console do Lambda.

Há várias maneiras de inicializar um projeto do Lambda em TypeScript. Por exemplo, é possível criar um projeto padrão usando o npm, criar uma aplicação do AWS SAM, ou criar uma aplicação do AWS CDK. Para criar um projeto usando o npm:

npm init

Seu código de função reside em um arquivo .ts, que você transcompila em um arquivo JavaScript em tempo de compilação. É possível usar o esbuild ou o compilador TypeScript da Microsoft (tsc) para transcompilar seu código TypeScript em JavaScript. Para usar o esbuild, adicione-o como uma dependência de desenvolvimento:

npm install -D esbuild

Um projeto típico de função do Lambda em TypeScript segue esta estrutura geral:

/project-root ├── index.ts - Contains main handler ├── dist/ - Contains compiled JavaScript ├── package.json - Project metadata and dependencies ├── package-lock.json - Dependency lock file ├── tsconfig.json - TypeScript configuration └── node_modules/ - Installed dependencies

Exemplo de função do Lambda em TypeScript

O exemplo de código apresentado a seguir para uma função do Lambda em Node.js aceita informações sobre um pedido, produz um recibo em formato de texto e armazena esse arquivo em um bucket do Amazon S3. Este exemplo define um tipo de evento personalizado (OrderEvent). Para saber como importar definições de tipo para fontes de eventos da AWS, consulte Definições de tipo para o Lambda.

nota

Este exemplo usa um manipulador de módulo ES. O Lambda oferece suporte a manipuladores de módulo ES e CommonJS. Para obter mais informações, consulte Módulos CommonJS e ES.

exemplo Função do Lambda index.ts
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'; // Initialize the S3 client outside the handler for reuse const s3Client = new S3Client(); // Define the shape of the input event type OrderEvent = { order_id: string; amount: number; item: string; } /** * Lambda handler for processing orders and storing receipts in S3. */ export const handler = async (event: OrderEvent): Promise<string> => { try { // Access environment variables const bucketName = process.env.RECEIPT_BUCKET; if (!bucketName) { throw new Error('RECEIPT_BUCKET environment variable is not set'); } // Create the receipt content and key destination const receiptContent = `OrderID: ${event.order_id}\nAmount: $${event.amount.toFixed(2)}\nItem: ${event.item}`; const key = `receipts/${event.order_id}.txt`; // Upload the receipt to S3 await uploadReceiptToS3(bucketName, key, receiptContent); console.log(`Successfully processed order ${event.order_id} and stored receipt in S3 bucket ${bucketName}`); return 'Success'; } catch (error) { console.error(`Failed to process order: ${error instanceof Error ? error.message : 'Unknown error'}`); throw error; } }; /** * Helper function to upload receipt to S3 */ async function uploadReceiptToS3(bucketName: string, key: string, receiptContent: string): Promise<void> { try { const command = new PutObjectCommand({ Bucket: bucketName, Key: key, Body: receiptContent }); await s3Client.send(command); } catch (error) { throw new Error(`Failed to upload receipt to S3: ${error instanceof Error ? error.message : 'Unknown error'}`); } }

Este arquivo index.ts contém as seguintes seções de código:

  • Bloco import: utilize este bloco para incluir as bibliotecas necessárias para a função do Lambda, como clientes do AWS SDK.

  • Declaração de const s3Client: inicializa um cliente do Amazon S3 fora da função do manipulador. Isso faz com que o Lambda execute esse código durante a fase de inicialização, e o cliente é preservado para reutilização em várias invocações.

  • type OrderEvent: define a estrutura do evento de entrada esperado.

  • export const handler: esta é a função de manipulação principal invocada pelo Lambda. Ao implantar sua função, especifique index.handler para a propriedade Handler. O valor da propriedade Handler é o nome do arquivo e o nome do método do manipulador exportado, separados por um ponto.

  • Função uploadReceiptToS3: uma função auxiliar referenciada pela função do manipulador principal.

Para que esta função funcione corretamente, seu perfil de execução deve permitir a ação s3:PutObject. Além disso, certifique-se de definir a variável de ambiente RECEIPT_BUCKET. Após uma invocação com êxito, o bucket do Amazon S3 deve conter um arquivo de recibo.

Módulos CommonJS e ES

O Node.js oferece suporte a dois sistemas de módulos, CommonJS e ECMAScript (módulos ES). O Lambda recomenda o uso de módulos ES, pois eles oferecem suporte à espera de alto nível, o que permite que tarefas assíncronas sejam concluídas durante a inicialização do ambiente de execução.

O Node.js trata arquivos com uma extensão de nome de arquivo .cjs como módulos CommonJS, enquanto uma extensão .mjs denota módulos ES. Por padrão, o Node.js trata arquivos com a extensão do nome de arquivo .js como módulos CommonJS. É possível configurar o Node.js para tratar arquivos .js como módulos ES especificando o type como module no arquivo package.json da função. É possível configurar o Node.js no Lambda para detectar automaticamente se um arquivo .js deve ser tratado como CommonJS ou como um módulo ES adicionando o sinalizador —experimental-detect-module à variável de ambiente NODE_OPTIONS. Para obter mais informações, consulte Recursos experimentais do Node.js.

Os exemplos a seguir mostram manipuladores de funções escritos usando módulos ES e módulos CommonJS. Todos os exemplos restantes nesta página usam módulos ES.

Inicialização do Node.js

O Node.js utiliza um modelo de E/S sem bloqueio que oferece suporte a operações assíncronas eficientes usando um loop de eventos. Por exemplo, se o Node.js fizer uma chamada de rede, a função continuará processando outras operações sem bloquear uma resposta da rede. Quando a resposta da rede é recebida, ela é colocada na fila de retorno de chamada. As tarefas da fila são processadas quando a tarefa atual é concluída.

O Lambda recomenda usar espera de alto nível para que as tarefas assíncronas iniciadas durante a inicialização do ambiente de execução sejam concluídas durante a inicialização. As tarefas assíncronas que não são concluídas durante a inicialização geralmente são executadas durante a primeira chamada da função. Isso pode causar comportamento inesperado ou erros. Por exemplo, sua inicialização de função pode fazer uma chamada de rede para buscar um parâmetro da AWS Parameter Store. Se essa tarefa não for concluída durante a inicialização, o valor poderá ser nulo durante uma invocação. Também pode haver um atraso entre a inicialização e a invocação, o que pode gerar erros em operações sensíveis a tempo. Em especial, as chamadas de serviço da AWS podem depender de assinaturas de solicitação sensíveis a tempo, resultando em falhas se a chamada de serviço não for concluída durante a fase de inicialização. A conclusão de tarefas durante a inicialização geralmente melhora a performance da inicialização a frio e a performance da primeira invocação ao usar a simultaneidade provisionada. Para obter mais informações, consulte a postagem no nosso blog Uso de módulos ES do Node.js e de espera em nível superior no AWS Lambda.

Convenções de nomenclatura para manipuladores

Quando você configura uma função, o valor da configuração do Handler é o nome do arquivo e o nome do módulo do handler exportado, separados por um ponto. O padrão para as funções criadas no console e para os exemplos deste guia é index.handler. Isso indica o método de handler que é exportado do arquivo index.js ou index.mjs.

Se você criar uma função no console usando um nome de arquivo ou nome de manipulador de funções diferente, deverá editar o nome do manipulador padrão.

Para alterar o nome do manipulador de funções (console)
  1. Abra a página Funções do console do Lambda e escolha sua função.

  2. Escolha a guia Código.

  3. Role para baixo até o painel Configurações de runtime e escolha Editar.

  4. Em Manipulador, insira o novo nome para seu manipulador de funções.

  5. Escolha Salvar.

Definição e acesso ao objeto do evento de entrada

O formato de entrada JSON é o mais comum e padrão para funções do Lambda. Neste exemplo, a função espera uma entrada semelhante à seguinte:

{ "order_id": "12345", "amount": 199.99, "item": "Wireless Headphones" }

Ao trabalhar com funções do Lambda em TypeScript, é possível definir a estrutura do evento de entrada usando um tipo ou interface. Neste exemplo, definimos a estrutura do evento usando um tipo:

type OrderEvent = { order_id: string; amount: number; item: string; }

Depois de definir o tipo ou a interface, use-a na assinatura do manipulador para garantir a segurança do tipo:

export const handler = async (event: OrderEvent): Promise<string> => {

Durante a compilação, o TypeScript valida se o objeto do evento contém os campos obrigatórios com os tipos corretos. Por exemplo, o compilador TypeScript retornará um erro se você tentar usar event.order_id como um número ou event.amount como uma string.

Padrões de manipulador válidos para funções TypeScript

Recomendamos usar async/await para declarar o manipulador da função em vez de usar retornos de chamada. O uso de async/await é uma forma concisa e legível de escrever código assíncrono em Node.js, sem a necessidade de usar retornos de chamada aninhados ou promessas de encadeamento. Com async/await, é possível escrever um código que seja lido como código síncrono e, ao mesmo tempo, seja assíncrono e sem bloqueio.

Os exemplos desta seção usam o tipo S3Event. No entanto, é possível usar quaisquer outros tipos de evento da AWS no pacote @types/aws-lambda ou definir seu próprio tipo de evento. Para usar tipos de @types/aws-lambda:

  1. Adicione o pacote @types/aws-lambda como uma dependência de desenvolvimento:

    npm install -D @types/aws-lambda
  2. Importe os tipos necessários, como Context, S3Event ou Callback.

manipuladores de funções assíncronas (recomendados)

A palavra-chave async marca uma função como assíncrona, e a palavra-chave await pausa a execução da função até que uma Promise seja resolvida. O manipulador aceita os seguintes argumentos:

As assinaturas válidas para o padrão async/await são:

export const handler = async (event: S3Event): Promise<void> => { };
export const handler = async (event: S3Event, context: Context): Promise<void> => { };
nota

Ao processar matrizes de itens de forma assíncrona, certifique-se de usar await com Promise.all para garantir que todas as operações sejam concluídas. Métodos como forEach não esperam que os retornos de chamada assíncronos sejam concluídos. Para obter mais informações, consulte Array.prototype.forEach() na documentação do Mozilla.

Manipuladores de funções síncronas

Quando sua função não executa nenhuma tarefa assíncrona, é possível usar um manipulador de função síncrona, usando uma das assinaturas de função a seguir:

export const handler = (event: S3Event): void => { };
export const handler = (event: S3Event, context: Context): void => { };

Manipuladores de função de streaming de resposta

O Lambda oferece suporte ao streaming de resposta com Node.js. Os manipuladores de funções de streaming de resposta usam o decorador awslambda.streamifyResponse() e usam 3 parâmetros: event, responseStream e context. A assinatura da função é:

export const handler = awslambda.streamifyResponse(async (event: APIGatewayProxyEvent, responseStream: NodeJS.WritableStream, context: Context) => { });

Para acessar mais informações, consulte Streaming de respostas para funções do Lambda.

Manipuladores de funções baseadas em retorno de chamada

nota

Só há suporte para os manipuladores de funções baseadas em retorno de chamada até o Node.js 22. A partir do Node.js 24, as tarefas assíncronas devem ser implementadas usando manipuladores de funções assíncronas.

Os manipuladores de funções baseadas em retorno de chamada podem usar os argumentos event, context e callback. O argumento de retorno de chamada espera um Error e uma resposta, a qual deve ser serializável em JSON.

Aqui está a assinatura válida para o padrão do manipulador de retorno de chamada:

export const handler = (event: S3Event, context: Context, callback: Callback<void>): void => { };

A função continuará em execução até que o loop de evento esteja vazio ou o tempo da função se esgote. A resposta não é enviada para o chamador até que todas as tarefas de loop de evento estejam concluídas. Se a função expirar, um erro será retornado. É possível configurar o runtime para enviar a resposta imediatamente, definindo context.callbackWaitsForEmptyEventLoop como false.

exemplo Função TypeScript com retorno de chamada

O exemplo a seguir usa APIGatewayProxyCallback, um tipo de retorno de chamada especializado específico para integrações do API Gateway. A maioria das fontes de eventos da AWS usa o tipo genérico Callback mostrado nas assinaturas acima.

import { Context, APIGatewayProxyCallback, APIGatewayEvent } from 'aws-lambda'; export const lambdaHandler = (event: APIGatewayEvent, context: Context, callback: APIGatewayProxyCallback): void => { console.log(`Event: ${JSON.stringify(event, null, 2)}`); console.log(`Context: ${JSON.stringify(context, null, 2)}`); callback(null, { statusCode: 200, body: JSON.stringify({ message: 'hello world', }), }); };

Usar o SDK para JavaScript v3 em seu manipulador

Frequentemente, você usará as funções do Lambda para interagir com ou fazer atualizações em outros recursos da AWS. A maneira mais simples de interagir com esses recursos é usar o AWS SDK para JavaScript. Todos os runtimes Node.js do Lambda compatíveis incluem o SDK para JavaScript versão 3. No entanto, recomendamos fortemente que você inclua os clientes do AWS SDK necessários em seu pacote de implantação. Isso maximiza a compatibilidade com versões anteriores durante futuras atualizações do runtime do Lambda.

Para adicionar as dependências do SDK à sua função, use o comando npm install para os clientes específicos do SDK que você precisa. No código de exemplo, usamos o cliente do Amazon S3. Adicione essa dependência executando o seguinte comando no diretório que contém seu arquivos package.json:

npm install @aws-sdk/client-s3

No código da função, importe o cliente e os comandos necessários, conforme demonstrado na função de exemplo:

import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';

Em seguida, inicialize um cliente do Amazon S3:

const s3Client = new S3Client();

Neste exemplo, inicializamos nosso cliente do Amazon S3 fora da função do manipulador principal para evitar a necessidade que inicializá-lo a cada vez que nossa função é invocada. Após inicializar o cliente do SDK, você poderá usá-lo para fazer chamadas de API para esse serviço da AWS. O código de exemplo chama a API PutObject do Amazon S3 da seguinte forma:

const command = new PutObjectCommand({ Bucket: bucketName, Key: key, Body: receiptContent });

Acesso a variáveis de ambiente

No código do manipulador, você pode fazer referência a quaisquer variáveis de ambiente usando o método process.env. Neste exemplo, referenciamos a variável de ambiente RECEIPT_BUCKET definida usando as seguintes linhas de código:

// Access environment variables const bucketName = process.env.RECEIPT_BUCKET; if (!bucketName) { throw new Error('RECEIPT_BUCKET environment variable is not set'); }

Usar o estado global

O Lambda executa seu código estático durante a fase de inicialização antes de invocar a função pela primeira vez. Os recursos criados durante a inicialização permanecem na memória entre as invocações, para que você possa evitar ter que criá-los toda vez que invocar sua função.

No código de exemplo, o código de inicialização do cliente do S3 está fora do manipulador. O runtime inicializa o cliente antes que a função manipule seu primeiro evento e o cliente permanece disponível para reutilização em todas as invocações.

Práticas recomendadas de codificação para funções do Lambda em TypeScript

Siga estas diretrizes ao criar funções do Lambda:

  • Separe o manipulador do Lambda da lógica central. Isso permite que você crie uma função mais fácil para teste de unidade.

  • Controle as dependências no pacote de implantação da função. O ambiente de execução do AWS Lambda contém várias bibliotecas. Para os runtimes em Node.js e Python, os AWS SDKs estão incluídos. Para habilitar o conjunto de recursos e atualizações de segurança mais recente, o Lambda atualizará periodicamente essas bibliotecas. Essas atualizações podem introduzir alterações sutis ao comportamento de sua função do Lambda. Para ter controle total das dependências usadas por sua função, empacote todas as dependências em seu pacote de implantação.

  • Minimize a complexidade de suas dependências. Prefira frameworks mais simples que sejam carregados rapidamente no startup do ambiente de execução.

  • Minimize o tamanho do pacote de implantação para conter somente o necessário para o runtime. Isso reduzirá a quantidade de tempo necessária para que seu pacote de implantação seja obtido por download e desempacotado antes da invocação.

Aproveite a reutilização do ambiente de execução para melhorar a performance da função. Inicialize clientes SDK e conexões de banco de dados fora do manipulador de funções e armazene em cache os ativos estáticos localmente no diretório /tmp. As invocações subsequentes processadas pela mesma instância da função podem reutilizar esses recursos. Isso economiza custos reduzindo o runtime da função.

Para evitar possíveis vazamentos de dados entre invocações, não use o ambiente de execução para armazenar dados do usuário, eventos ou outras informações com implicações de segurança. Se sua função depende de um estado mutável que não pode ser armazenado na memória dentro do manipulador, considere criar uma função separada ou versões separadas de uma função para cada usuário.

Use uma diretiva de keep-alive para manter conexões persistentes. O Lambda limpa conexões ociosas ao longo do tempo. A tentativa de reutilizar uma conexão ociosa ao invocar uma função resultará em um erro de conexão. Para manter sua conexão persistente, use a diretiva keep-alive associada ao runtime. Para obter um exemplo, consulte Reutilizar conexões com keep-alive em Node.js.

Use variáveis de ambiente para passar parâmetros operacionais para sua função. Por exemplo, se estiver gravando em um bucket do Amazon S3, em vez fixar no código o nome do bucket em que você está gravando, configure o nome do bucket como uma variável de ambiente.

Evite usar invocações recursivas em sua função do Lambda, em que a função invoca a si mesma ou inicia um processo que pode invocar a função novamente. Isso pode levar a um volume não intencional de invocações da função e a custos elevados. Se você observar um volume não intencional de invocações, defina a simultaneidade reservada da função como 0 imediatamente para limitar todas as invocações da função enquanto atualiza o código.

Não use APIs não documentadas e não públicas no código da função do Lambda. Para os tempos de execução gerenciados pelo AWS Lambda, o Lambda aplica periodicamente atualizações funcionais e de segurança às APIs internas do Lambda. Essas atualizações internas da API podem ser incompatíveis com versões anteriores, gerando consequências não intencionais, como falhas de invocação, caso sua função tenha dependência nessas APIs não públicas. Consulte a referência da API para obter uma lista de APIs disponíveis publicamente.

Escreva um código idempotente. Escrever um código idempotente para suas funções garante que eventos duplicados sejam tratados da mesma maneira. Seu código deve validar eventos adequadamente e lidar corretamente com eventos duplicados. Para obter mais informações, consulte Como torno minha função do Lambda idempotente?.