---
title: "Go com AWS Lambda: API Serverless em Produção"
url: "https://golang.com.br/aprenda/go-aws-lambda/"
markdown_url: "https://golang.com.br/aprenda/go-aws-lambda.MD"
description: "Aprenda a criar uma API serverless em Go com AWS Lambda, API Gateway, DynamoDB, testes locais, build para Linux e deploy seguro."
date: "2026-05-31"
author: ""
---

# 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](/aprenda/api-rest-go/), [Go com Docker](/aprenda/golang-docker/) e [Go com PostgreSQL](/aprenda/golang-postgresql/). Se seu foco é carreira, a página de [vagas Go com AWS](/vagas/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:

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

Inicialize o módulo:

```bash
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`:

```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:

```bash
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:

```go
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:

```go
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:

```go
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`:

```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:

```makefile
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:

```go
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:

```go
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:

```go
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](/vagas/aws/) pedem exatamente essa combinação de backend, eventos, cloud e responsabilidade por produção.
