惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Vercel News
Vercel News
F
Fortinet All Blogs
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
GbyAI
GbyAI
MongoDB | Blog
MongoDB | Blog
Jina AI
Jina AI
aimingoo的专栏
aimingoo的专栏
I
InfoQ
IT之家
IT之家
罗磊的独立博客
Blog — PlanetScale
Blog — PlanetScale
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
The Cloudflare Blog
爱范儿
爱范儿
Microsoft Azure Blog
Microsoft Azure Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
美团技术团队
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
B
Blog RSS Feed
G
Google Developers Blog
大猫的无限游戏
大猫的无限游戏
博客园_首页
Engineering at Meta
Engineering at Meta
Martin Fowler
Martin Fowler

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
From Chaos to REST: How I Evolved My First AWS Lambda API
FeliDrummond · 2026-05-06 · via DEV Community

In my last post, I talked about how I started using AWS Lambda and Amazon API Gateway to build my first API Building My First AWS Lambda API. But to be honest, what I had in the beginning wasn’t exactly a well-structured API. It was functional, but far from ideal. In this post, I want to show the real evolution of my code, going through three stages: a “task executor” (RPC/tunneling style), an API with standardized responses and error handling, and finally a proper REST API.

At first, everything revolved around a single endpoint using the POST method. The logic depended on a field called "tarefa" (task) inside the request body. In other words, instead of using HTTP itself to express the intention of the operation, I created a kind of manual command system. Something like "tarefa": "delete_log" or "tarefa": "get_log". Although this works, this model follows a style closer to RPC (Remote Procedure Call), also known as tunneling. In practice, the API becomes a command executor, where every decision is handled inside a large conditional block with multiple if/elif statements.

This approach introduced several important problems. The first one was completely ignoring HTTP semantics. Methods like GET, POST, PUT, and DELETE already have well-defined meanings, and by using only POST for everything, I lost clarity, standardization, and compatibility with the web ecosystem. Another issue was high coupling: everything depended on the "tarefa" field, concentrating multiple responsibilities into a single function, which makes the code harder to maintain and scale. Additionally, the responses were inconsistent — sometimes a plain string, sometimes JSON — which makes frontend consumption much harder. There was also no proper handling of HTTP status codes: even when errors occurred, the API would still return status 200. Finally, there was no exception handling, so any unexpected error could break the function entirely.

In the second stage, I started organizing the code better. I introduced a standardized response format with fields like "success", "message", and "data", ensuring that every response had a predictable structure. I also began properly handling HTTP status codes, using values like 200, 400, 404, and 500 depending on the situation. Another important improvement was adding a try/except block, ensuring the API would always return a response, even in case of internal errors. I also defined default values at the beginning of the function — such as status, success, message, and data — and let each block modify only what was necessary. This brought a significant improvement in organization and clarity. However, the structure was still based on a single endpoint and a single method, with all logic depending on the "tarefa" field.

That’s when the third stage came in: adopting the REST model. This required an important shift in mindset. I stopped thinking in terms of actions and started thinking in terms of resources. Instead of asking “which task should be executed?”, I started asking “which resource am I interacting with and what operation am I performing on it?”. With this change, the intention of the request became defined by the combination of HTTP method and route, even before looking at the request body. For example: GET /logs to list logs, GET /logs/{id} to retrieve a specific log, POST /logs to create, PUT /logs/{id} to update, and DELETE /logs/{id} to delete. In this model, there is no longer a need for a large if tarefa == ... block, because each route already represents a specific responsibility.

This change brought clear benefits. The organization improved significantly, the code became more readable, and maintenance became simpler. The API started to follow HTTP standards, making integrations with tools and other systems easier. There was also a significant reduction in coupling, since each route now handles a specific responsibility instead of centralizing everything in one place. In terms of scalability and clarity, the difference between a single-endpoint model and REST is huge.

The main lesson from this journey is that you don’t need to start perfectly. I began with something simple that worked, then organized it, standardized it, and only then evolved to a more robust model. When I started, I thought I was building an API, but in practice, I was building a disguised command executor — and that’s okay. This kind of evolution is part of the learning process.

As next steps, I plan to keep improving this API by adding authentication and setting up an automated deployment pipeline using GitHub Actions, so I no longer need to manually update the Lambda function through the AWS console. If you’re just starting out, you’ll probably make these same “mistakes” — and that’s part of the process. What matters is not getting everything right from the beginning, but understanding what you’re doing and evolving consistently.

```python title="lambda_function.py
Código 1:
import json
import boto3
import uuid
from boto3.dynamodb.conditions import Attr

dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('LogsAutomacao')

def lambda_handler(event, context):
body = json.loads(event.get("body", "{}"))

tarefa = body.get("tarefa", "nenhuma")
mensagem = body.get("mensagem", "")


if tarefa == "log":
    table.put_item(Item={
        'id': str(uuid.uuid4()),
        'mensagem': mensagem
    })
    resposta =  f"Log salvo com sucesso: {mensagem}"

elif tarefa == "atualizar_log":
    id_log = body.get("id")
    nova_mensagem = body.get("mensagem")


    if not id_log or not nova_mensagem:
        resposta = "ID ou nova mensagem não fornecidos"
    else:
        response = table.update_item(Key = {"id":id_log},            
        UpdateExpression="SET mensagem = :m",
        ExpressionAttributeValues={
            ':m': nova_mensagem
        })
        resposta = f"Log atualizado com sucesso: {id_log}"

elif tarefa == "filtrar_logs":
    termo = body.get("mensagem", "")
    if not termo:
        resposta = "Termo de busca não fornecido"
    else:
        response = table.scan(FilterExpression=Attr('mensagem').contains(termo))
        itens = response.get('Items', [])
        resposta = json.dumps(itens)


elif tarefa == "buscar_log":
    id_log = body.get("id")  
    if not id_log:
        resposta = "ID não fornecido"
    else:
        response = table.get_item(Key = {"id":id_log})
        item = response.get("Item")


    if item:
        resposta = json.dumps(item)
    else:
        resposta = "Log não encontrado"


elif  tarefa == "deletar_log":
    id_log = body.get("id")


    if not id_log:
        resposta = "ID não fornecido"
    else:
        table.delete_item(
            Key={
                'id': id_log
            }
        )
        resposta = f"Log deletado com sucesso: {id_log}"




elif tarefa == "listar_logs":


    response = table.scan()
    itens = response.get('Items', [])


    resposta = json.dumps(itens)



elif tarefa == "processar":
    numeros = body.get("numeros", [])
    if not numeros:
        resposta = "Nenhum número enviado"
    else:
        soma = sum(numeros)
        media = soma / len(numeros)
        quantidade = len(numeros)

        resposta = f"Soma:{soma} | Média:{media} | Quantidade:{quantidade}"



else:
    resposta = "Tarefa não reconhecida"


return {
    "statusCode": 200,
    "body": resposta
}

Enter fullscreen mode Exit fullscreen mode

Código 2:
import json
import boto3
import uuid
from boto3.dynamodb.conditions import Attr

dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('LogsAutomacao')

def lambda_handler(event, context):
body = json.loads(event.get("body", "{}"))
tarefa = body.get("tarefa", "nenhuma")

    # Configuração padrão da resposta
status = 200
sucesso = True
mensagem = ""
dados = None
try:
    if tarefa == "log":
        msg_log = body.get("mensagem", "")
        id_log = str(uuid.uuid4())
        table.put_item(Item={
            'id': id_log,
            'mensagem': msg_log
        })
        mensagem = "Log salvo com sucesso"
        dados = {
        "id": id_log,
        "mensagem": msg_log
        }



    elif tarefa == "atualizar_log":
        id_log = body.get("id")
        nova_mensagem = body.get("mensagem")


        if not id_log or not nova_mensagem:
            status, sucesso, mensagem = 400, False, "ID ou nova mensagem não fornecidos"


        else:
            response = table.update_item(Key = {"id":id_log},            
            UpdateExpression="SET mensagem = :m",
            ExpressionAttributeValues={
                ':m': nova_mensagem
            })
            mensagem = f"Log {id_log} atualizado"

    elif tarefa == "filtrar_logs":
        termo = body.get("mensagem", "")
        if not termo:
            status, sucesso, mensagem = 400, False, "Termo de busca não fornecido"
        else:
            response = table.scan(FilterExpression=Attr('mensagem').contains(termo))
            dados = response.get('Items', [])
            if not dados:
                status, sucesso, mensagem = 404, False, "Termo de busca não encontrado"
            else:
                 mensagem = f"Busca concluída. Encontrados: {len(dados)}"



    elif tarefa == "buscar_log":
        id_log = body.get("id")  
        if not id_log:
            status, sucesso, mensagem = 400, False, "ID não fornecido"
        else:
            response = table.get_item(Key = {"id":id_log})
            dados = response.get("Item")
            if not dados:
                status, sucesso, mensagem = 404, False, "Log não encontrado"
            else:
                mensagem = "Log recuperado com sucesso"




    elif  tarefa == "deletar_log":
        id_log = body.get("id")


        if not id_log:
            status, sucesso, mensagem = 400, False, "ID não fornecido"
        else:
            table.delete_item(
                Key={
                    'id': id_log
                }
            )
            mensagem = f"Log deletado com sucesso: ID = {id_log}"            





    elif tarefa == "listar_logs":


        response = table.scan()
        dados = response.get('Items', [])
        mensagem = f"Total de logs: {len(dados)}"






    elif tarefa == "processar":
        numeros = body.get("numeros", [])
        if not numeros or not isinstance(numeros, list):
            status, sucesso, mensagem = 400, False, "Lista de números inválida ou vazia"
        else:
            soma = sum(numeros)
            dados = {
                "soma": soma,
                "media": soma / len(numeros),
                "quantidade": len(numeros)
            }
            mensagem = "Cálculos realizados"



    else:
        status, sucesso, mensagem = 400, False, "Tarefa não reconhecida"
except Exception as e:
    # Captura erros inesperados (ex: erro de permissão no DynamoDB)
    status, sucesso, mensagem = 500, False, f"Erro interno: {str(e)}"


return {
"statusCode": status,
"headers": {
    "Content-Type": "application/json",
    "Access-Control-Allow-Origin": "*" # Útil se for usar em um site (CORS)
},
"body": json.dumps({
    "sucesso": sucesso,
    "mensagem": mensagem,
    "dados": dados
})
}

Enter fullscreen mode Exit fullscreen mode

Código 3:
import json
import boto3
import uuid
from boto3.dynamodb.conditions import Attr

dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('LogsAutomacao')

def resposta(status, sucesso, mensagem, dados=None):
return {
"statusCode": status,
"headers": {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*"
},
"body": json.dumps({
"sucesso": sucesso,
"mensagem": mensagem,
"dados": dados
})
}

def lambda_handler(event, context):

metodo = event["requestContext"]["http"]["method"]
path = event["rawPath"].replace("/dev", "")
path_params = event.get("pathParameters") or {}
query_params = event.get("queryStringParameters") or {}

body = json.loads(event.get("body", "{}"))

try:

    # 🔹 POST /logs (CRIAR)
    if metodo == "POST" and path == "/logs":
        msg = body.get("mensagem", "")
        id_log = str(uuid.uuid4())

        table.put_item(Item={
            "id": id_log,
            "mensagem": msg
        })

        return resposta(201, True, "Log criado", {
            "id": id_log,
            "mensagem": msg
        })

    # 🔹 GET /logs (LISTAR ou FILTRAR)
    elif metodo == "GET" and path == "/logs":

        termo = query_params.get("mensagem")

        if termo:
            response = table.scan(
                FilterExpression=Attr('mensagem').contains(termo)
            )
        else:
            response = table.scan()

        return resposta(200, True, "Logs retornados", response.get("Items", []))

    # 🔹 GET /logs/{id}
    elif metodo == "GET" and "id" in path_params:
        id_log = path_params["id"]

        response = table.get_item(Key={"id": id_log})
        item = response.get("Item")

        if not item:
            return resposta(404, False, "Log não encontrado")

        return resposta(200, True, "Log encontrado", item)

    # 🔹 PUT /logs/{id}
    elif metodo == "PUT" and "id" in path_params:
        id_log = path_params["id"]
        nova_mensagem = body.get("mensagem")

        if not nova_mensagem:
            return resposta(400, False, "Mensagem obrigatória")

        table.update_item(
            Key={"id": id_log},
            UpdateExpression="SET mensagem = :m",
            ExpressionAttributeValues={":m": nova_mensagem}
        )

        return resposta(200, True, "Log atualizado")

    # 🔹 DELETE /logs/{id}
    elif metodo == "DELETE" and "id" in path_params:
        id_log = path_params["id"]

        table.delete_item(Key={"id": id_log})

        return resposta(200, True, "Log deletado")

    # 🔹 POST /processar
    elif metodo == "POST" and path == "/processar":
        numeros = body.get("numeros", [])

        if not numeros:
            return resposta(400, False, "Lista inválida")

        soma = sum(numeros)

        return resposta(200, True, "Processado", {
            "soma": soma,
            "media": soma / len(numeros),
            "quantidade": len(numeros)
        })

    else:
        return resposta(404, False, "Rota não encontrada")

except Exception as e:
    return resposta(500, False, str(e))

Enter fullscreen mode Exit fullscreen mode


Enter fullscreen mode Exit fullscreen mode