Arquitetura Serverless em 2025: Por Que Sua Próxima API Deveria Ser Serverless
Olá HaWkers, você ainda mantém servidores rodando 24/7 esperando requisições? Paga por capacidade que não usa 90% do tempo? Passa madrugadas configurando auto-scaling?
Serverless mudou o jogo. Em 2025, a questão não é mais "devo usar serverless?", mas sim "por que ainda não estou usando?". Vou te mostrar exatamente como funciona, quando usar, e código real de produção.
O Que É Serverless (De Verdade)?
Serverless NÃO significa "sem servidores". Significa que você não gerencia servidores.
Modelo tradicional:
- Você provisiona EC2/VPS
- Instala Node.js, PM2, nginx
- Configura load balancer, auto-scaling
- Monitora uso de CPU/RAM
- Paga 24/7, mesmo sem tráfego
Modelo serverless:
- Você escreve função
- Deploy com um comando
- Plataforma gerencia tudo
- Escala automaticamente (0 a milhões de requisições)
- Paga apenas por execução real
Principais Plataformas em 2025
1. AWS Lambda (O Gigante)
- Maior ecossistema (integração com 200+ serviços AWS)
- Suporta Node.js, Python, Go, Java, .NET, Rust
- Cold start: ~100-200ms (melhorou muito)
- Pricing: $0.20 por 1M requisições + compute time
2. Vercel Functions (O Dev-Friendly)
- Deploy integrado com Git
- Edge network global
- Perfeito para Next.js/React
- Cold start: ~50ms
- Pricing: 100k invocações grátis, depois $0.40/1M
3. Cloudflare Workers (O Mais Rápido)
- Edge computing (roda em 300+ cidades)
- Cold start: ~0ms (sempre "quente")
- V8 isolates (não containers)
- Pricing: 100k/dia grátis, depois $0.50/1M
4. Netlify Functions (O Simples)
- Integração perfeita com JAMstack
- Deploy automático
- Good for startups
- Pricing: 125k/mês grátis
Sua Primeira Function: Hello World Real
AWS Lambda + API Gateway:
// handler.js
exports.handler = async (event) => {
const { name = 'World' } = event.queryStringParameters || {};
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
body: JSON.stringify({
message: `Hello, ${name}!`,
timestamp: new Date().toISOString(),
requestId: event.requestContext.requestId
})
};
};
Deploy com Serverless Framework:
# serverless.yml
service: hello-world-api
provider:
name: aws
runtime: nodejs20.x
region: us-east-1
functions:
hello:
handler: handler.handler
events:
- httpApi:
path: /hello
method: get
# Deploy com um comando
# serverless deploy
Resultado: API rodando em https://xxxxxxx.execute-api.us-east-1.amazonaws.com/hello?name=Jeff
Caso Real: API de E-commerce
Vamos construir API completa de produtos com CRUD, validação e cache.
// api/products/index.js
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocumentClient, ScanCommand } from '@aws-sdk/lib-dynamodb';
const client = new DynamoDBClient({});
const docClient = DynamoDBDocumentClient.from(client);
export const handler = async (event) => {
const { httpMethod, pathParameters, body } = event;
try {
switch (httpMethod) {
case 'GET':
return pathParameters?.id
? await getProduct(pathParameters.id)
: await listProducts();
case 'POST':
return await createProduct(JSON.parse(body));
case 'PUT':
return await updateProduct(pathParameters.id, JSON.parse(body));
case 'DELETE':
return await deleteProduct(pathParameters.id);
default:
return response(405, { error: 'Method not allowed' });
}
} catch (error) {
console.error('Error:', error);
return response(500, { error: error.message });
}
};
// Helper: Resposta padronizada
function response(statusCode, data) {
return {
statusCode,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
body: JSON.stringify(data)
};
}
// GET /products
async function listProducts() {
const command = new ScanCommand({
TableName: process.env.PRODUCTS_TABLE
});
const result = await docClient.send(command);
return response(200, {
products: result.Items,
count: result.Count
});
}
// GET /products/:id
async function getProduct(id) {
const { GetCommand } = await import('@aws-sdk/lib-dynamodb');
const command = new GetCommand({
TableName: process.env.PRODUCTS_TABLE,
Key: { id }
});
const result = await docClient.send(command);
if (!result.Item) {
return response(404, { error: 'Product not found' });
}
return response(200, result.Item);
}
// POST /products
async function createProduct(data) {
const { PutCommand } = await import('@aws-sdk/lib-dynamodb');
// Validação
const errors = validateProduct(data);
if (errors.length > 0) {
return response(400, { errors });
}
const product = {
id: crypto.randomUUID(),
...data,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
};
const command = new PutCommand({
TableName: process.env.PRODUCTS_TABLE,
Item: product
});
await docClient.send(command);
return response(201, product);
}
// PUT /products/:id
async function updateProduct(id, data) {
const { UpdateCommand } = await import('@aws-sdk/lib-dynamodb');
const errors = validateProduct(data, true);
if (errors.length > 0) {
return response(400, { errors });
}
// Construir expression de update dinamicamente
const updateExpressions = [];
const expressionAttributeNames = {};
const expressionAttributeValues = {};
Object.keys(data).forEach((key, index) => {
updateExpressions.push(`#field${index} = :value${index}`);
expressionAttributeNames[`#field${index}`] = key;
expressionAttributeValues[`:value${index}`] = data[key];
});
updateExpressions.push('#updatedAt = :updatedAt');
expressionAttributeNames['#updatedAt'] = 'updatedAt';
expressionAttributeValues[':updatedAt'] = new Date().toISOString();
const command = new UpdateCommand({
TableName: process.env.PRODUCTS_TABLE,
Key: { id },
UpdateExpression: `SET ${updateExpressions.join(', ')}`,
ExpressionAttributeNames: expressionAttributeNames,
ExpressionAttributeValues: expressionAttributeValues,
ReturnValues: 'ALL_NEW'
});
const result = await docClient.send(command);
return response(200, result.Attributes);
}
// DELETE /products/:id
async function deleteProduct(id) {
const { DeleteCommand } = await import('@aws-sdk/lib-dynamodb');
const command = new DeleteCommand({
TableName: process.env.PRODUCTS_TABLE,
Key: { id }
});
await docClient.send(command);
return response(204, null);
}
// Validação de produto
function validateProduct(data, isUpdate = false) {
const errors = [];
if (!isUpdate && !data.name) {
errors.push('Name is required');
}
if (data.name && data.name.length < 3) {
errors.push('Name must be at least 3 characters');
}
if (!isUpdate && data.price === undefined) {
errors.push('Price is required');
}
if (data.price !== undefined && (data.price < 0 || isNaN(data.price))) {
errors.push('Price must be a positive number');
}
return errors;
}
Otimizações Essenciais
1. Reduzir Cold Starts:
// ❌ Ruim: Import dentro da função
export const handler = async () => {
const AWS = require('aws-sdk'); // Import a cada execução
const db = new AWS.DynamoDB.DocumentClient();
// ...
};
// ✅ Bom: Import no topo (reutilizado)
const { DynamoDBDocumentClient } = require('@aws-sdk/lib-dynamodb');
const docClient = DynamoDBDocumentClient.from(new DynamoDBClient({}));
export const handler = async () => {
// docClient já está instanciado
};
// ✅ Ainda melhor: Lazy loading
let docClient;
function getDocClient() {
if (!docClient) {
const { DynamoDBClient } = require('@aws-sdk/client-dynamodb');
const { DynamoDBDocumentClient } = require('@aws-sdk/lib-dynamodb');
docClient = DynamoDBDocumentClient.from(new DynamoDBClient({}));
}
return docClient;
}
2. Provisioned Concurrency (Para APIs críticas):
# serverless.yml
functions:
api:
handler: handler.handler
provisionedConcurrency: 2 # Sempre 2 instâncias "quentes"
reservedConcurrency: 100 # Limita concorrência máxima
3. Caching Inteligente:
// Cache em memória (persiste entre invocações da mesma instância)
let cachedProducts = null;
let cacheExpiry = 0;
async function getProductsCached() {
const now = Date.now();
if (cachedProducts && now < cacheExpiry) {
console.log('Cache hit!');
return cachedProducts;
}
console.log('Cache miss, fetching...');
const products = await fetchProductsFromDB();
cachedProducts = products;
cacheExpiry = now + (5 * 60 * 1000); // Cache por 5 minutos
return products;
}
4. Bundle Size Otimizado:
// ❌ Ruim: Import completo
const _ = require('lodash');
// ✅ Bom: Import específico
const debounce = require('lodash/debounce');
// ✅ Ainda melhor: Tree-shaking com ES modules
import { debounce } from 'lodash-es';
Integrações Poderosas
1. S3 Triggers (Processar uploads):
// Redimensionar imagens automaticamente
export const handler = async (event) => {
const s3 = new S3Client({});
const sharp = require('sharp');
for (const record of event.Records) {
const bucket = record.s3.bucket.name;
const key = record.s3.object.key;
// Download da imagem
const { Body } = await s3.send(new GetObjectCommand({
Bucket: bucket,
Key: key
}));
const buffer = await streamToBuffer(Body);
// Redimensionar
const resized = await sharp(buffer)
.resize(800, 600, { fit: 'inside' })
.jpeg({ quality: 80 })
.toBuffer();
// Upload da versão redimensionada
await s3.send(new PutObjectCommand({
Bucket: bucket,
Key: `thumbnails/${key}`,
Body: resized,
ContentType: 'image/jpeg'
}));
console.log(`✓ Resized ${key}`);
}
};
2. EventBridge Scheduled (Cron jobs):
functions:
sendDailyReport:
handler: handlers/reports.daily
events:
- schedule:
rate: cron(0 9 * * ? *) # Todo dia às 9h UTC
enabled: true
// handlers/reports.js
export const daily = async () => {
const users = await fetchActiveUsers();
const report = generateReport(users);
await sendEmail({
to: 'admin@example.com',
subject: 'Daily Report',
body: report
});
console.log(`✓ Sent report to ${users.length} users`);
};
3. SQS Queues (Processamento assíncrono):
// Producer: Envia mensagens para fila
export const createOrder = async (event) => {
const sqs = new SQSClient({});
const order = JSON.parse(event.body);
await sqs.send(new SendMessageCommand({
QueueUrl: process.env.ORDERS_QUEUE_URL,
MessageBody: JSON.stringify(order)
}));
return response(202, { message: 'Order queued for processing' });
};
// Consumer: Processa mensagens da fila
export const processOrder = async (event) => {
for (const record of event.Records) {
const order = JSON.parse(record.body);
try {
await processPayment(order);
await sendConfirmationEmail(order);
await updateInventory(order);
console.log(`✓ Order ${order.id} processed`);
} catch (error) {
console.error(`✗ Order ${order.id} failed:`, error);
// Mensagem volta para fila (retry automático)
throw error;
}
}
};
Custos Reais: Comparação
Cenário: API com 1M requisições/mês, 200ms médio
Solução | Custo/mês | Detalhes |
---|---|---|
EC2 t3.small (24/7) | $17 | + $5 Load Balancer = $22 |
AWS Lambda | $0.40 | $0.20 req + $0.20 compute |
Vercel Functions | $0.40 | Após 100k grátis |
Cloudflare Workers | $5 | Plano pago ($5/mês) |
Vantagem serverless: Economiza 80-90% em tráfego baixo/médio.
Quando serverless fica caro:
- Tráfego constante alto (>10M req/mês)
- Funções de longa duração (>15min)
Quando NÃO Usar Serverless
Evite para:
- WebSockets persistentes (use EC2/ECS)
- Processamento pesado (ML training, video encoding)
- Latência crítica (<10ms consistente)
- Estado persistente em memória (caches grandes)
Alternativa híbrida:
// API serverless + Worker tradicional
// Lambda para endpoints
export const api = async (event) => {
// Processa requisição leve
// Para tarefas pesadas, envia para fila
await sqs.sendMessage({
queueUrl: WORKER_QUEUE,
body: JSON.stringify({ task: 'heavy-compute' })
});
};
// EC2 worker processa fila
// Roda 24/7, otimizado para tarefas pesadas
O Futuro do Serverless
Tendências 2025:
- Edge computing (Cloudflare Workers, Vercel Edge)
- Serverless containers (AWS Fargate, Cloud Run)
- Streaming responses (dados progressivos)
- IA integrada (embeddings, summarization)
Para entender melhor padrões assíncronos essenciais em serverless, confira Descobrindo o Poder do Async/Await em JavaScript.
Bora pra cima! 🦅
💻 Domine JavaScript de Verdade
O conhecimento que você adquiriu neste artigo é só o começo. Há técnicas, padrões e práticas que transformam desenvolvedores iniciantes em profissionais requisitados.
Invista no Seu Futuro
Preparei um material completo para você dominar JavaScript:
Formas de pagamento:
- 3x de R$34,54 sem juros
- ou R$97,90 à vista