Padrões de Handler Lambda
Escreva handlers Lambda que sejam tipados, idempotentes e otimizados para inicialização para Node.js 24 na AWS.
Receita
Cartão de receita de referência rápida - pronto para copiar e colar.
import type { APIGatewayProxyHandlerV2, Context } from "aws-lambda";
// Fase de inicialização (cold start) - executa uma vez por ambiente de execução
const tableName = process.env.TABLE_NAME!;
export const handler: APIGatewayProxyHandlerV2 = async (event, context) => {
context.callbackWaitsForEmptyEventLoop = false;
const id = event.pathParameters?.id;
if (!id) {
return { statusCode: 400, body: JSON.stringify({ error: "id required" }) };
}
const item = await getItem(tableName, id);
return {
statusCode: item ? 200 : 404,
body: JSON.stringify(item ?? { error: "not found" }),
};
};
async function getItem(table: string, id: string) {
return { id, table };
}Quando usar isso: Toda nova função Lambda antes de adotar wrappers de framework.
Exemplo de Trabalho
// src/handlers/orders.ts
import type {
APIGatewayProxyEventV2,
APIGatewayProxyResultV2,
Context,
SQSBatchResponse,
SQSEvent,
} from "aws-lambda";
import { processOrder } from "../services/orders.js";
// ---- Handler HTTP API ----
export async function httpHandler(
event: APIGatewayProxyEventV2,
context: Context
): Promise<APIGatewayProxyResultV2> {
context.callbackWaitsForEmptyEventLoop = false;
if (event.requestContext.http.method === "POST" && event.rawPath === "/orders") {
const body = event.body ? JSON.parse(event.body) : {};
const order = await processOrder(body);
return { statusCode: 201, body: JSON.stringify(order) };
}
return { statusCode: 404, body: JSON.stringify({ error: "not found" }) };
}
// ---- Lote SQS com falhas parciais ----
export async function sqsHandler(event: SQSEvent): Promise<SQSBatchResponse> {
const batchItemFailures: { itemIdentifier: string }[] = [];
await Promise.all(
event.Records.map(async (record) => {
try {
const msg = JSON.parse(record.body) as { orderId: string };
await processOrder(msg);
} catch {
batchItemFailures.push({ itemIdentifier: record.messageId });
}
})
);
return { batchItemFailures };
}O que isso demonstra:
callbackWaitsForEmptyEventLoop = falseencerra a invocação quando o handler retorna (não espere por timers de pool de banco de dados abertos)APIGatewayProxyEventV2tipado para HTTP API- Resposta de falha parcial de lote SQS para retentativas sem reprocessar sucessos
Mergulho Profundo
Estilos de Handler
| Estilo | Status | Notas |
|---|---|---|
async (event) => {} | Preferido | O valor de retorno se torna a resposta |
async (event, context, callback) => {} | Legado | Evite em novo código TypeScript |
| Síncrono apenas com Callback | Descontinuado | Sem suporte a promessas |
Fase de Inicialização vs. Invocação
// INICIALIZAÇÃO (fora do handler) - cache de clientes, análise de env
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
const ddb = new DynamoDBClient({});
export const handler = async () => {
// INVOCACÃO (dentro do handler) - apenas lógica por solicitação
await ddb.send(/* ... */);
};O Lambda reutiliza o ambiente de execução entre as invocações. Coloque a configuração cara no escopo do módulo.
Campos de Contexto que Você Realmente Usa
export const handler = async (_event: unknown, context: Context) => {
console.log(JSON.stringify({
awsRequestId: context.awsRequestId,
functionName: context.functionName,
remainingMs: context.getRemainingTimeInMillis(),
}));
};getRemainingTimeInMillis() protege loops longos antes do timeout rígido.
Resumo da Fonte de Eventos
| Fonte | Importação de Tipo | Armadilha |
|---|---|---|
| HTTP API | APIGatewayProxyEventV2 | Corpos Base64 |
| REST API | APIGatewayProxyEvent | Tipos v1 vs v2 diferem |
| SQS | SQSEvent | Entrega "pelo menos uma vez" |
| S3 | S3Event | Evento por objeto |
| EventBridge | EventBridgeEvent<string, T> | Tipagem do payload detail |
Armadilhas
- Novo pool de banco de dados por invocação dentro do handler - esgota conexões. Correção: pool no escopo do módulo com
maxbaixo, ou RDS Proxy. - Esquecer
callbackWaitsForEmptyEventLoop = false- trava até o pool ficar ocioso. Correção: defina comofalseouawait pool.end()(geralmente errado para reutilização). - Handlers SQS não idempotentes - cobranças duplicadas. Correção: chaves de idempotência no DynamoDB.
- Lançar erros de validação 4xx - Lambda marca a invocação como falha; pode retentar. Correção: retorne uma resposta 4xx, não lance.
- Logar
eventgrande - PII no CloudWatch. Correção: registre apenas IDs e metadados. - Misturar tipos v1 e v2 da API Gateway - compilação passa, campo de tempo de execução ausente. Correção: corresponda ao tipo de integração no IaC.
Alternativas
| Alternativa | Use Quando | Não Use Quando |
|---|---|---|
| Handlers tipados brutos | Controle total, menor pacote | Equipe quer roteamento Express |
| serverless-http | Levantar aplicativo Express rapidamente | APIs sensíveis a cold start |
| @fastify/aws-lambda | Fastify com ponte Lambda | Já usa Express |
| Lambda Function URLs | HTTP público simples sem API GW | Precisa de WAF, throttling, chaves de API |
FAQs
Handlers devem retornar ou lançar em caso de 500?
Retorne statusCode: 500 para APIs HTTP. Lance apenas quando quiser que o Lambda retente (invocações assíncronas) ou marque o item do lote como falho.
Como eu tipifico o detalhe de um evento personalizado?
EventBridgeEvent<"OrderCreated", { orderId: string }> e estreite em event["detail-type"].
Um arquivo de handler ou muitos?
Um handler exportado por função Lambda na AWS. Compartilhe código via importações de services/; não multiplexe gatilhos não relacionados em um handler, a menos que use uma biblioteca de roteamento.
O NestJS muda o padrão?
@nestjs/platform-aws-lambda envolve o bootstrap. Ainda defina callbackWaitsForEmptyEventLoop no adaptador de bootstrap.
Como eu testo handlers localmente?
Importe o handler e passe eventos fictícios de src/__fixtures__/apigw-v2-get.json. Não é necessário iniciar o SAM para testes unitários.
E o middleware Middy?
@middy/core envolve handlers para análise de JSON, CORS e normalização de erros. Útil para handlers brutos; menos necessário com adaptadores Express/Fastify.
Relacionados
- Bases do Serverless - Lambda vs contêineres
- Mitigação de Cold Start - otimização de inicialização
- @aws-sdk v3 - clientes na fase de inicialização
- serverless-express / aws-lambda-fastify - wrappers de framework
- Melhores Práticas Serverless - checklist da seção
Versões da Pilha: Esta página foi escrita para Node.js 24.18.0 (LTS Ativo), npm 10+, TypeScript 5.6+, Express 5, Fastify 5 e NestJS 11.