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.

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.jsonTratamento 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.

