Janeiro 2026 · ~7 min

Go com AWS Lambda: API Serverless em Produção

Aprenda a criar uma API serverless em Go com AWS Lambda, API Gateway, DynamoDB, testes locais, build para Linux e deploy seguro.

Go com AWS Lambda: API Serverless em Produção

Go combina muito bem com AWS Lambda. A linguagem gera binários pequenos, sobe rápido, consome pouca memória e não exige runtime pesado. Para times brasileiros que precisam entregar APIs, workers, automações cloud ou integrações assíncronas sem operar servidores o tempo todo, Lambda pode ser um caminho pragmático.

Este guia mostra como estruturar uma API serverless em Go, do primeiro handler ao deploy. A ideia não é vender serverless como solução mágica: você vai entender onde Lambda ajuda, onde atrapalha e quais cuidados deixam o projeto mais próximo de produção.

Se você ainda está montando a base, leia antes API REST com Go, Go com Docker e Go com PostgreSQL. Se seu foco é carreira, a página de vagas Go com AWS mostra por que Lambda, SQS, DynamoDB e observabilidade aparecem tanto em descrições de backend, plataforma e DevOps.


Quando usar Go com Lambda

Lambda é melhor quando o trabalho é curto, previsível e acionado por HTTP, fila, evento ou agendamento. Bons casos de uso incluem:

  • APIs pequenas ou médias atrás do API Gateway
  • Webhooks de pagamento, CRM, GitHub ou ferramentas internas
  • Workers que consomem SQS, SNS, EventBridge ou Kinesis
  • Rotinas agendadas com EventBridge Scheduler
  • Processamento de arquivos enviados para S3
  • Automações internas que rodam poucas vezes por dia

Go ajuda nesses cenários porque o pacote final normalmente é um binário único. Isso simplifica deploy, reduz dependências e melhora cold start em comparação com runtimes mais pesados. Também é uma linguagem ótima para lidar com concorrência controlada, timeouts, contexto e chamadas HTTP para serviços externos.

Mas Lambda não é ideal para tudo. Evite quando você precisa de processos longos, conexões persistentes como WebSocket tradicional, workloads com muita memória, tarefas que passam do limite de execução, controle avançado de rede ou servidores que precisam ficar quentes o tempo todo. Nesses casos, ECS, EKS, Fly.io, uma VM simples ou outro modelo de deploy pode fazer mais sentido.


Estrutura do projeto

Vamos criar uma API simples de tarefas usando Lambda, API Gateway e DynamoDB. A estrutura fica assim:

minha-api-lambda/
├── cmd/
│   └── api/
│       └── main.go
├── internal/
│   ├── handler/
│   │   └── tasks.go
│   └── store/
│       └── dynamodb.go
├── go.mod
├── template.yaml
└── Makefile

Inicialize o módulo:

mkdir minha-api-lambda
cd minha-api-lambda
go mod init exemplo.com/minha-api-lambda
go get github.com/aws/aws-lambda-go/events
go get github.com/aws/aws-lambda-go/lambda
go get github.com/aws/aws-sdk-go-v2/config
go get github.com/aws/aws-sdk-go-v2/service/dynamodb

O pacote aws-lambda-go fornece o adaptador do runtime Lambda. O SDK v2 da AWS será usado para acessar DynamoDB com contexto, timeouts e configuração padrão do ambiente.


Primeiro handler HTTP

Crie cmd/api/main.go:

package main

import (
    "context"
    "encoding/json"
    "net/http"

    "github.com/aws/aws-lambda-go/events"
    "github.com/aws/aws-lambda-go/lambda"
)

type resposta struct {
    Mensagem string `json:"mensagem"`
}

func handler(ctx context.Context, req events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
    body, err := json.Marshal(resposta{Mensagem: "API Go rodando no AWS Lambda"})
    if err != nil {
        return events.APIGatewayProxyResponse{StatusCode: http.StatusInternalServerError}, err
    }

    return events.APIGatewayProxyResponse{
        StatusCode: http.StatusOK,
        Headers: map[string]string{
            "Content-Type": "application/json; charset=utf-8",
        },
        Body: string(body),
    }, nil
}

func main() {
    lambda.Start(handler)
}

Para compilar para Lambda, gere um binário Linux:

GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o bootstrap ./cmd/api
zip function.zip bootstrap

Em runtimes modernos da AWS, você também pode usar provided.al2023 com binário chamado bootstrap. Isso deixa o deploy simples e evita dependência de um runtime Go gerenciado específico.


Rotas com API Gateway

O APIGatewayProxyRequest traz método, path, headers, query string e body. Um roteador pequeno já resolve muitos projetos:

func handler(ctx context.Context, req events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
    switch {
    case req.HTTPMethod == http.MethodGet && req.Path == "/tarefas":
        return listarTarefas(ctx)
    case req.HTTPMethod == http.MethodPost && req.Path == "/tarefas":
        return criarTarefa(ctx, req.Body)
    default:
        return jsonResponse(http.StatusNotFound, map[string]string{"erro": "rota nao encontrada"})
    }
}

func jsonResponse(status int, payload any) (events.APIGatewayProxyResponse, error) {
    body, err := json.Marshal(payload)
    if err != nil {
        return events.APIGatewayProxyResponse{StatusCode: http.StatusInternalServerError}, err
    }

    return events.APIGatewayProxyResponse{
        StatusCode: status,
        Headers: map[string]string{"Content-Type": "application/json; charset=utf-8"},
        Body: string(body),
    }, nil
}

Para APIs maiores, considere aws-lambda-go-api-proxy com chi, gorilla/mux ou outro roteador HTTP. Só evite começar com complexidade demais: muitas funções Lambda em produção são simples o bastante para um switch explícito por método e rota.


DynamoDB com contexto

DynamoDB aparece bastante em arquiteturas serverless porque escala sem servidor e integra bem com Lambda. Um repositório mínimo pode receber o cliente no boot da função:

package main

import (
    "context"
    "log"
    "os"

    "github.com/aws/aws-lambda-go/lambda"
    "github.com/aws/aws-sdk-go-v2/config"
    "github.com/aws/aws-sdk-go-v2/service/dynamodb"
)

var ddb *dynamodb.Client
var tabela string

func main() {
    ctx := context.Background()

    cfg, err := config.LoadDefaultConfig(ctx)
    if err != nil {
        log.Fatalf("carregando config AWS: %v", err)
    }

    ddb = dynamodb.NewFromConfig(cfg)
    tabela = os.Getenv("TASKS_TABLE")
    if tabela == "" {
        log.Fatal("TASKS_TABLE nao configurada")
    }

    lambda.Start(handler)
}

Inicializar clientes fora do handler é importante. A AWS pode reutilizar o mesmo ambiente de execução em múltiplas invocações, então você economiza tempo e conexões quando não recria SDK, configurações e caches a cada request.

Ao chamar DynamoDB, sempre propague o context.Context recebido pelo handler. Assim timeouts, cancelamentos e deadlines do Lambda chegam até o SDK:

func listarTarefas(ctx context.Context) (events.APIGatewayProxyResponse, error) {
    out, err := ddb.Scan(ctx, &dynamodb.ScanInput{
        TableName: &tabela,
        Limit:     aws.Int32(25),
    })
    if err != nil {
        return jsonResponse(http.StatusInternalServerError, map[string]string{"erro": "falha ao buscar tarefas"})
    }

    return jsonResponse(http.StatusOK, map[string]any{"items": out.Items})
}

Em produção, prefira Query com chave bem desenhada em vez de Scan amplo. Scan é aceitável para tutorial, painel interno pequeno ou tabela minúscula, mas pode ficar caro e lento conforme os dados crescem.


Deploy com AWS SAM

Uma forma direta de versionar infraestrutura é usar AWS SAM. Crie template.yaml:

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: API Go serverless com Lambda e DynamoDB

Globals:
  Function:
    Runtime: provided.al2023
    Architectures:
      - x86_64
    Timeout: 10
    MemorySize: 128
    Environment:
      Variables:
        TASKS_TABLE: !Ref TasksTable

Resources:
  ApiFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: .
      Handler: bootstrap
      Policies:
        - DynamoDBCrudPolicy:
            TableName: !Ref TasksTable
      Events:
        ListTasks:
          Type: Api
          Properties:
            Path: /tarefas
            Method: GET
        CreateTask:
          Type: Api
          Properties:
            Path: /tarefas
            Method: POST

  TasksTable:
    Type: AWS::DynamoDB::Table
    Properties:
      BillingMode: PAY_PER_REQUEST
      AttributeDefinitions:
        - AttributeName: pk
          AttributeType: S
      KeySchema:
        - AttributeName: pk
          KeyType: HASH

E um Makefile simples:

build:
	GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o bootstrap ./cmd/api

deploy: build
	sam deploy --guided

O primeiro sam deploy --guided pergunta stack name, região e permissões. Depois disso, o deploy pode rodar no CI com sam deploy --no-confirm-changeset --no-fail-on-empty-changeset.


Testes locais

Não deixe todo feedback depender da AWS. Extraia regras de negócio para funções comuns e teste sem Lambda:

func TestJsonResponse(t *testing.T) {
    res, err := jsonResponse(http.StatusOK, map[string]string{"ok": "true"})
    if err != nil {
        t.Fatal(err)
    }
    if res.StatusCode != http.StatusOK {
        t.Fatalf("status = %d", res.StatusCode)
    }
    if !strings.Contains(res.Body, "true") {
        t.Fatalf("body inesperado: %s", res.Body)
    }
}

Para simular o evento HTTP, chame o handler diretamente:

func TestHandlerNotFound(t *testing.T) {
    req := events.APIGatewayProxyRequest{HTTPMethod: http.MethodGet, Path: "/nao-existe"}
    res, err := handler(context.Background(), req)
    if err != nil {
        t.Fatal(err)
    }
    if res.StatusCode != http.StatusNotFound {
        t.Fatalf("status = %d", res.StatusCode)
    }
}

Para DynamoDB, use interfaces pequenas no seu pacote de store. Assim você testa handler com fake em memória e deixa testes de integração para uma camada separada, usando DynamoDB Local ou uma tabela temporária.


Observabilidade e segurança

Uma Lambda sem logs bons vira caixa-preta. No mínimo, registre método, rota, status, duração e erro. Use logs estruturados quando possível:

log.Printf("method=%s path=%s status=%d", req.HTTPMethod, req.Path, status)

Em produção, considere CloudWatch Logs Insights, métricas customizadas, alarmes de erro, DLQ para eventos assíncronos e tracing com AWS X-Ray ou OpenTelemetry. Também configure timeout realista: se sua chamada externa costuma levar 2 segundos, uma função com timeout de 30 segundos só demora mais para falhar e custa mais.

Na parte de segurança, use permissões IAM mínimas. Se a função só lê uma tabela, não dê dynamodb:*. Se só publica em uma fila, limite por ARN. Segredos devem vir de Secrets Manager, SSM Parameter Store ou variáveis criptografadas, nunca do código.

Também cuide de validação de entrada. API Gateway e Lambda não tornam payload confiável. Valide JSON, limite tamanho de body, trate Content-Type, normalize erros e não exponha mensagens internas para o usuário final.


Checklist de produção

Antes de publicar uma API Go em Lambda, confira:

  • O binário é compilado com GOOS=linux e CGO_ENABLED=0
  • Clientes AWS são inicializados fora do handler
  • context.Context é propagado para chamadas externas
  • Logs têm rota, status, duração e erro
  • Permissões IAM são específicas por recurso
  • Timeouts e memória foram ajustados com base em teste real
  • Erros 4xx e 5xx têm respostas JSON previsíveis
  • Deploy é reproduzível por SAM, CDK, Terraform ou CI
  • Há alarmes para erro, timeout e throttling
  • Custos de API Gateway, Lambda, logs e DynamoDB foram estimados

Go com AWS Lambda é uma ótima opção quando você quer simplicidade operacional sem abrir mão de performance. Comece pequeno, meça cold starts e latência, mantenha a infraestrutura versionada e conecte o aprendizado com o mercado: muitas vagas Golang com AWS pedem exatamente essa combinação de backend, eventos, cloud e responsabilidade por produção.

Perguntas frequentes

Go é uma boa linguagem para AWS Lambda?

Sim. Go compila para um binário único, inicia rápido, usa pouca memória e funciona bem para APIs, workers, automações e consumidores de eventos em AWS Lambda.

Preciso usar framework para criar Lambda em Go?

Não. A biblioteca oficial github.com/aws/aws-lambda-go já resolve o handler. Frameworks como SAM, Serverless Framework ou CDK ajudam no deploy, mas não são obrigatórios para começar.

Quando escolher Lambda em vez de ECS ou Kubernetes?

Lambda faz sentido para tráfego variável, jobs curtos, integrações por evento e APIs pequenas. Para serviços longos, workloads com estado, conexões persistentes ou controle fino de runtime, ECS, EKS ou VMs podem ser melhores.