Serverless on AWS Lambda: Affordable Functions, APIs, and Automation

Why “invisible” servers might be the answer for small businesses

Have you ever received a request from a client who needs to expose an endpoint to validate coupons, but doesn’t have the budget to keep a server running 24 hours a day? Or have you ever needed to sync the inventory of your physical store with a marketplace, only to find that the traditional solution consumed hours of development and infrastructure costs?

These situations are more common than we imagine, especially in retail environments where every penny counts. The promise of serverless — executing code only when an event occurs — allows you to turn those demands into lightweight functions, paid only for the actual execution time. In the AWS ecosystem, the service that materializes this idea is AWS Lambda.

Below, I’ll show how to build functions, APIs, and triggers in a practical and cost‑effective way, using the resources I already master in my integration projects between OSPOS and marketplaces, and in the automation pipelines I’ve developed for clients.

Wikimedia Foundation servers: the infrastructure behind on-demand services.
Wikimedia Foundation servers: the infrastructure behind on-demand services. — Image: Wikimedia Foundation Servers-8055 08.jpg by Victorgrigas — CC BY-SA 3.0 · via Wikimedia Commons

1. Basic Lambda: Python and Node.js in practice

The first thing we need to understand is that a Lambda is simply a snippet of code that AWS runs in response to an event. There is no server to provision, nor a virtual machine to monitor. Just upload the code (or point to a repository) and set the permissions.

1.1 Minimal structure of a Python function

import json

def lambda_handler(event, context):
    # O objeto `event` traz os dados do gatilho
    name = event.get("queryStringParameters", {}).get("nome", "Mundo")
    resposta = {"mensagem": f"Olá, {name}!"}
    return {
        "statusCode": 200,
        "headers": {"Content-Type": "application/json"},
        "body": json.dumps(resposta)
    }

Save the file as handler.py and upload it as a zipped deployment package, or use AWS SAM (see section 7) to automate the process.

1.2 Minimal structure of a Node.js function

exports.handler = async (event) => {
    const name = (event.queryStringParameters || {}).nome || "Mundo";
    const resposta = { mensagem: `Olá, ${name}!` };
    return {
        statusCode: 200,
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(resposta)
    };
};

Node.js is often chosen when we need high‑performance libraries for I/O, something I've used in lead‑pipeline projects where the latency of external API calls was critical.

2. Turning the Lambda into a REST API with API Gateway

A Lambda by itself does not expose an HTTP endpoint. For that we use Amazon API Gateway, which routes HTTP requests to the function and returns the response to the client.

2.1 Quick setup via Console

  1. Create a new API of type REST API (not an HTTP API to have full resource control).
  2. Define a /saudacao resource and a GET method.
  3. In the Integration Request, select Lambda Function and specify the function created above.
  4. Save and Deploy to a stage, for example dev.

Done, the generated URL (something like https://abcde.execute-api.us-east-1.amazonaws.com/dev/saudacao?nome=Ismael) is now ready to be consumed.

2.2 Infrastructure code with SAM

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Resources:
  SaudacaoFunction:
    Type: AWS::Serverless::Function
    Properties:
      Runtime: python3.11
      Handler: handler.lambda_handler
      CodeUri: ./src
      MemorySize: 128
      Timeout: 5
      Events:
        GetSaudacao:
          Type: Api
          Properties:
            Path: /saudacao
            Method: get

With sam build && sam deploy --guided the entire stack (Lambda + API) will be provisioned in a few minutes. In my inventory‑service, I used exactly this pattern to expose endpoints that synchronize inventory between OSPOS and Mercado Livre, keeping the integration layer extremely lightweight and cheap.

3. Event Triggers: S3, SQS, and DynamoDB

The biggest advantage of Lambda is the variety of events that can invoke it. Let’s look at three of the most useful ones for a full‑stack developer’s daily work.

3.1 File processing with S3

Imagine the store needs to generate thumbnails of product images as soon as the supplier uploads them to a bucket. Lambda can read the object, process the image, and save the result to another bucket.

import boto3
from PIL import Image
import io

s3 = boto3.client('s3')

def lambda_handler(event, context):
    for record in event['Records']:
        bucket = record['s3']['bucket']['name']
        key    = record['s3']['object']['key']

        obj = s3.get_object(Bucket=bucket, Key=key)
        img = Image.open(io.BytesIO(obj['Body'].read()))
        img.thumbnail((200, 200))

        out_buffer = io.BytesIO()
        img.save(out_buffer, 'JPEG')
        out_buffer.seek(0)

        s3.put_object(
            Bucket=f"{bucket}-thumb",
            Key=key,
            Body=out_buffer,
            ContentType='image/jpeg'
        )

The trigger configuration is done in the S3 console (Properties → Event notifications tab) or via SAM:

Events:
  ImageUpload:
    Type: S3
    Properties:
      Bucket: my-product-images
      Events: s3:ObjectCreated:Put

3.2 Message queues with SQS

In projects where I need to guarantee that lead ingestion never loses a message, I route the data to an SQS queue. A Lambda configured as a consumer reads the queue, enriches the lead with AI, and writes it to the database.

exports.handler = async (event) => {
    for (const record of event.Records) {
        const payload = JSON.parse(record.body);
        // fictitious call to an AI service
        const enriched = await enrichLead(payload);
        await saveToPostgres(enriched);
    }
    return { statusCode: 200 };
};

The benefit is that Lambda only runs when there are messages, avoiding fixed costs.

3.3 Change streams in DynamoDB

When inventory changes in OSPOS, I write the update to a DynamoDB table. A Lambda attached to a DynamoDB Stream propagates the change to the marketplace in real time.

def lambda_handler(event, context):
    for rec in event['Records']:
        if rec['eventName'] == 'MODIFY':
            new_image = rec['dynamodb']['NewImage']
            sku = new_image['sku']['S']
            qty = int(new_image['quantidade']['N'])
            sync_to_marketplace(sku, qty)

This architecture helped me maintain consistency across sales channels without needing a heavy cron job.

4. Cold start and mitigation strategies

A cold start occurs when Lambda has to spin up a new execution environment because no warm instances are available. The time spent can range from a few milliseconds to several seconds, depending on the runtime and the number of dependencies.

4.1 Main causes

  • Package size – more files = longer download time.
  • Language – runtimes such as Java and .NET tend to start slower than Python or Node.js.
  • Allocated memory – more memory provides more CPU, reducing initialization time.

4.2 Practical mitigations

  1. Keep‑alive via CloudWatch Events – scheduling invocations every 5 minutes keeps the function “warm”. In tests with the lead‑pipeline, this technique reduced the average response time from 800 ms to under 150 ms.
  2. Layers for dependencies – separating large libraries into a reusable layer prevents the function zip from growing.
  3. Provisioned Concurrency – AWS allows you to reserve pre‑initialized instances. Although it adds extra cost, it can be justified for critical APIs.
  4. Runtime choice – for simple validation or webhook tasks, prefer Python or Node.js. When the workload demands high CPU performance, consider Go, which I am studying intensively in 2026.

5. Real Costs vs EC2: When the Numbers Add Up

A point that always worries me when recommending solutions to small business owners is the total cost of ownership. Let’s compare a typical scenario:

ServiceTypical configurationApproximate monthly cost*
AWS Lambda128 MB, 200 ms per invocation, 1 M executionsUS$ 0.75
EC2 t3.micro1 vCPU, 1 GB RAM, 24 h/30 daysUS$ 8.50
RDS (PostgreSQL)db.t3.micro, 20 GBUS$ 15.00

*Values based on the price in the us-east-1 region (2026) and without discounts.

The difference is striking when the load is sporadic. If the API receives, for example, 10 k requests per month, Lambda still stays below a dollar, while EC2 continues consuming resources even with no traffic.

Moreover, Lambda’s automatic elasticity eliminates the need for manual scaling, reducing the risk of under‑ or over‑provisioning.

6. Automating the infrastructure: SAM vs CDK

To get the function code and its configuration into production in a reproducible way, I use Infrastructure as Code (IaC) tools.

6.1 AWS SAM (Serverless Application Model)

SAM is an extension of CloudFormation focused on serverless resources. The YAML syntax is concise and already includes abstractions like AWS::Serverless::Function. When I need to version a function quickly, this is my first step.

Globals:
  Function:
    Timeout: 10
    Runtime: nodejs20.x

Resources:
  ProcessaLead:
    Type: AWS::Serverless::Function
    Properties:
      Handler: index.handler
      CodeUri: ./lead-processor
      Events:
        Queue:
          Type: SQS
          Properties:
            Queue: !GetAtt LeadQueue.Arn

6.2 AWS CDK (Cloud Development Kit)

When the project grows and involves multiple stacks (for example, integration between OSPOS, S3, DynamoDB, and Lambda), I prefer CDK because it lets you write the infrastructure in TypeScript or Python, reusing program logic.

import * as cdk from 'aws-cdk-lib';
import { Function, Runtime, Code } from 'aws-cdk-lib/aws-lambda';
import { RestApi } from 'aws-cdk-lib/aws-apigateway';
import { Table } from 'aws-cdk-lib/aws-dynamodb';

export class InventarioStack extends cdk.Stack {
  constructor(scope: cdk.App, id: string) {
    super(scope, id);

    const estoque = new Table(this, 'Estoque', {
      partitionKey: { name: 'sku', type: cdk.aws_dynamodb.AttributeType.STRING },
      removalPolicy: cdk.RemovalPolicy.DESTROY,
    });

    const lambdaSync = new Function(this, 'SyncLambda', {
      runtime: Runtime.PYTHON_3_11,
      handler: 'sync.handler',
      code: Code.fromAsset('lambda/sync'),
      environment: { TABLE_NAME: estoque.tableName },
    });

    estoque.grantReadWriteData(lambdaSync);

    const api = new RestApi(this, 'InventarioAPI');
    const recurso = api.root.addResource('sync');
    recurso.addMethod('POST', new cdk.aws_apigateway.LambdaIntegration(lambdaSync));
  }
}

With cdk deploy the full stack is created, and future changes are handled as a diff, avoiding surprises.

7. Practical Conclusion

Serverless on AWS isn’t just a fad; it’s a strategy that lets developers – even those coming from backgrounds like retail and POS – deliver cheap, scalable APIs and automations.

In my journey, the experience of migrating 10 k products between store systems taught me that integration must be lightweight and resilient. The Lambdas I wrote to connect OSPOS to Mercado Livre proved that, with just a few megabytes of code, you can move thousands of orders without needing a dedicated server.

If you still have doubts about feasibility, try creating a simple function, hook it up to API Gateway, and measure the cost in the AWS console. The price difference compared to a traditional EC2 is usually eye‑opening.

Takeaways

  • Start small: a single Lambda can replace an entire server for occasional tasks.
  • Use Python or Node.js for lower cold start times and lightweight packages.
  • Separate dependencies into Layers to reduce the size of the deployment package.
  • Monitor costs via AWS Cost Explorer; most serverless functions stay below 1 USD per million invocations.
  • Automate with SAM or CDK to ensure the infrastructure tracks the code, making continuous delivery easier.
  • Test cold start mitigation strategies (keep‑alive, provisioned concurrency) when latency is critical.

Post glossary

serverless
A model where the cloud provider manages servers and charges per execution, scaling functions on demand.
event
A relevant system occurrence (click, order, message) that triggers asynchronous actions, the basis of event-driven architectures.
service
A software unit with a well-defined responsibility, exposed through an interface and reusable by other system parts.
python
A high-level programming language with readable syntax, popular for scripts, automation, data and AI.
node
Node.js: a JavaScript runtime outside the browser, built on the V8 engine, used mainly for servers and command-line tools.
api
Application Programming Interface: a set of rules that lets one program talk to another, exposing data and features in a standardized way.
http
Hypertext Transfer Protocol: the communication protocol used between browsers and web servers.
url
Uniform Resource Locator: the unique address that locates a resource on the web.
cpu
Central Processing Unit: the processor that executes program instructions, the computer's 'brain'.
webhook
An automatic HTTP call one system makes to another when an event happens, removing the need for polling.
Go
Google's compiled language known for simple concurrency (goroutines) and performance, widely used in backend services.
ram
Random Access Memory: fast volatile memory used for data in use while programs run.
postgresql
Advanced open source relational database known for extensibility, native JSON support and ACID compliance.
typescript
Microsoft's statically typed superset of JavaScript that adds compile-time types, catching errors before the code runs.
Ismael Douglas

About the author

Ismael Douglas

Full Stack developer passionate about building fast, well-crafted web products. I write about what I learn along the way — code, infra and best practices.

Check out my portfolio →