Voltar para o Blog

Arquitetura Serverless com JavaScript: Guia Completo com AWS Lambda e Vercel

Ola HaWkers, serverless deixou de ser uma buzzword para se tornar uma das arquiteturas mais adotadas em producao. Em 2025, entender serverless nao e mais opcional - e uma habilidade essencial para desenvolvedores que querem construir aplicacoes escalaveis sem gerenciar infraestrutura.

Voce ja pensou em como seria nao se preocupar com servidores, provisionamento, ou escalabilidade? Pagar apenas pelo que usa? Essa e a promessa do serverless, e neste artigo vamos explorar como implementa-lo na pratica.

O Que e Serverless e Por Que Importa

Serverless nao significa "sem servidor" - significa que voce nao precisa gerenciar servidores. A infraestrutura e totalmente abstraida, permitindo que voce foque apenas no codigo.

Serverless Architecture

Vantagens do Modelo Serverless

Custo:

  • Pague apenas pelo tempo de execucao
  • Sem custos de servidores ociosos
  • Escala automatica sem provisionar recursos extras

Operacional:

  • Zero gerenciamento de infraestrutura
  • Deploy simplificado
  • Alta disponibilidade automatica

Desenvolvimento:

  • Foco total no codigo
  • Iteracao rapida
  • Menor time-to-market

AWS Lambda: O Padrao da Industria

AWS Lambda e o servico serverless mais utilizado no mundo. Vejamos como criar funcoes robustas com Node.js.

Estrutura Basica de uma Lambda

// handler.ts
import { APIGatewayProxyHandler, APIGatewayProxyResult } from 'aws-lambda';

interface UsuarioInput {
  nome: string;
  email: string;
  idade?: number;
}

interface RespostaPadrao {
  sucesso: boolean;
  mensagem: string;
  dados?: unknown;
}

// Funcao utilitaria para respostas padronizadas
const criarResposta = (
  statusCode: number,
  body: RespostaPadrao
): APIGatewayProxyResult => ({
  statusCode,
  headers: {
    'Content-Type': 'application/json',
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Credentials': true
  },
  body: JSON.stringify(body)
});

export const criarUsuario: APIGatewayProxyHandler = async (event) => {
  try {
    // Validar corpo da requisicao
    if (!event.body) {
      return criarResposta(400, {
        sucesso: false,
        mensagem: 'Corpo da requisicao e obrigatorio'
      });
    }

    const dados: UsuarioInput = JSON.parse(event.body);

    // Validacoes de negocio
    if (!dados.nome || !dados.email) {
      return criarResposta(400, {
        sucesso: false,
        mensagem: 'Nome e email sao obrigatorios'
      });
    }

    if (!dados.email.includes('@')) {
      return criarResposta(400, {
        sucesso: false,
        mensagem: 'Email invalido'
      });
    }

    // Simular criacao no banco (em producao, use DynamoDB ou outro)
    const novoUsuario = {
      id: Date.now().toString(),
      ...dados,
      criadoEm: new Date().toISOString()
    };

    // Log para CloudWatch
    console.log('Usuario criado:', JSON.stringify(novoUsuario));

    return criarResposta(201, {
      sucesso: true,
      mensagem: 'Usuario criado com sucesso',
      dados: novoUsuario
    });

  } catch (error) {
    console.error('Erro ao criar usuario:', error);

    return criarResposta(500, {
      sucesso: false,
      mensagem: 'Erro interno do servidor'
    });
  }
};

Configuracao com Serverless Framework

# serverless.yml
service: api-usuarios

frameworkVersion: '3'

provider:
  name: aws
  runtime: nodejs20.x
  region: sa-east-1
  stage: ${opt:stage, 'dev'}
  memorySize: 256
  timeout: 10
  environment:
    STAGE: ${self:provider.stage}
    TABLE_NAME: ${self:service}-${self:provider.stage}-usuarios

  iam:
    role:
      statements:
        - Effect: Allow
          Action:
            - dynamodb:GetItem
            - dynamodb:PutItem
            - dynamodb:UpdateItem
            - dynamodb:DeleteItem
            - dynamodb:Query
            - dynamodb:Scan
          Resource:
            - !GetAtt UsuariosTable.Arn

functions:
  criarUsuario:
    handler: src/handlers/usuarios.criarUsuario
    events:
      - http:
          path: usuarios
          method: post
          cors: true

  buscarUsuario:
    handler: src/handlers/usuarios.buscarUsuario
    events:
      - http:
          path: usuarios/{id}
          method: get
          cors: true

  listarUsuarios:
    handler: src/handlers/usuarios.listarUsuarios
    events:
      - http:
          path: usuarios
          method: get
          cors: true

resources:
  Resources:
    UsuariosTable:
      Type: AWS::DynamoDB::Table
      Properties:
        TableName: ${self:provider.environment.TABLE_NAME}
        AttributeDefinitions:
          - AttributeName: id
            AttributeType: S
        KeySchema:
          - AttributeName: id
            KeyType: HASH
        BillingMode: PAY_PER_REQUEST

plugins:
  - serverless-esbuild
  - serverless-offline

custom:
  esbuild:
    bundle: true
    minify: true
    sourcemap: true
    target: node20

Integracao com DynamoDB

// src/lib/dynamodb.ts
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import {
  DynamoDBDocumentClient,
  GetCommand,
  PutCommand,
  QueryCommand,
  ScanCommand
} from '@aws-sdk/lib-dynamodb';

const client = new DynamoDBClient({
  region: process.env.AWS_REGION || 'sa-east-1'
});

export const dynamoDB = DynamoDBDocumentClient.from(client);

// src/handlers/usuarios.ts
import { APIGatewayProxyHandler } from 'aws-lambda';
import { dynamoDB } from '../lib/dynamodb';
import { GetCommand, PutCommand, ScanCommand } from '@aws-sdk/lib-dynamodb';
import { v4 as uuidv4 } from 'uuid';

const TABLE_NAME = process.env.TABLE_NAME!;

export const criarUsuario: APIGatewayProxyHandler = async (event) => {
  try {
    const dados = JSON.parse(event.body || '{}');

    const usuario = {
      id: uuidv4(),
      nome: dados.nome,
      email: dados.email,
      criadoEm: new Date().toISOString(),
      atualizadoEm: new Date().toISOString()
    };

    await dynamoDB.send(new PutCommand({
      TableName: TABLE_NAME,
      Item: usuario,
      ConditionExpression: 'attribute_not_exists(id)'
    }));

    return {
      statusCode: 201,
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ sucesso: true, dados: usuario })
    };
  } catch (error) {
    console.error('Erro:', error);
    return {
      statusCode: 500,
      body: JSON.stringify({ sucesso: false, erro: 'Erro interno' })
    };
  }
};

export const buscarUsuario: APIGatewayProxyHandler = async (event) => {
  try {
    const { id } = event.pathParameters || {};

    if (!id) {
      return {
        statusCode: 400,
        body: JSON.stringify({ sucesso: false, mensagem: 'ID obrigatorio' })
      };
    }

    const resultado = await dynamoDB.send(new GetCommand({
      TableName: TABLE_NAME,
      Key: { id }
    }));

    if (!resultado.Item) {
      return {
        statusCode: 404,
        body: JSON.stringify({ sucesso: false, mensagem: 'Usuario nao encontrado' })
      };
    }

    return {
      statusCode: 200,
      body: JSON.stringify({ sucesso: true, dados: resultado.Item })
    };
  } catch (error) {
    console.error('Erro:', error);
    return {
      statusCode: 500,
      body: JSON.stringify({ sucesso: false, erro: 'Erro interno' })
    };
  }
};

export const listarUsuarios: APIGatewayProxyHandler = async () => {
  try {
    const resultado = await dynamoDB.send(new ScanCommand({
      TableName: TABLE_NAME,
      Limit: 100
    }));

    return {
      statusCode: 200,
      body: JSON.stringify({
        sucesso: true,
        dados: resultado.Items || [],
        total: resultado.Count
      })
    };
  } catch (error) {
    console.error('Erro:', error);
    return {
      statusCode: 500,
      body: JSON.stringify({ sucesso: false, erro: 'Erro interno' })
    };
  }
};

Vercel Edge Functions: Performance no Edge

Vercel oferece uma experiencia serverless focada em performance, executando funcoes no edge (mais proximo do usuario).

API Routes com Next.js

// app/api/produtos/route.ts
import { NextRequest, NextResponse } from 'next/server';

interface Produto {
  id: string;
  nome: string;
  preco: number;
  estoque: number;
}

// Simulando banco de dados
const produtos: Produto[] = [
  { id: '1', nome: 'Notebook Pro', preco: 4500, estoque: 10 },
  { id: '2', nome: 'Mouse Gamer', preco: 250, estoque: 50 },
  { id: '3', nome: 'Teclado Mecanico', preco: 450, estoque: 30 }
];

export async function GET(request: NextRequest) {
  const { searchParams } = new URL(request.url);
  const categoria = searchParams.get('categoria');
  const limite = parseInt(searchParams.get('limite') || '10');

  let resultado = [...produtos];

  if (categoria) {
    // Filtrar por categoria se necessario
  }

  resultado = resultado.slice(0, limite);

  return NextResponse.json({
    sucesso: true,
    dados: resultado,
    total: resultado.length
  });
}

export async function POST(request: NextRequest) {
  try {
    const dados = await request.json();

    // Validacoes
    if (!dados.nome || !dados.preco) {
      return NextResponse.json(
        { sucesso: false, erro: 'Nome e preco sao obrigatorios' },
        { status: 400 }
      );
    }

    const novoProduto: Produto = {
      id: Date.now().toString(),
      nome: dados.nome,
      preco: dados.preco,
      estoque: dados.estoque || 0
    };

    produtos.push(novoProduto);

    return NextResponse.json(
      { sucesso: true, dados: novoProduto },
      { status: 201 }
    );
  } catch (error) {
    return NextResponse.json(
      { sucesso: false, erro: 'Erro ao processar requisicao' },
      { status: 500 }
    );
  }
}

Edge Functions para Personalizacao

// middleware.ts
import { NextRequest, NextResponse } from 'next/server';

export const config = {
  matcher: ['/api/:path*', '/dashboard/:path*']
};

export function middleware(request: NextRequest) {
  // Detectar pais do usuario via header
  const pais = request.geo?.country || 'BR';
  const cidade = request.geo?.city || 'Unknown';

  // Adicionar headers de localizacao
  const response = NextResponse.next();
  response.headers.set('x-user-country', pais);
  response.headers.set('x-user-city', cidade);

  // Rate limiting simples baseado em IP
  const ip = request.ip || 'unknown';
  const rateLimitKey = `rate-limit:${ip}`;

  // Em producao, use um servico como Upstash Redis
  // para rate limiting distribuido

  // Log da requisicao
  console.log(`[${new Date().toISOString()}] ${request.method} ${request.url} - ${pais}/${cidade}`);

  return response;
}

// app/api/personalizacao/route.ts
import { NextRequest, NextResponse } from 'next/server';

export const runtime = 'edge'; // Executar no edge

export async function GET(request: NextRequest) {
  const pais = request.headers.get('x-user-country') || 'BR';

  // Personalizacao baseada em localizacao
  const configuracoes = {
    BR: {
      moeda: 'BRL',
      idioma: 'pt-BR',
      formatoData: 'dd/MM/yyyy',
      ofertas: ['frete-gratis-sudeste', 'desconto-pix']
    },
    US: {
      moeda: 'USD',
      idioma: 'en-US',
      formatoData: 'MM/dd/yyyy',
      ofertas: ['free-shipping', 'black-friday']
    }
  };

  const config = configuracoes[pais as keyof typeof configuracoes] || configuracoes.BR;

  return NextResponse.json({
    sucesso: true,
    dados: config,
    processadoEm: 'edge',
    regiao: request.geo?.region || 'unknown'
  });
}

Padroes e Melhores Praticas

Cold Start: Minimizando Latencia

Cold start e o tempo que leva para uma funcao inicializar na primeira execucao. Aqui estao estrategias para minimiza-lo:

// EVITE: imports dinamicos dentro do handler
export const handler = async () => {
  const { DynamoDBClient } = await import('@aws-sdk/client-dynamodb'); // Cold start!
  // ...
};

// PREFIRA: imports no topo do arquivo
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';

// Inicialize conexoes fora do handler (reutilizadas entre invocacoes)
const client = new DynamoDBClient({});

export const handler = async () => {
  // client ja esta inicializado
};

Estrutura de Projeto Recomendada

projeto-serverless/
├── src/
│   ├── handlers/           # Funcoes Lambda
│   │   ├── usuarios.ts
│   │   ├── produtos.ts
│   │   └── pedidos.ts
│   ├── lib/                # Utilitarios compartilhados
│   │   ├── dynamodb.ts
│   │   ├── s3.ts
│   │   └── validacao.ts
│   ├── types/              # Tipos TypeScript
│   │   └── index.ts
│   └── middlewares/        # Middlewares customizados
│       └── autenticacao.ts
├── tests/
│   ├── unit/
│   └── integration/
├── serverless.yml
├── package.json
└── tsconfig.json

Tratamento de Erros Robusto

// src/lib/erros.ts
export class ErroAplicacao extends Error {
  constructor(
    public mensagem: string,
    public statusCode: number = 500,
    public codigo?: string
  ) {
    super(mensagem);
    this.name = 'ErroAplicacao';
  }
}

export class ErroValidacao extends ErroAplicacao {
  constructor(mensagem: string) {
    super(mensagem, 400, 'ERRO_VALIDACAO');
  }
}

export class ErroNaoEncontrado extends ErroAplicacao {
  constructor(recurso: string) {
    super(`${recurso} nao encontrado`, 404, 'NAO_ENCONTRADO');
  }
}

export class ErroNaoAutorizado extends ErroAplicacao {
  constructor() {
    super('Nao autorizado', 401, 'NAO_AUTORIZADO');
  }
}

// src/lib/handler-wrapper.ts
import { APIGatewayProxyHandler, APIGatewayProxyResult } from 'aws-lambda';
import { ErroAplicacao } from './erros';

type HandlerFn = (event: any, context: any) => Promise<any>;

export const wrapHandler = (fn: HandlerFn): APIGatewayProxyHandler => {
  return async (event, context): Promise<APIGatewayProxyResult> => {
    try {
      const resultado = await fn(event, context);

      return {
        statusCode: 200,
        headers: {
          'Content-Type': 'application/json',
          'Access-Control-Allow-Origin': '*'
        },
        body: JSON.stringify({ sucesso: true, dados: resultado })
      };
    } catch (error) {
      console.error('Erro no handler:', error);

      if (error instanceof ErroAplicacao) {
        return {
          statusCode: error.statusCode,
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            sucesso: false,
            erro: error.mensagem,
            codigo: error.codigo
          })
        };
      }

      return {
        statusCode: 500,
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          sucesso: false,
          erro: 'Erro interno do servidor'
        })
      };
    }
  };
};

// Uso:
import { wrapHandler } from '../lib/handler-wrapper';
import { ErroValidacao, ErroNaoEncontrado } from '../lib/erros';

export const buscarProduto = wrapHandler(async (event) => {
  const { id } = event.pathParameters;

  if (!id) {
    throw new ErroValidacao('ID do produto e obrigatorio');
  }

  const produto = await buscarProdutoNoBanco(id);

  if (!produto) {
    throw new ErroNaoEncontrado('Produto');
  }

  return produto;
});

Quando Usar (e Quando Evitar) Serverless

Casos de Uso Ideais

Use serverless quando:

  • Cargas de trabalho imprevisiveis ou com picos
  • APIs REST/GraphQL
  • Processamento de eventos (webhooks, filas)
  • Automacoes e tarefas agendadas
  • Prototipacao rapida
  • Projetos com orcamento limitado

Quando Considerar Alternativas

Evite serverless quando:

  • Processamento de longa duracao (>15 minutos)
  • Aplicacoes que exigem latencia ultra-baixa consistente
  • Workloads com alta utilizacao constante (pode ser mais caro)
  • Necessidade de estado persistente em memoria

Comparativo de Custos

Cenario: API com 1 milhao de requisicoes/mes

Provedor Custo Estimado
AWS Lambda ~$0.20 (128MB, 100ms)
Vercel Pro Incluido no plano $20/mes
Google Cloud Functions ~$0.40
Azure Functions ~$0.20

Os custos reais variam com memoria, tempo de execucao e transferencia de dados.

Monitoramento e Observabilidade

Para producao, e essencial ter visibilidade sobre suas funcoes:

Ferramentas recomendadas:

  • AWS CloudWatch - Logs e metricas nativas
  • Datadog - APM completo para serverless
  • Lumigo - Debugging visual de serverless
  • Sentry - Tracking de erros

Se voce quer se aprofundar em performance de aplicacoes web, recomendo conferir o artigo Web APIs Modernas do Navegador que complementa bem os conceitos de serverless.

Bora pra cima! 🦅

Comentários (0)

Esse artigo ainda não possui comentários 😢. Seja o primeiro! 🚀🦅

Adicionar comentário